import type { Node, Edge } from "@xyflow/react";
import type { ElkNode, ElkExtendedEdge } from "elkjs";

import type { DiagramNode } from "../types/graph";
import type { ElkWorkerResponse } from "./elk-worker";

// ── Layout Options ──────────────────────────────────────────────────────────

export type LayoutDirection = "DOWN" | "RIGHT" | "LEFT" | "UP";
export type EdgeRouting = "ORTHOGONAL" | "SPLINES" | "POLYLINE";

export interface ElkLayoutOptions {
  direction: LayoutDirection;
  edgeRouting: EdgeRouting;
  nodeSpacing?: number;
  layerSpacing?: number;
}

const DEFAULT_OPTIONS: ElkLayoutOptions = {
  direction: "DOWN",
  edgeRouting: "ORTHOGONAL",
  nodeSpacing: 80,
  layerSpacing: 100,
};

const DEFAULT_NODE_WIDTH = 150;
const DEFAULT_NODE_HEIGHT = 50;
export const SOFT_CAP_NODE_COUNT = 200;

// ── ELK Graph Building ─────────────────────────────────────────────────────

export function buildElkGraph(
  nodes: Node[],
  edges: Edge[],
  options: Partial<ElkLayoutOptions> = {},
): ElkNode {
  const opts = { ...DEFAULT_OPTIONS, ...options };

  return {
    id: "root",
    layoutOptions: {
      "elk.algorithm": "layered",
      "elk.direction": opts.direction,
      "elk.edgeRouting": opts.edgeRouting,
      "elk.spacing.nodeNode": String(opts.nodeSpacing ?? 80),
      "elk.layered.spacing.nodeNodeBetweenLayers": String(
        opts.layerSpacing ?? 100,
      ),
      "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
      "elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
    },
    children: nodes.map((node) => {
      const data = node.data as unknown as DiagramNode;
      return {
        id: node.id,
        width: data.w ?? node.measured?.width ?? DEFAULT_NODE_WIDTH,
        height: node.measured?.height ?? DEFAULT_NODE_HEIGHT,
      };
    }),
    edges: edges.map(
      (edge) =>
        ({
          id: edge.id,
          sources: [edge.source],
          targets: [edge.target],
        }) as ElkExtendedEdge,
    ),
  };
}

// ── Position Resolution ─────────────────────────────────────────────────────

export function resolvePositions(
  elkGraph: ElkNode,
  originalNodes: Node[],
): Node[] {
  const positionMap = new Map<string, { x: number; y: number }>();
  for (const child of elkGraph.children ?? []) {
    if (child.x !== undefined && child.y !== undefined) {
      positionMap.set(child.id, { x: child.x, y: child.y });
    }
  }

  return originalNodes.map((node) => {
    const data = node.data as unknown as DiagramNode;
    // Skip nodes explicitly marked as manually positioned (set by user drag in Story 2.9)
    if (data.manuallyPositioned) return node;

    const elkPos = positionMap.get(node.id);
    if (!elkPos) return node;

    return {
      ...node,
      position: elkPos,
    };
  });
}

// ── Web Worker Management ───────────────────────────────────────────────────

let worker: Worker | null = null;
let pendingReject: ((reason: Error) => void) | null = null;

const LAYOUT_TIMEOUT_MS = 10_000;

function getWorker(): Worker {
  if (!worker) {
    worker = new Worker(new URL("./elk-worker.ts", import.meta.url), {
      type: "module",
    });
  }
  return worker;
}

export function terminateWorker(): void {
  if (pendingReject) {
    pendingReject(new Error("Layout cancelled"));
    pendingReject = null;
  }
  if (worker) {
    worker.terminate();
    worker = null;
  }
}

// ── Main Layout Function ────────────────────────────────────────────────────

export function computeLayout(
  nodes: Node[],
  edges: Edge[],
  options: Partial<ElkLayoutOptions> = {},
): Promise<Node[]> {
  return new Promise((resolve, reject) => {
    if (nodes.length === 0) {
      resolve(nodes);
      return;
    }

    // Cancel any in-flight layout (single-flight pattern)
    if (pendingReject) {
      pendingReject(new Error("Layout superseded"));
      pendingReject = null;
    }

    const elkGraph = buildElkGraph(nodes, edges, options);
    const w = getWorker();

    let settled = false;
    const timeoutId = setTimeout(() => {
      if (!settled) {
        settled = true;
        pendingReject = null;
        w.removeEventListener("message", handler);
        reject(new Error("ELK layout timed out"));
      }
    }, LAYOUT_TIMEOUT_MS);

    const handler = (event: MessageEvent<ElkWorkerResponse>) => {
      if (settled) return;
      settled = true;
      clearTimeout(timeoutId);
      pendingReject = null;
      w.removeEventListener("message", handler);

      if (event.data.type === "result" && event.data.graph) {
        resolve(resolvePositions(event.data.graph, nodes));
      } else {
        reject(new Error(event.data.message ?? "ELK layout failed"));
      }
    };

    pendingReject = reject;
    w.addEventListener("message", handler);
    w.postMessage({ type: "layout", graph: elkGraph });
  });
}
