"use client";

import { memo, useEffect, useState } from "react";
import { Handle, Position, useUpdateNodeInternals, type NodeProps } from "@xyflow/react";
import { COMPONENT_BY_TYPE, portSide, portRole, portLabel, nodeSize, genericMeta } from "@/lib/componentMeta";
import { mediaColor, mediaForPort } from "@/lib/mediaStyle";
import { effectiveSide, type Side } from "@/lib/orientation";
import type { EntropykNodeData } from "@/store/diagramStore";
import { hxFamily } from "@/lib/hxFamily";
import { ComponentIcon } from "./ComponentIcon";
import { Package } from "lucide-react";
import { fetchModule } from "@/lib/api";

const SIDE_POSITION: Record<Side, Position> = {
  left: Position.Left,
  right: Position.Right,
  top: Position.Top,
  bottom: Position.Bottom,
};

function EntropykNode({ data, id, selected }: NodeProps) {
  const d = data as unknown as EntropykNodeData;
  const nodeType = String(d?.kind || d?.type || "");
  const isModule = nodeType === "ModuleInstance" || !!d?.moduleName;
  const rotation = d?.rotation ?? 0;
  const flipH = !!d?.flipH;
  const flipV = !!d?.flipV;

  // React Flow caches Handle positions on first render; flipping/rotating a
  // node changes the effective side of each port, but the renderer keeps
  // routing edges from the stale position until React Flow is told to
  // re-measure. `useUpdateNodeInternals` is the documented helper for this:
  // it invalidates the cached Handle positions of the node and re-routes
  // every connected edge from the new Handle sides.
  const updateNodeInternals = useUpdateNodeInternals();
  useEffect(() => {
    updateNodeInternals(id);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id, rotation, flipH, flipV]);

  if (isModule) {
    return <ModuleNodeFace data={d} selected={selected} />;
  }

  const meta = COMPONENT_BY_TYPE[nodeType] ?? genericMeta(nodeType);
  const { w: BOX_W, h: BOX_H } = nodeSize(nodeType);
  const isController = nodeType === "SaturatedController";
  const secondaryFluid =
    d?.params?.secondary_fluid != null ? String(d.params.secondary_fluid) : null;
  const family = hxFamily(nodeType, { secondaryFluid });

  const accent = family?.accent ?? meta.color;

  const placement: Record<string, { side: Side; offset: number }> = {};
  for (const port of meta.ports) {
    const side = effectiveSide(portSide(port) as Side, rotation, flipH, flipV);
    const peers = meta.ports.filter(
      (p) => effectiveSide(portSide(p) as Side, rotation, flipH, flipV) === side,
    );
    const idx = peers.indexOf(port);
    placement[port] = {
      side,
      offset: peers.length === 1 ? 0.5 : (idx + 1) / (peers.length + 1),
    };
  }

  const iconTransform = [
    rotation ? `rotate(${rotation}deg)` : "",
    flipH ? "scaleX(-1)" : "",
    flipV ? "scaleY(-1)" : "",
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <div className="flex flex-col items-center" style={{ width: BOX_W }}>
      {/* Symbol IS the node — selection is a CAD marquee, not a card */}
      <div
        className="ek-node relative"
        style={{
          width: BOX_W,
          height: BOX_H,
          outline: selected ? "1.5px dashed var(--accent)" : "1.5px dashed transparent",
          outlineOffset: 4,
        }}
      >
        {isController ? (
          <div className="h-full w-full overflow-hidden rounded-[3px] border border-[var(--line-strong)] bg-white">
            <ControlNodeFace data={d} />
          </div>
        ) : (
          <div
            className="h-full w-full"
            style={{ transform: iconTransform || undefined }}
          >
            <ComponentIcon type={nodeType} color={accent} secondaryFluid={secondaryFluid} />
          </div>
        )}

        {meta.ports.map((port) => {
          const { side, offset } = placement[port];
          const horizontal = side === "left" || side === "right";
          const kind = mediaForPort({ data: d }, port);
          const c = mediaColor(kind);
          const style: React.CSSProperties = {
            ...(horizontal ? { top: `${offset * 100}%` } : { left: `${offset * 100}%` }),
            borderColor: c,
            background: c,
          };
          return (
            <Handle
              key={`${port}::${side}`}
              type={portRole(port)}
              position={SIDE_POSITION[side]}
              id={port}
              style={style}
              isConnectable
              title={`${port} · ${kind}`}
              className="media-handle"
            />
          );
        })}

        {/* Port tags sit INSIDE the symbol bounds — never collide with names */}
        {meta.ports.length > 2 &&
          meta.ports.map((port) => {
            const { side, offset } = placement[port];
            const horizontal = side === "left" || side === "right";
            const style: React.CSSProperties = horizontal
              ? { top: `${offset * 100}%`, transform: "translateY(-50%)", [side]: 8 }
              : { left: `${offset * 100}%`, transform: "translateX(-50%)", [side]: 7 };
            return (
              <span
                key={`t-${port}`}
                className="mono pointer-events-none absolute text-[6.5px] uppercase tracking-wide text-[var(--ink-faint)]"
                style={style}
              >
                {portLabel(port)}
              </span>
            );
          })}
      </div>

      <div className="mt-1.5 flex max-w-[200px] flex-col items-center leading-tight">
        {!isController && (
          <span className="mono truncate text-[10.5px] text-[var(--ink)]">{d.name}</span>
        )}
        <span className="truncate text-[8.5px] text-[var(--ink-faint)]">
          {isController ? "override control" : meta.label} · C{d.circuit}
        </span>
      </div>
    </div>
  );
}

function ControlNodeFace({ data }: { data: EntropykNodeData }) {
  const p = data.params;
  const measure = `${String(p.measure_component ?? "—")}.${shortOutput(String(p.measure_output ?? "—"))}`;
  const actuator = `${String(p.actuator_component ?? "—")}.${String(p.actuator_factor ?? "—")}`;
  const target = formatControlNumber(p.target);
  const bounds = `${formatControlNumber(p.min)}…${formatControlNumber(p.max)}`;
  const objectiveCount = controlObjectiveCount(p.objectives_json);

  return (
    <div className="flex h-full w-full flex-col overflow-hidden text-left">
      <div className="flex items-center justify-between bg-[#4c1d95] px-2 py-1 text-white">
        <span className="mono max-w-[122px] truncate text-[10px] font-semibold">{data.name}</span>
        <span className="rounded bg-white/20 px-1 py-0.5 text-[7px] font-bold uppercase tracking-wide">
          SS
        </span>
      </div>
      <div className="grid flex-1 grid-cols-[1fr_44px_1fr] items-stretch bg-[#f5f3ff]">
        <div className="flex min-w-0 flex-col justify-center px-2">
          <span className="text-[7px] font-semibold uppercase tracking-[0.08em] text-[#6b5b8c]">
            Controlled
          </span>
          <span className="mono truncate text-[9px] font-semibold text-[#2e1f4a]">{measure}</span>
          <span className="mono text-[8px] text-[#6b5b8c]">SP {target}</span>
        </div>
        <div className="flex flex-col items-center justify-center border-x border-[#ddd6fe] bg-[#ede9fe]">
          <span className="text-[7px] font-bold uppercase text-[#5b21b6]">Select</span>
          <span className="mono mt-0.5 grid h-5 min-w-5 place-items-center rounded-full bg-[#5b21b6] px-1 text-[9px] font-bold text-white">
            {objectiveCount}
          </span>
        </div>
        <div className="flex min-w-0 flex-col justify-center px-2">
          <span className="text-[7px] font-semibold uppercase tracking-[0.08em] text-[#6b5b8c]">
            Actuator
          </span>
          <span className="mono truncate text-[9px] font-semibold text-[#2e1f4a]">{actuator}</span>
          <span className="mono text-[8px] text-[#6b5b8c]">{bounds}</span>
        </div>
      </div>
    </div>
  );
}

function controlObjectiveCount(value: number | string | boolean | undefined): number {
  if (typeof value !== "string") return 0;
  try {
    const parsed: unknown = JSON.parse(value);
    return Array.isArray(parsed) ? parsed.length : 0;
  } catch {
    return 0;
  }
}

function shortOutput(output: string): string {
  const labels: Record<string, string> = {
    saturationTemperature: "Tsat",
    heatTransferRate: "Qdot",
    massFlowRate: "mdot",
    temperature: "T",
    pressure: "P",
    capacity: "Q",
    superheat: "SH",
    subcooling: "SC",
  };
  return labels[output] ?? output;
}

function formatControlNumber(value: number | string | boolean | undefined): string {
  return typeof value === "number" && Number.isFinite(value) ? Number(value.toPrecision(4)).toString() : "—";
}

export default memo(EntropykNode, (prev, next) => {
  const a = prev.data as unknown as EntropykNodeData;
  const b = next.data as unknown as EntropykNodeData;
  return (
    prev.selected === next.selected &&
    a.type === b.type &&
    a.name === b.name &&
    a.circuit === b.circuit &&
    (a.rotation ?? 0) === (b.rotation ?? 0) &&
    !!a.flipH === !!b.flipH &&
    !!a.flipV === !!b.flipV &&
    a.params === b.params
  );
});

function ModuleNodeFace({
  data,
  selected,
}: {
  data: EntropykNodeData;
  selected?: boolean;
}) {
  const moduleName = String(data.params.module_name ?? "?");
  const updateNodeInternals = useUpdateNodeInternals();

  const [ports, setPorts] = useState<string[]>(() => {
    if (typeof data.params.module_ports === "string") {
      try {
        const parsed = JSON.parse(data.params.module_ports);
        if (Array.isArray(parsed)) return parsed;
      } catch {}
    }
    if (Array.isArray(data.params.module_ports)) return data.params.module_ports as string[];
    if (Array.isArray(data.module_ports)) return data.module_ports as string[];
    return [];
  });

  useEffect(() => {
    if (moduleName !== "?") {
      fetchModule(moduleName).then((modData) => {
        if (modData && typeof modData === "object") {
          if ("ports" in modData) {
            const pMap = (modData as { ports?: Record<string, string> }).ports;
            if (pMap && typeof pMap === "object") {
              setPorts(Object.keys(pMap));
            }
          }
        }
      });
    }
  }, [moduleName]);

  useEffect(() => {
    updateNodeInternals(data.name);
  }, [data.name, ports.length, updateNodeInternals]);

  const leftPorts: string[] = [];
  const rightPorts: string[] = [];

  ports.forEach((p) => {
    const isOutlet = /out|discharge|outlet/i.test(p);
    const isInlet = /in|suction|inlet/i.test(p);
    if (isOutlet && !isInlet) {
      rightPorts.push(p);
    } else if (isInlet && !isOutlet) {
      leftPorts.push(p);
    } else {
      if (leftPorts.length <= rightPorts.length) leftPorts.push(p);
      else rightPorts.push(p);
    }
  });

  const maxPortCount = Math.max(leftPorts.length, rightPorts.length, 1);
  const BOX_W = 140;
  const BOX_H = Math.max(100, maxPortCount * 28 + 40);

  return (
    <div className="flex flex-col items-center" style={{ width: BOX_W }}>
      <div
        className="ek-node relative flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-[var(--accent)] bg-white shadow-sm transition-all"
        style={{
          width: BOX_W,
          height: BOX_H,
          outline: selected ? "2px dashed var(--accent)" : "1.5px dashed transparent",
          outlineOffset: 4,
        }}
        title={`Double-clic pour éditer la classe ${moduleName}`}
      >
        <Package size={32} className="text-[var(--accent)]" />
        <span className="mono mt-1 max-w-[110px] truncate text-[10px] font-semibold text-[var(--ink)]">
          {moduleName}
        </span>

        {leftPorts.map((port, idx) => {
          const offset = (idx + 1) / (leftPorts.length + 1);
          return (
            <div key={`left-${port}`}>
              <Handle
                type="target"
                position={Position.Left}
                id={port}
                style={{
                  top: `${offset * 100}%`,
                  borderColor: "var(--accent)",
                  background: "var(--accent)",
                }}
                className="media-handle"
                isConnectable
                title={port}
              />
              <span
                className="mono pointer-events-none absolute left-2 text-[8px] text-[var(--ink-dim)]"
                style={{ top: `${offset * 100}%`, transform: "translateY(-50%)" }}
              >
                {port}
              </span>
            </div>
          );
        })}

        {rightPorts.map((port, idx) => {
          const offset = (idx + 1) / (rightPorts.length + 1);
          return (
            <div key={`right-${port}`}>
              <Handle
                type="source"
                position={Position.Right}
                id={port}
                style={{
                  top: `${offset * 100}%`,
                  borderColor: "var(--accent)",
                  background: "var(--accent)",
                }}
                className="media-handle"
                isConnectable
                title={port}
              />
              <span
                className="mono pointer-events-none absolute right-2 text-[8px] text-[var(--ink-dim)]"
                style={{ top: `${offset * 100}%`, transform: "translateY(-50%)" }}
              >
                {port}
              </span>
            </div>
          );
        })}
      </div>

      <div className="mt-1.5 flex max-w-[160px] flex-col items-center leading-tight">
        <span className="mono truncate text-[11px] font-bold text-[var(--ink)]">
          {data.name}
        </span>
        <span className="truncate text-[8.5px] font-medium text-[var(--ink-faint)]">
          Module ({moduleName}) · C{data.circuit}
        </span>
      </div>
    </div>
  );
}
