import {
  normalizePanelWidth,
  packLayout,
  visiblePanels,
  type DashboardLayout,
  type DashboardPanel,
  type PanelWidth,
} from "./dashboard-layout";

/** Container widths. Narrow stacks, medium uses six columns, wide uses twelve. */
export const STACK_CONTAINER = 640;
export const WIDE_CONTAINER = 960;

export type GridColumns = 1 | 6 | 12;

export type GridPlacement = {
  i: string;
  x: number;
  y: number;
  w: number;
  h: number;
};

const MEDIUM_WIDTHS = [2, 3, 4, 6] as const;
const STORED_FROM_MEDIUM: Record<(typeof MEDIUM_WIDTHS)[number], PanelWidth> = {
  2: 4,
  3: 6,
  4: 8,
  6: 12,
};
const MEDIUM_FROM_STORED: Record<PanelWidth, (typeof MEDIUM_WIDTHS)[number]> = {
  4: 2,
  6: 3,
  8: 4,
  12: 6,
};

export function gridColumnsForContainer(width: number): GridColumns {
  if (!Number.isFinite(width) || width < STACK_CONTAINER) return 1;
  if (width < WIDE_CONTAINER) return 6;
  return 12;
}

function nearest(value: number, choices: readonly number[]): number {
  let best = choices[0];
  let distance = Number.POSITIVE_INFINITY;
  for (const choice of choices) {
    const next = Math.abs(choice - value);
    if (next < distance) {
      distance = next;
      best = choice;
    }
  }
  return best;
}

/** Snap a resize to the columns available at this container width, staying inside the row. */
export function snapGridWidth(width: number, columns: GridColumns, max: number = columns): number {
  if (columns === 1) return 1;
  const choices = (columns === 6 ? MEDIUM_WIDTHS : [4, 6, 8, 12]).filter((choice) => choice <= max);
  if (choices.length === 0) return Math.min(max, columns === 6 ? 2 : 4);
  return nearest(width, choices);
}

export function gridWidthFromStored(width: number, columns: GridColumns): number {
  if (columns === 1) return 1;
  const stored = normalizePanelWidth(width);
  if (columns === 6) return MEDIUM_FROM_STORED[stored];
  return stored;
}

export function storedWidthFromGrid(width: number, columns: GridColumns, previous: number): PanelWidth {
  if (columns === 1) return normalizePanelWidth(previous);
  if (columns === 6) {
    const snapped = nearest(width, MEDIUM_WIDTHS) as (typeof MEDIUM_WIDTHS)[number];
    return STORED_FROM_MEDIUM[snapped];
  }
  return normalizePanelWidth(width);
}

export function rowsForContent(px: number, rowHeight = 8, margin = 12): number {
  if (!Number.isFinite(px) || px <= 0) return 6;
  return Math.max(2, Math.ceil((px + margin + 8) / (rowHeight + margin)));
}

/** Place visible panels on the column count the container can show. */
export function placementsForLayout(
  layout: DashboardLayout,
  columns: GridColumns,
  heights: Record<string, number> = {},
): GridPlacement[] {
  let x = 0;
  let y = 0;
  let rowHeight = 1;
  return visiblePanels(layout).map((panel) => {
    const w = gridWidthFromStored(panel.w, columns);
    const h = heights[panel.id] ?? 6;
    if (x > 0 && x + w > columns) {
      y += rowHeight;
      x = 0;
      rowHeight = 1;
    }
    const item = { i: panel.id, x, y, w, h };
    x += w;
    rowHeight = Math.max(rowHeight, h);
    if (x >= columns) {
      y += rowHeight;
      x = 0;
      rowHeight = 1;
    }
    return item;
  });
}

/**
 * Turn a finished drag or resize into the stored layout.
 * Order follows the visual reading order. Widths snap back to 4, 6, 8, or 12.
 * A stacked container does not overwrite the width the viewer chose on a wider screen.
 */
export function applyGridPlacement(
  layout: DashboardLayout,
  items: readonly { i: string; x: number; y: number; w: number }[],
  columns: GridColumns,
): DashboardLayout {
  const hidden = layout.panels.filter((panel) => !panel.visible);
  const byId = new Map(layout.panels.map((panel) => [panel.id, panel]));
  const seen = new Set<string>();
  const ordered: DashboardPanel[] = items
    .slice()
    .sort((a, b) => a.y - b.y || a.x - b.x)
    .flatMap((item) => {
      const panel = byId.get(item.i);
      if (!panel || !panel.visible || seen.has(panel.id)) return [];
      seen.add(panel.id);
      return [{ ...panel, w: storedWidthFromGrid(item.w, columns, panel.w) }];
    });
  for (const panel of visiblePanels(layout)) {
    if (!seen.has(panel.id)) ordered.push(panel);
  }
  return packLayout({ ...layout, panels: [...ordered, ...hidden] });
}
