"use client";

import type { RedirectGraph, RedirectGraphEdge, RedirectGraphNode } from "@domainops/shared-types";
import { useMemo } from "react";

const NODE_WIDTH = 180;
const NODE_HEIGHT = 48;
const X_GAP = 240;
const Y_GAP = 80;
const PADDING = 40;

const STATUS_FILL: Record<string, string> = {
  live: "#237804",
  provisioning: "#ad6800",
  parked: "#434343",
  redirected: "#0958d9",
  deploying: "#d4380d",
};

type EdgeKind = "normal" | "chain" | "loop" | "flagged" | "flip";

interface LaidOutNode extends RedirectGraphNode {
  x: number;
  y: number;
}

function layout(nodes: RedirectGraphNode[], edges: RedirectGraphEdge[], loops: number[][]) {
  const edgesBySource = new Map(edges.map((e) => [e.source_domain_id, e]));
  const hasIncoming = new Set(edges.filter((e) => e.target_domain_id !== null).map((e) => e.target_domain_id as number));
  const loopSet = new Set(loops.flat());

  const participantIds = new Set<number>();
  edges.forEach((e) => {
    participantIds.add(e.source_domain_id);
    if (e.target_domain_id) participantIds.add(e.target_domain_id);
  });

  const columns = new Map<number, number>();
  const roots = [...participantIds].filter((id) => edgesBySource.has(id) && !hasIncoming.has(id) && !loopSet.has(id));

  const visited = new Set<number>();
  function assign(id: number, col: number) {
    if (visited.has(id)) return;
    visited.add(id);
    columns.set(id, Math.max(columns.get(id) ?? 0, col));
    const edge = edgesBySource.get(id);
    if (edge?.target_domain_id) assign(edge.target_domain_id, col + 1);
  }
  roots.forEach((id) => assign(id, 0));
  participantIds.forEach((id) => {
    if (!columns.has(id)) columns.set(id, 0);
  });

  const columnGroups = new Map<number, number[]>();
  participantIds.forEach((id) => {
    const c = columns.get(id) as number;
    if (!columnGroups.has(c)) columnGroups.set(c, []);
    columnGroups.get(c)?.push(id);
  });

  const byId = new Map(nodes.map((n) => [n.id, n]));
  const laidOut: LaidOutNode[] = [];
  let maxCol = 0;
  let maxRow = 0;

  [...columnGroups.entries()]
    .sort((a, b) => a[0] - b[0])
    .forEach(([col, ids]) => {
      maxCol = Math.max(maxCol, col);
      ids.forEach((id, row) => {
        maxRow = Math.max(maxRow, row);
        const node = byId.get(id);
        if (!node) return;
        laidOut.push({ ...node, x: col * X_GAP + PADDING, y: row * Y_GAP + PADDING });
      });
    });

  return {
    nodes: laidOut,
    width: (maxCol + 1) * X_GAP + NODE_WIDTH + PADDING,
    height: (maxRow + 1) * Y_GAP + PADDING,
  };
}

function classifyEdges(
  edges: RedirectGraphEdge[],
  chains: number[][],
  loops: number[][],
  flagged: { source_domain_id: number; target_domain_id: number }[],
  flips: { source_domain_id: number; target_domain_id: number }[],
): Map<number, EdgeKind> {
  const kinds = new Map<number, EdgeKind>();
  const pairKind = new Map<string, EdgeKind>();

  // Priority: loop > chain > flip > flagged. A chain/loop edge keeps its
  // structural color even when it's also flagged or flipped - those still
  // show up in the Problems panel below, they just don't steal the color
  // that says "this edge is part of a chain/loop with a one-click fix".
  const markPairs = (paths: number[][], kind: EdgeKind) => {
    paths.forEach((path) => {
      for (let i = 0; i < path.length; i++) {
        const from = path[i];
        const to = path[(i + 1) % path.length];
        if (kind === "chain" && i === path.length - 1) continue;
        pairKind.set(`${from}:${to}`, kind);
      }
    });
  };
  const markPairsIfUnset = (items: { source_domain_id: number; target_domain_id: number }[], kind: EdgeKind) => {
    items.forEach((f) => {
      const key = `${f.source_domain_id}:${f.target_domain_id}`;
      if (!pairKind.has(key)) pairKind.set(key, kind);
    });
  };
  markPairs(chains, "chain");
  markPairs(loops, "loop");
  markPairsIfUnset(flips, "flip");
  markPairsIfUnset(flagged, "flagged");

  edges.forEach((e) => {
    if (e.target_domain_id === null) return;
    const key = `${e.source_domain_id}:${e.target_domain_id}`;
    kinds.set(e.redirect_id, pairKind.get(key) ?? "normal");
  });

  return kinds;
}

const EDGE_COLOR: Record<EdgeKind, string> = {
  normal: "#8c8c8c",
  chain: "#fa8c16",
  loop: "#f5222d",
  flagged: "#d4b106",
  flip: "#722ed1",
};

export function RedirectMapSvg({
  graph,
  onSelectNode,
}: {
  graph: RedirectGraph;
  onSelectNode: (nodeId: number) => void;
}) {
  const { nodes, width, height } = useMemo(
    () => layout(graph.nodes, graph.edges, graph.loops),
    [graph.nodes, graph.edges, graph.loops],
  );
  const edgeKinds = useMemo(
    () => classifyEdges(graph.edges, graph.chains, graph.loops, graph.flagged_targets, graph.direction_flips),
    [graph.edges, graph.chains, graph.loops, graph.flagged_targets, graph.direction_flips],
  );
  const positionById = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);

  if (nodes.length === 0) {
    return (
      <div className="redirect-map__empty text-sm text-gray-500 py-10 text-center">
        No redirects to map yet - create one from a domain&apos;s detail page.
      </div>
    );
  }

  return (
    <div className="redirect-map__scroll" style={{ overflow: "auto", maxHeight: 560 }}>
      <svg width={width} height={height} className="redirect-map__svg">
        <defs>
          {Object.entries(EDGE_COLOR).map(([kind, color]) => (
            <marker
              key={kind}
              id={`arrow-${kind}`}
              viewBox="0 0 10 10"
              refX="9"
              refY="5"
              markerWidth="7"
              markerHeight="7"
              orient="auto-start-reverse"
            >
              <path d="M0,0 L10,5 L0,10 z" fill={color} />
            </marker>
          ))}
        </defs>

        {graph.edges.map((edge) => {
          if (edge.target_domain_id === null) return null;
          const source = positionById.get(edge.source_domain_id);
          const target = positionById.get(edge.target_domain_id);
          if (!source || !target) return null;

          const kind = edgeKinds.get(edge.redirect_id) ?? "normal";
          const color = EDGE_COLOR[kind];
          const x1 = source.x + NODE_WIDTH;
          const y1 = source.y + NODE_HEIGHT / 2;
          const x2 = target.x;
          const y2 = target.y + NODE_HEIGHT / 2;
          const isBackwards = x2 < x1;

          const path = isBackwards
            ? `M${x1},${y1} C${x1 + 60},${y1 - 40} ${x2 - 60},${y2 - 40} ${x2},${y2}`
            : `M${x1},${y1} L${x2},${y2}`;

          return (
            <path
              key={edge.redirect_id}
              d={path}
              fill="none"
              stroke={color}
              strokeWidth={kind === "normal" ? 1.5 : 2.5}
              strokeDasharray={kind === "flagged" ? "4 3" : undefined}
              markerEnd={`url(#arrow-${kind})`}
              className={`redirect-map__edge redirect-map__edge--${kind}`}
            />
          );
        })}

        {nodes.map((node) => (
          <g
            key={node.id}
            transform={`translate(${node.x}, ${node.y})`}
            onClick={() => onSelectNode(node.id)}
            style={{ cursor: "pointer" }}
            className="redirect-map__node"
          >
            <rect
              width={NODE_WIDTH}
              height={NODE_HEIGHT}
              rx={6}
              fill="#1f1f1f"
              stroke={STATUS_FILL[node.status] ?? "#8c8c8c"}
              strokeWidth={2}
            />
            <text x={10} y={20} fontSize={12} fill="#e6e6e6" fontFamily="monospace">
              {node.fqdn.length > 22 ? `${node.fqdn.slice(0, 21)}…` : node.fqdn}
            </text>
            <text x={10} y={36} fontSize={10} fill={STATUS_FILL[node.status] ?? "#8c8c8c"}>
              {node.status}
              {node.robots_state === "noindex" ? " · noindex" : ""}
            </text>
          </g>
        ))}
      </svg>
    </div>
  );
}
