import {
  DO_GROUP_CHILD_WIDTH,
  DO_GROUP_HEADER,
  DO_GROUP_INNER_COLS_MAX,
  DO_GROUP_MIN_SIZE,
  DO_GROUP_PAD,
  PICK_GROUP_MIN_SIZE,
  NODE_GAP,
  NODE_HEIGHT,
  NODE_WIDTH,
} from "./traceFlowConstants";
import { TraceGraphLayoutNode, sortNodesInPhase } from "./traceGraphLayout";
import { TraceGraphNode } from "./buildTraceGraphNodes";

export type DoGroupChildPlacement = { x: number; y: number; compact: boolean };

/** Fewer columns for large groups — wider cards, no horizontal cramming. */
const preferredDoGroupCols = (memberCount: number): number => {
  if (memberCount <= 1) return 1;
  if (memberCount >= 15) return 2;
  if (memberCount >= 6) return 3;
  return 2;
};

const sumDoGroupQty = (
  children: TraceGraphLayoutNode[],
): { totalQty: number; uom?: string } | null => {
  let totalQty = 0;
  let hasQty = false;
  let uom: string | undefined;
  children.forEach((child) => {
    const qty = child.qty;
    if (qty != null && Number.isFinite(Number(qty))) {
      totalQty += Number(qty);
      hasQty = true;
    }
    if (!uom && child.uom?.trim()) uom = child.uom.trim();
  });
  return hasQty ? { totalQty, uom } : null;
};

export const computeDoGroupBoxLayout = (
  memberCount: number,
): { width: number; height: number; cols: number } => {
  const childW = DO_GROUP_CHILD_WIDTH;
  const cols = Math.min(DO_GROUP_INNER_COLS_MAX, memberCount, preferredDoGroupCols(memberCount));
  const rows = Math.ceil(memberCount / cols);
  const gridW = cols * childW + Math.max(0, cols - 1) * NODE_GAP;
  const gridH = rows * NODE_HEIGHT + Math.max(0, rows - 1) * NODE_GAP;
  return {
    width: gridW + DO_GROUP_PAD * 2,
    height: DO_GROUP_HEADER + gridH + DO_GROUP_PAD * 2,
    cols,
  };
};

export const layoutDoGroupChildPlacements = (
  children: TraceGraphLayoutNode[],
): Map<string, DoGroupChildPlacement> => {
  const sorted = [...children].sort(sortNodesInPhase);
  const { cols } = computeDoGroupBoxLayout(sorted.length);
  const childW = DO_GROUP_CHILD_WIDTH;
  const result = new Map<string, DoGroupChildPlacement>();

  sorted.forEach((child, index) => {
    const col = index % cols;
    const row = Math.floor(index / cols);
    result.set(child.id, {
      x: DO_GROUP_PAD + col * (childW + NODE_GAP),
      y: DO_GROUP_HEADER + DO_GROUP_PAD + row * (NODE_HEIGHT + NODE_GAP),
      compact: false,
    });
  });

  return result;
};

export const applyDoOutboundGrouping = (
  nodes: TraceGraphLayoutNode[],
  groupTitle: (count: number) => string,
  minSize = DO_GROUP_MIN_SIZE,
): TraceGraphLayoutNode[] => {
  const doByColumnAndWarehouse = new Map<string, TraceGraphLayoutNode[]>();

  nodes.forEach((node) => {
    if (node.kind !== "DO_OUT" || node.doGroupId) return;
    const whKey = (node.warehouseCode ?? "").trim().toUpperCase() || "__none__";
    const key = `${node.column}::${whKey}`;
    const list = doByColumnAndWarehouse.get(key) ?? [];
    list.push(node);
    doByColumnAndWarehouse.set(key, list);
  });

  const groupNodes: TraceGraphLayoutNode[] = [];
  const childGroupIds = new Map<string, string>();

  doByColumnAndWarehouse.forEach((columnNodes, key) => {
    if (columnNodes.length < minSize) return;

    const column = columnNodes[0]!.column;
    const warehouseCode = columnNodes[0]!.warehouseCode?.trim() || undefined;
    const whSlug = (warehouseCode ?? "none").replace(/[^a-zA-Z0-9_-]/g, "_");
    const groupId = `do-group-col-${column}-${whSlug}`;
    const sorted = [...columnNodes].sort(sortNodesInPhase);
    const { width, height } = computeDoGroupBoxLayout(sorted.length);
    const minSort = sorted.reduce((min, n) => Math.min(min, n.sortKey), sorted[0]!.sortKey);
    const qtySummary = sumDoGroupQty(sorted);

    sorted.forEach((child) => childGroupIds.set(child.id, groupId));

    const groupNode: TraceGraphLayoutNode = {
      id: groupId,
      kind: "DO_GROUP",
      timestamp: sorted[0]?.timestamp ?? null,
      sortKey: minSort,
      title: groupTitle(sorted.length),
      subtitle: [sorted[0]?.dayKey, warehouseCode].filter(Boolean).join(" · ") || "",
      categoryLabel: sorted[0]?.categoryLabel,
      warehouseCode,
      details: [],
      phase: sorted[0]!.phase,
      column,
      dayKey: sorted[0]!.dayKey,
      laneIndex: sorted[0]!.laneIndex,
      sequenceIndex: sorted[0]!.sequenceIndex,
      branchIndex: 0,
      branchSize: 1,
      dayPhaseStaggerIndex: sorted[0]!.dayPhaseStaggerIndex,
      groupBoxWidth: width,
      groupBoxHeight: height,
      groupMemberCount: sorted.length,
      groupTotalQty: qtySummary?.totalQty,
      groupUom: qtySummary?.uom,
    };
    groupNodes.push(groupNode);
  });

  if (!groupNodes.length) return nodes;

  return [
    ...nodes.map((node) => {
      const groupId = childGroupIds.get(node.id);
      if (!groupId) return node;
      return { ...node, doGroupId: groupId };
    }),
    ...groupNodes,
  ].sort((a, b) => {
    if (a.sortKey !== b.sortKey) return a.sortKey - b.sortKey;
    return a.id.localeCompare(b.id);
  });
};

export const applyMaterialPickGrouping = (
  nodes: TraceGraphLayoutNode[],
  groupTitle: (pickOrderCode: string, count: number) => string,
  minSize = PICK_GROUP_MIN_SIZE,
): TraceGraphLayoutNode[] => {
  const picksByKey = new Map<string, TraceGraphLayoutNode[]>();

  nodes.forEach((node) => {
    if (node.kind !== "MATERIAL_PICK" || node.doGroupId) return;
    const pickCode = node.refCode?.trim();
    if (!pickCode) return;
    const key = `${node.column}::${pickCode}`;
    const list = picksByKey.get(key) ?? [];
    list.push(node);
    picksByKey.set(key, list);
  });

  const groupNodes: TraceGraphLayoutNode[] = [];
  const childGroupIds = new Map<string, string>();

  picksByKey.forEach((columnNodes, key) => {
    if (columnNodes.length < minSize) return;

    const pickCode = key.split("::").slice(1).join("::");
    const groupId = `pick-group-${key.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
    const sorted = [...columnNodes].sort(sortNodesInPhase);
    const { width, height } = computeDoGroupBoxLayout(sorted.length);
    const minSort = sorted.reduce((min, n) => Math.min(min, n.sortKey), sorted[0]!.sortKey);

    sorted.forEach((child) => childGroupIds.set(child.id, groupId));

    const groupNode: TraceGraphLayoutNode = {
      id: groupId,
      kind: "PICK_GROUP",
      timestamp: sorted[0]?.timestamp ?? null,
      sortKey: minSort,
      title: groupTitle(pickCode, sorted.length),
      subtitle: sorted[0]?.dayKey ?? "",
      refCode: pickCode,
      refId: sorted[0]?.refId,
      docLinkKind: "pick",
      consoCode: sorted[0]?.consoCode,
      jobOrderCode: sorted[0]?.jobOrderCode,
      categoryLabel: sorted[0]?.categoryLabel,
      details: [],
      phase: sorted[0]!.phase,
      column: sorted[0]!.column,
      dayKey: sorted[0]!.dayKey,
      laneIndex: sorted[0]!.laneIndex,
      sequenceIndex: sorted[0]!.sequenceIndex,
      branchIndex: 0,
      branchSize: 1,
      dayPhaseStaggerIndex: sorted[0]!.dayPhaseStaggerIndex,
      groupBoxWidth: width,
      groupBoxHeight: height,
      groupMemberCount: sorted.length,
    };
    groupNodes.push(groupNode);
  });

  if (!groupNodes.length) return nodes;

  return [
    ...nodes.map((node) => {
      const groupId = childGroupIds.get(node.id);
      if (!groupId) return node;
      return { ...node, doGroupId: groupId };
    }),
    ...groupNodes,
  ].sort((a, b) => {
    if (a.sortKey !== b.sortKey) return a.sortKey - b.sortKey;
    return a.id.localeCompare(b.id);
  });
};

export const applyFlowNodeGrouping = (
  nodes: TraceGraphLayoutNode[],
  labels: {
    flowDoGroupTitle: (count: number) => string;
    flowPickGroupTitle: (pickOrderCode: string, count: number) => string;
  },
): TraceGraphLayoutNode[] =>
  applyMaterialPickGrouping(
    applyDoOutboundGrouping(nodes, labels.flowDoGroupTitle),
    labels.flowPickGroupTitle,
  );

export const regroupLayoutCells = (
  nodes: TraceGraphLayoutNode[],
): TraceGraphLayoutNode[][] => {
  const visible = nodes.filter((n) => !isDoGroupChild(n));
  const groups = new Map<string, TraceGraphLayoutNode[]>();

  visible.forEach((node) => {
    const key = `${node.column}:${node.laneIndex}`;
    const list = groups.get(key) ?? [];
    list.push(node);
    groups.set(key, list);
  });

  groups.forEach((list) => {
    list.sort(sortNodesInPhase);
    const size = list.length;
    list.forEach((node, idx) => {
      node.branchIndex = idx;
      node.branchSize = size;
    });
  });

  return Array.from(groups.values()).sort((a, b) => {
    const na = a[0]!;
    const nb = b[0]!;
    if (na.column !== nb.column) return na.column - nb.column;
    return na.laneIndex - nb.laneIndex;
  });
};

export const isFlowGroupContainer = (node: TraceGraphNode | TraceGraphLayoutNode): boolean =>
  node.kind === "DO_GROUP" || node.kind === "PICK_GROUP";

export const isDoGroupChild = (node: TraceGraphNode | TraceGraphLayoutNode): boolean =>
  Boolean(node.doGroupId?.trim());
