import { MarkerType, type Edge, type Node } from "@xyflow/react";
import {
  COLUMN_WIDTH_MIN,
  HEADER_HEIGHT,
  LANE_GAP,
  LANE_MIN_HEIGHT,
  NODE_HEIGHT,
  NODE_WIDTH,
  NODE_WIDTH_BRANCH,
  PHASE_LABEL_WIDTH,
} from "./traceFlowConstants";
import {
  CELL_PAD_X,
  layoutCellMembers,
  computeLaneTops,
} from "./traceFlowLayout";
import { phaseAccent } from "./traceFlowNodeUtils";
import { TraceGraphLayout, TraceGraphLayoutNode, buildTraceFlowEdgePairs } from "./traceGraphLayout";
import type { TraceFlowEdgePair } from "./traceGraphSemantics";
import {
  assignTraceFlowEdgeRouting,
  enrichTraceFlowCorridorRouting,
  type TraceFlowNodeRect,
} from "./traceFlowEdgeLayout";
import {
  isDoGroupChild,
  isFlowGroupContainer,
  layoutDoGroupChildPlacements,
} from "./traceDoGroupLayout";

export type TraceFlowNodeData = {
  layoutNode: TraceGraphLayoutNode;
  compact: boolean;
  onTrace?: (params: { stockInLineId?: number; lotNo?: string; itemCode?: string }) => void;
  phaseLabel?: string;
  dateLabel?: string;
  phaseColor?: string;
  incomingHandleCount?: number;
  outgoingHandleCount?: number;
  searchActive?: boolean;
  searchMatch?: boolean;
  searchFocused?: boolean;
  nodeSelected?: boolean;
  /** DO / pick group header collapse. */
  groupCollapsed?: boolean;
  onToggleGroupCollapse?: () => void;
};

const cellKey = (laneIndex: number, column: number, stagger: number) =>
  `${laneIndex}:${column}:${stagger}`;

export const buildReactFlowGraph = (
  layout: TraceGraphLayout,
  phaseLabels: Record<string, string>,
  edgePairs?: TraceFlowEdgePair[],
): { nodes: Node<TraceFlowNodeData>[]; edges: Edge[]; graphHeight: number } => {
  const nodes: Node<TraceFlowNodeData>[] = [];
  const topLevelNodes = layout.nodes.filter((n) => !isDoGroupChild(n));

  const cells = new Map<string, TraceGraphLayoutNode[]>();
  topLevelNodes.forEach((n) => {
    const key = cellKey(n.laneIndex, n.column, n.dayPhaseStaggerIndex);
    const list = cells.get(key) ?? [];
    list.push(n);
    cells.set(key, list);
  });

  const cellLayouts = new Map<string, ReturnType<typeof layoutCellMembers>>();
  const { columnStarts, columnWidths, phaseSlotWidths, phaseSlotStarts } = layout.dayColumns;

  cells.forEach((members, key) => {
    const col = Number(key.split(":")[1]);
    const stagger = members[0]?.dayPhaseStaggerIndex ?? 0;
    const slotW = phaseSlotWidths[col]?.[stagger] ?? COLUMN_WIDTH_MIN;
    cellLayouts.set(key, layoutCellMembers(members, slotW - CELL_PAD_X * 2));
  });

  const doChildrenByGroup = new Map<string, TraceGraphLayoutNode[]>();
  layout.nodes.forEach((n) => {
    if (!n.doGroupId) return;
    const list = doChildrenByGroup.get(n.doGroupId) ?? [];
    list.push(n);
    doChildrenByGroup.set(n.doGroupId, list);
  });

  const childPlacementsByGroup = new Map<
    string,
    ReturnType<typeof layoutDoGroupChildPlacements>
  >();
  doChildrenByGroup.forEach((children, groupId) => {
    childPlacementsByGroup.set(groupId, layoutDoGroupChildPlacements(children));
  });

  const { laneTops, laneHeights, totalHeight } = computeLaneTops(layout.laneCount, cells);

  layout.columnDates.forEach((date, col) => {
    const dayW = columnWidths[col] ?? COLUMN_WIDTH_MIN;
    const dayStart = columnStarts[col] ?? col * COLUMN_WIDTH_MIN;
    nodes.push({
      id: `hdr-${col}`,
      type: "dateHeader",
      position: {
        x: PHASE_LABEL_WIDTH + dayStart + dayW / 2 - 40,
        y: 6,
      },
      data: { layoutNode: {} as TraceGraphLayoutNode, compact: false, dateLabel: date },
      draggable: false,
      selectable: false,
      connectable: false,
      focusable: false,
    });
  });

  layout.phaseOrder.forEach((phase, laneIdx) => {
    const laneH = laneHeights[laneIdx] ?? LANE_MIN_HEIGHT;
    nodes.push({
      id: `phase-${phase}`,
      type: "phaseLabel",
      position: {
        x: 8,
        y: laneTops[laneIdx] + Math.max(8, (laneH - 80) / 2),
      },
      data: {
        layoutNode: {} as TraceGraphLayoutNode,
        compact: false,
        phaseLabel: phaseLabels[phase] ?? phase,
        phaseColor: phaseAccent(phase),
      },
      draggable: false,
      selectable: false,
      connectable: false,
      focusable: false,
    });
  });

  topLevelNodes.forEach((layoutNode) => {
    const key = cellKey(layoutNode.laneIndex, layoutNode.column, layoutNode.dayPhaseStaggerIndex);
    const placed = cellLayouts.get(key)?.get(layoutNode.id);
    if (!placed) return;

    const dayStart = columnStarts[layoutNode.column] ?? layoutNode.column * COLUMN_WIDTH_MIN;
    const phaseSlotX =
      phaseSlotStarts[layoutNode.column]?.[layoutNode.dayPhaseStaggerIndex] ?? 0;
    const isGroup = isFlowGroupContainer(layoutNode);
    const groupW = layoutNode.groupBoxWidth ?? NODE_WIDTH;
    const groupH = layoutNode.groupBoxHeight ?? NODE_HEIGHT;

    nodes.push({
      id: layoutNode.id,
      type: layoutNode.kind === "PICK_GROUP" ? "pickGroup" : isGroup ? "doGroup" : "traceEvent",
      position: {
        x: PHASE_LABEL_WIDTH + dayStart + phaseSlotX + CELL_PAD_X + placed.x,
        y: laneTops[layoutNode.laneIndex] + placed.y,
      },
      data: { layoutNode, compact: placed.compact },
      draggable: false,
      connectable: false,
      selectable: !isGroup,
      zIndex: isGroup ? 0 : 1,
      style: isGroup ? { width: groupW, height: groupH } : undefined,
      width: isGroup ? groupW : undefined,
      height: isGroup ? groupH : undefined,
      measured: {
        width: isGroup ? groupW : placed.compact ? NODE_WIDTH_BRANCH : NODE_WIDTH,
        height: isGroup ? groupH : NODE_HEIGHT,
      },
    });
  });

  const parentPositions = new Map<string, { x: number; y: number }>();
  nodes.forEach((node) => {
    if (node.type === "doGroup" || node.type === "pickGroup") {
      parentPositions.set(node.id, node.position);
    }
  });

  doChildrenByGroup.forEach((children, groupId) => {
    const placements = childPlacementsByGroup.get(groupId);
    const parentPos = parentPositions.get(groupId);
    if (!placements || !parentPos) return;

    children.forEach((layoutNode) => {
      const placed = placements.get(layoutNode.id);
      if (!placed) return;

      nodes.push({
        id: layoutNode.id,
        type: "traceEvent",
        position: {
          x: parentPos.x + placed.x,
          y: parentPos.y + placed.y,
        },
        data: { layoutNode, compact: placed.compact },
        draggable: false,
        connectable: false,
        zIndex: 2,
        width: NODE_WIDTH,
        height: NODE_HEIGHT,
        measured: {
          width: NODE_WIDTH,
          height: NODE_HEIGHT,
        },
      });
    });
  });

  const flowPairs = edgePairs ?? buildTraceFlowEdgePairs(layout.nodes, layout.phaseOrder);
  const { routed, incomingCount, outgoingCount } = assignTraceFlowEdgeRouting(flowPairs);

  const nodeRects = new Map<string, TraceFlowNodeRect>();
  nodes.forEach((node) => {
    if (node.type !== "traceEvent" && node.type !== "doGroup" && node.type !== "pickGroup") return;
    const layoutNode = node.data.layoutNode;
    if (layoutNode.doGroupId) return;
    nodeRects.set(node.id, {
      x: node.position.x,
      y: node.position.y,
      width: node.measured?.width ?? NODE_WIDTH,
      height: node.measured?.height ?? NODE_HEIGHT,
      laneIndex: layoutNode.laneIndex,
    });
  });

  const corridorRouted = enrichTraceFlowCorridorRouting(routed, nodeRects, {
    laneTops,
    laneHeights,
    laneGap: LANE_GAP,
  });

  const nodesWithHandles = nodes.map((node) => {
    if (node.type !== "traceEvent" && node.type !== "doGroup" && node.type !== "pickGroup") return node;
    if (node.data.layoutNode.doGroupId) {
      return {
        ...node,
        data: {
          ...node.data,
          incomingHandleCount: 0,
          outgoingHandleCount: 0,
        },
      };
    }
    return {
      ...node,
      data: {
        ...node.data,
        incomingHandleCount: incomingCount.get(node.id) ?? 0,
        outgoingHandleCount: outgoingCount.get(node.id) ?? 0,
      },
    };
  });

  const nodeById = new Map(layout.nodes.map((n) => [n.id, n]));

  const edges: Edge[] = corridorRouted.map(
    ({
      fromId,
      toId,
      sourceHandle,
      targetHandle,
      offset,
      pathMode,
      corridorY,
      corridorEntryOffset,
      corridorExitOffset,
      corridorBranchOffset,
    }) => {
    const targetNode = nodeById.get(toId);
    const ref = targetNode?.refCode?.trim();
    const flowLabel =
      targetNode?.kind === "DO_GROUP" && targetNode.groupMemberCount
        ? String(targetNode.groupMemberCount)
        : targetNode?.kind === "PICK_GROUP" && ref
          ? ref
          : targetNode?.kind === "MATERIAL_PICK" && ref
            ? ref
            : (targetNode?.kind === "OUT" || targetNode?.kind === "DO_OUT") && ref
              ? ref
              : undefined;
    return {
      id: `e-${fromId}-${toId}`,
      source: fromId,
      target: toId,
      sourceHandle,
      targetHandle,
      type: "traceFlow",
      data: {
        offset,
        pathMode,
        corridorY,
        corridorEntryOffset,
        corridorExitOffset,
        corridorBranchOffset,
      },
      label: flowLabel,
      labelStyle: { fontSize: 10, fontWeight: 600, fill: "#5d4037" },
      labelBgStyle: { fill: "#fff8e1", fillOpacity: 0.95 },
      labelBgPadding: [4, 6] as [number, number],
      labelBgBorderRadius: 4,
      style: { stroke: "#757575", strokeWidth: 1.5, opacity: 0.75 },
      markerEnd: { type: MarkerType.ArrowClosed, color: "#757575", width: 16, height: 16 },
    };
  });

  return { nodes: nodesWithHandles, edges, graphHeight: totalHeight };
};

export const reactFlowGraphExtent = (
  layout: TraceGraphLayout,
  graphHeight?: number,
): [[number, number], [number, number]] => {
  const width = PHASE_LABEL_WIDTH + layout.dayColumns.timelineWidth + 32;
  const height = graphHeight ?? HEADER_HEIGHT + layout.laneCount * LANE_MIN_HEIGHT + 32;
  // Extra bottom room so expanded stock-take lifecycle panels stay pannable.
  const padX = 48;
  const padTop = 48;
  const padBottom = 360;
  return [
    [-padX, -padTop],
    [width + padX, height + padBottom],
  ];
};

export type ReactFlowGraphResult = ReturnType<typeof buildReactFlowGraph>;
