import type { Node, Edge } from "@xyflow/react";
import type {
  DiagramNode,
  DiagramEdge,
  DiagramType,
  GraphData,
} from "../types/graph";
import {
  resolveBpmnNodeType,
  resolveBpmnEdgeType,
} from "../types/bpmn/constants";
import {
  resolveErNodeType,
  resolveErEdgeType,
} from "../types/er/constants";
import {
  resolveOrgchartNodeType,
  resolveOrgchartEdgeType,
} from "../types/orgchart/constants";
import {
  resolveArchitectureNodeType,
  resolveArchitectureEdgeType,
} from "../types/architecture/constants";
import {
  resolveSequenceNodeType,
  resolveSequenceEdgeType,
} from "../types/sequence/constants";
import {
  resolveFlowchartNodeType,
  resolveFlowchartEdgeType,
} from "../types/flowchart/constants";

// ── Node Type Resolution ───────────────────────────────────────────────────

function resolveFlowNodeType(
  diagramType: DiagramType | undefined,
  nodeType: string,
): string {
  if (diagramType === "bpmn" || nodeType.startsWith("bpmn:")) {
    return resolveBpmnNodeType(nodeType);
  }
  if (diagramType === "er" || nodeType.startsWith("er:")) {
    return resolveErNodeType(nodeType);
  }
  if (diagramType === "orgchart" || nodeType.startsWith("org:")) {
    return resolveOrgchartNodeType(nodeType);
  }
  if (diagramType === "architecture" || nodeType.startsWith("arch:")) {
    return resolveArchitectureNodeType(nodeType);
  }
  if (diagramType === "sequence" || nodeType.startsWith("seq:")) {
    return resolveSequenceNodeType(nodeType);
  }
  if (diagramType === "flowchart" || nodeType.startsWith("flow:")) {
    return resolveFlowchartNodeType(nodeType);
  }
  return "default";
}

function resolveFlowEdgeType(
  diagramType: DiagramType | undefined,
  edgeType: string | undefined,
): string {
  if (diagramType === "bpmn") {
    return resolveBpmnEdgeType(edgeType);
  }
  if (diagramType === "er") {
    return resolveErEdgeType(edgeType);
  }
  if (diagramType === "orgchart") {
    return resolveOrgchartEdgeType(edgeType);
  }
  if (diagramType === "architecture") {
    return resolveArchitectureEdgeType(edgeType);
  }
  if (diagramType === "sequence") {
    return resolveSequenceEdgeType(edgeType);
  }
  if (diagramType === "flowchart") {
    return resolveFlowchartEdgeType(edgeType);
  }
  return "default";
}

// ── Graph → Flow Conversion ────────────────────────────────────────────────

export function graphNodeToFlowNode(
  node: DiagramNode,
  diagramType?: DiagramType,
): Node {
  return {
    id: node.id,
    type: resolveFlowNodeType(diagramType, node.type),
    position: node.position ?? { x: 0, y: 0 },
    data: {
      ...node,
      label: node.label,
    },
  };
}

export function graphEdgeToFlowEdge(
  edge: DiagramEdge,
  diagramType?: DiagramType,
): Edge {
  return {
    id: edge.id,
    source: edge.from,
    target: edge.to,
    label: edge.label,
    type: resolveFlowEdgeType(diagramType, edge.type),
    data: { ...edge },
  };
}

/** Convert BPMN pools/lanes into @xyflow/react group nodes.
 * Also sets parentId on child nodes for @xyflow/react grouping. */
function createPoolLaneNodes(
  data: GraphData,
): { poolLaneNodes: Node[]; childParentMap: Map<string, string> } {
  const poolLaneNodes: Node[] = [];
  const childParentMap = new Map<string, string>();

  if (!data.pools) return { poolLaneNodes, childParentMap };

  // Build lane lookup from nodes
  const laneIds = new Set<string>();
  for (const pool of data.pools) {
    for (const lane of pool.lanes) {
      laneIds.add(lane.id);
    }
  }

  // Map each node to its lane parent
  for (const n of data.nodes) {
    if (n.lane && laneIds.has(n.lane)) {
      childParentMap.set(n.id, n.lane);
    }
  }

  for (const pool of data.pools) {
    poolLaneNodes.push({
      id: pool.id,
      type: "bpmnPool",
      position: { x: 0, y: 0 },
      data: { label: pool.label, type: "bpmn:pool" },
      style: { width: 100, height: 100 },
    });

    for (const lane of pool.lanes) {
      poolLaneNodes.push({
        id: lane.id,
        type: "bpmnLane",
        position: { x: 0, y: 0 },
        parentId: pool.id,
        data: { label: lane.label, type: "bpmn:lane" },
        style: { width: 100, height: 100 },
      });
    }
  }

  return { poolLaneNodes, childParentMap };
}

/** Convert BPMN groups into @xyflow/react group nodes. */
function createGroupNodes(
  data: GraphData,
): { groupNodes: Node[]; groupChildMap: Map<string, string> } {
  const groupNodes: Node[] = [];
  const groupChildMap = new Map<string, string>();

  if (!data.groups) return { groupNodes, groupChildMap };

  for (const group of data.groups) {
    groupNodes.push({
      id: group.id,
      type: "bpmnGroup",
      position: { x: 0, y: 0 },
      data: { label: group.label, type: "bpmn:group", color: group.color },
      style: { width: 100, height: 100 },
    });
  }

  // Map nodes to their group
  for (const n of data.nodes) {
    if (n.group && data.groups.some((g) => g.id === n.group)) {
      groupChildMap.set(n.id, n.group);
    }
  }

  return { groupNodes, groupChildMap };
}

export function graphToFlow(data: GraphData): { nodes: Node[]; edges: Edge[] } {
  const diagramType = data.meta?.diagramType;

  if (diagramType === "bpmn") {
    const containerNodes: Node[] = [];
    const childParentMap = new Map<string, string>();

    // Create pool/lane group nodes if present
    if (data.pools?.length) {
      const { poolLaneNodes, childParentMap: plMap } =
        createPoolLaneNodes(data);
      containerNodes.push(...poolLaneNodes);
      for (const [k, v] of plMap) childParentMap.set(k, v);
    }

    // Create group nodes if present
    if (data.groups?.length) {
      const { groupNodes, groupChildMap } = createGroupNodes(data);
      containerNodes.push(...groupNodes);
      for (const [k, v] of groupChildMap) {
        // Lane parentId takes precedence over group parentId
        if (!childParentMap.has(k)) {
          childParentMap.set(k, v);
        }
      }
    }

    const contentNodes = (data.nodes ?? []).map((node) => {
      const flowNode = graphNodeToFlowNode(node, diagramType);
      const parentId = childParentMap.get(node.id);
      if (parentId) {
        flowNode.parentId = parentId;
      }
      return flowNode;
    });

    return {
      nodes: [...containerNodes, ...contentNodes],
      edges: (data.edges ?? []).map((e) => graphEdgeToFlowEdge(e, diagramType)),
    };
  }

  return {
    nodes: (data.nodes ?? []).map((n) => graphNodeToFlowNode(n, diagramType)),
    edges: (data.edges ?? []).map((e) => graphEdgeToFlowEdge(e, diagramType)),
  };
}

// ── Flow → Graph Conversion ────────────────────────────────────────────────

export function flowNodeToGraphNode(node: Node): DiagramNode {
  const data = node.data as unknown as DiagramNode & { label: string };
  return {
    id: node.id,
    type: data.type ?? "flow:process",
    label: data.label,
    position: node.position,
    ...(data.tag !== undefined && { tag: data.tag }),
    ...(data.icon !== undefined && { icon: data.icon }),
    ...(data.color !== undefined && { color: data.color }),
    ...(data.w !== undefined && { w: data.w }),
    ...(data.lane !== undefined && { lane: data.lane }),
    ...(data.group !== undefined && { group: data.group }),
    ...(data.columns !== undefined && { columns: data.columns }),
    ...(data.lifeline !== undefined && { lifeline: data.lifeline }),
    ...(data.parentId !== undefined && { parentId: data.parentId }),
  };
}

export function flowEdgeToGraphEdge(edge: Edge): DiagramEdge {
  const data = edge.data as unknown as DiagramEdge | undefined;
  return {
    id: edge.id,
    from: edge.source,
    to: edge.target,
    label: typeof edge.label === "string" ? edge.label : undefined,
    type: data?.type,
    color: data?.color,
    cardinality: data?.cardinality,
  };
}

export function flowToGraph(
  nodes: Node[],
  edges: Edge[],
  meta?: GraphData["meta"],
): GraphData {
  const containerTypes = new Set([
    "bpmnPool",
    "bpmnLane",
    "bpmnGroup",
  ]);

  // Reconstruct pools from pool/lane group nodes
  const poolNodes = nodes.filter((n) => n.type === "bpmnPool");
  const laneNodes = nodes.filter((n) => n.type === "bpmnLane");
  const groupNodes = nodes.filter((n) => n.type === "bpmnGroup");

  const pools =
    poolNodes.length > 0
      ? poolNodes.map((pool) => ({
          id: pool.id,
          label: String(
            (pool.data as Record<string, unknown>).label ?? "",
          ),
          lanes: laneNodes
            .filter((lane) => lane.parentId === pool.id)
            .map((lane) => ({
              id: lane.id,
              label: String(
                (lane.data as Record<string, unknown>).label ?? "",
              ),
            })),
        }))
      : undefined;

  const groups =
    groupNodes.length > 0
      ? groupNodes.map((g) => {
          const d = g.data as Record<string, unknown>;
          return {
            id: g.id,
            label: String(d.label ?? ""),
            ...(d.color ? { color: String(d.color) } : {}),
          };
        })
      : undefined;

  return {
    meta,
    nodes: nodes
      .filter((n) => !containerTypes.has(n.type ?? ""))
      .map(flowNodeToGraphNode),
    edges: edges.map(flowEdgeToGraphEdge),
    ...(pools && { pools }),
    ...(groups && { groups }),
  };
}
