"use client";

import { useEffect, useMemo, useState } from "react";
import {
  Activity,
  AlertTriangle,
  CheckCircle2,
  ClipboardList,
  Eye,
  EyeOff,
  Gauge,
  GitBranch,
  TerminalSquare,
  Zap,
} from "lucide-react";
import {
  CartesianGrid,
  Line,
  LineChart,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import {
  fetchMollier,
  type IterationInfo,
  type MollierResponse,
  type SimulationResult,
} from "@/lib/api";
import { useDiagramStore } from "@/store/diagramStore";
import type { EntropykNodeData } from "@/lib/configBuilder";
import type { Node } from "@xyflow/react";

interface Props {
  result: SimulationResult | null;
  error: string | null;
  config: unknown | null;
  simulating?: boolean;
  /** Validation issues surfaced from the shared simulate hook. */
  issues?: string[];
}

type Tab = "overview" | "edges" | "components" | "solver" | "debug";
type StoreSnapshot = ReturnType<typeof useDiagramStore.getState>;
type DiagramNode = StoreSnapshot["nodes"][number];
type DiagramEdge = StoreSnapshot["edges"][number];
type StateEntry = NonNullable<SimulationResult["state"]>[number];
type ComponentRow = NonNullable<SimulationResult["components"]>[number];
type PowerUnit = "W" | "kW";

const SHOW_SOLVER_KEY = "entropyk.showSolverLog";

const FLUIDS = ["R410A", "R134a", "R290", "R744", "R32", "R1234yf", "Water", "Air"];
const BACKENDS = ["CoolProp", "Test"];
const SOLVERS = [
  { value: "newton", label: "Newton" },
  { value: "picard", label: "Picard" },
];

function readShowSolverPref(): boolean {
  if (typeof window === "undefined") return true;
  const v = window.localStorage.getItem(SHOW_SOLVER_KEY);
  if (v === null) return true;
  return v !== "0" && v !== "false";
}

export default function ResultsPanel({
  result,
  error,
  config,
  simulating = false,
  issues = [],
}: Props) {
  const [tab, setTab] = useState<Tab>("overview");
  const [powerUnit, setPowerUnit] = useState<PowerUnit>("kW");
  const [showSolverLog, setShowSolverLog] = useState(true);
  const [mollier, setMollier] = useState<MollierResponse | null>(null);

  const nodes = useDiagramStore((s) => s.nodes);
  const edges = useDiagramStore((s) => s.edges);
  const fluid = useDiagramStore((s) => s.fluid);
  const fluidBackend = useDiagramStore((s) => s.fluidBackend);
  const solverStrategy = useDiagramStore((s) => s.solverStrategy);
  const setFluid = useDiagramStore((s) => s.setFluid);
  const setFluidBackend = useDiagramStore((s) => s.setFluidBackend);
  const setSolverStrategy = useDiagramStore((s) => s.setSolverStrategy);
  const simulatingStore = useDiagramStore((s) => s.simulating);
  const isSimulating = simulating || simulatingStore;

  const cycleData = useMemo(() => buildCycleData(result, nodes, edges), [result, nodes, edges]);
  const resultFluid = scenarioFluid(config);
  const edgeRows = result?.state ?? [];
  const localHxRows = useMemo(
    () => buildLocalHeatExchangerRows(nodes, edges, result?.state),
    [nodes, edges, result?.state],
  );
  const componentRows = useMemo(() => {
    const backendRows = result?.components ?? [];
    const backendNames = new Set(backendRows.map((row) => row.name));
    return [...backendRows, ...localHxRows.filter((row) => !backendNames.has(row.name))];
  }, [result?.components, localHxRows]);
  const localHeatExchangeW = localHxRows.reduce(
    (sum, row) => sum + Math.abs(row.energy?.heat_transfer_w ?? 0),
    0,
  );
  const converged = isConverged(result);
  const perf = result?.performance ?? {};
  const coolingW = kwToW(perf.q_cooling_kw) ?? perf.cooling_capacity_w ?? null;
  const heatingW = kwToW(perf.q_heating_kw) ?? perf.heating_capacity_w ?? null;
  const shaftPowerW = totalPower(perf);
  const hasExternalDuty = Math.abs(coolingW ?? 0) > 1e-9 || Math.abs(heatingW ?? 0) > 1e-9;
  const cop = hasExternalDuty ? perf.cop ?? perf.cop_cooling ?? perf.cop_heating : null;
  const residual = result?.convergence?.final_residual ?? null;
  const iterations = result?.convergence?.iterations ?? result?.iterations ?? null;
  const iterationHistory = result?.convergence?.iteration_history ?? [];
  const resultSolverStrategy = result?.convergence?.strategy ?? null;
  const resultStatus = String(result?.status ?? "").toLowerCase();
  const resultError =
    result && !converged && ["error", "failed", "diverged"].includes(resultStatus)
      ? result.error || statusLabel(result.status)
      : null;
  const displayError = error ?? resultError;
  const benchTitle = isSimulating
    ? "Solving..."
    : displayError
      ? "Solver stopped"
      : result
        ? statusLabel(result.status)
        : "Ready to solve";
  const benchDetail = isSimulating
    ? "Simulation running"
    : buildBenchDetail(result, displayError, iterations, residual);

  useEffect(() => {
    setShowSolverLog(readShowSolverPref());
  }, []);

  useEffect(() => {
    if (typeof window === "undefined") return;
    window.localStorage.setItem(SHOW_SOLVER_KEY, showSolverLog ? "1" : "0");
    if (!showSolverLog && tab === "solver") setTab("overview");
  }, [showSolverLog, tab]);

  // After a solve, jump to Solver when iteration history is available and the
  // log is enabled — otherwise users never discover the tab.
  useEffect(() => {
    if (!result || isSimulating) return;
    const history = result.convergence?.iteration_history ?? [];
    if (showSolverLog && history.length > 0) {
      setTab("solver");
    }
  }, [result, isSimulating, showSolverLog]);

  useEffect(() => {
    let active = true;
    // Never hit CoolProp (Mollier) while a solve is in flight — concurrent
    // CoolProp calls used to crash ui-server → bare HTTP 500 + stuck spinner.
    if (
      isSimulating ||
      !result ||
      !fluid ||
      fluid.toLowerCase() === "water" ||
      fluid.toLowerCase() === "air"
    ) {
      if (!result) setMollier(null);
      return;
    }
    fetchMollier(fluid)
      .then((response) => {
        if (active) setMollier(response.ok ? response : null);
      })
      .catch(() => {
        if (active) setMollier(null);
      });
    return () => {
      active = false;
    };
  }, [fluid, result, isSimulating]);

  // Always reserve the Solver tab slot when the eye toggle is on (default).
  const tabCount = showSolverLog ? 5 : 4;

  return (
    <div className="flex min-h-0 flex-1 flex-col">
      <Header
        title="Solve bench"
        action={
          <div className="flex items-center gap-2">
            <div className="flex items-center gap-1.5">
              <label className="flex items-center gap-1 text-[10px] text-[var(--ink-dim)]">
                <span className="mono uppercase tracking-wide">Fluid</span>
                <select
                  value={fluid}
                  onChange={(e) => setFluid(e.target.value)}
                  className="rounded border border-[var(--line)] bg-white px-1.5 py-0.5 text-[10px] text-[var(--ink)] outline-none focus:border-[var(--accent)]"
                >
                  {FLUIDS.map((f) => (
                    <option key={f} value={f}>
                      {f}
                    </option>
                  ))}
                </select>
              </label>
              <label className="flex items-center gap-1 text-[10px] text-[var(--ink-dim)]">
                <span className="mono uppercase tracking-wide">Props</span>
                <select
                  value={fluidBackend}
                  onChange={(e) => setFluidBackend(e.target.value)}
                  className="rounded border border-[var(--line)] bg-white px-1.5 py-0.5 text-[10px] text-[var(--ink)] outline-none focus:border-[var(--accent)]"
                >
                  {BACKENDS.map((b) => (
                    <option key={b} value={b}>
                      {b}
                    </option>
                  ))}
                </select>
              </label>
              <label className="flex items-center gap-1 text-[10px] text-[var(--ink-dim)]">
                <span className="mono uppercase tracking-wide">Solver</span>
                <select
                  value={solverStrategy}
                  onChange={(e) => setSolverStrategy(e.target.value)}
                  className="rounded border border-[var(--line)] bg-white px-1.5 py-0.5 text-[10px] text-[var(--ink)] outline-none focus:border-[var(--accent)]"
                >
                  {SOLVERS.map((s) => (
                    <option key={s.value} value={s.value}>
                      {s.label}
                    </option>
                  ))}
                </select>
              </label>
            </div>
            <button
              type="button"
              className="inline-flex items-center gap-1 rounded-md border border-[var(--line)] bg-white px-2 py-1 text-[10px] text-[var(--ink-dim)] hover:border-[var(--accent)] hover:text-[var(--accent)]"
              onClick={() => setShowSolverLog((v) => !v)}
              title={showSolverLog ? "Hide solver iterations" : "Show solver iterations"}
            >
              {showSolverLog ? <Eye size={12} /> : <EyeOff size={12} />}
              Solver log
            </button>
            <PowerUnitSelect value={powerUnit} onChange={setPowerUnit} />
          </div>
        }
      />

      <div className="border-b border-[var(--line)] bg-[var(--chrome-2)] px-3 py-2">
        <div className="bench-card">
          <div className="flex items-center gap-2">
            {displayError ? (
              <AlertTriangle size={16} className="text-[var(--hot)]" />
            ) : converged ? (
              <CheckCircle2 size={16} className="text-[var(--ok)]" />
            ) : (
              <Activity size={16} className="text-[var(--warn)]" />
            )}
            <div className="min-w-0">
              <div className="text-[13px] font-semibold text-[var(--ink)]">
                {benchTitle}
              </div>
              <div className="mono mt-0.5 text-[10px] text-[var(--ink-faint)]">
                {benchDetail}
                {resultSolverStrategy ? ` · ${resultSolverStrategy}` : ""}
              </div>
            </div>
          </div>
          <ResidualTrace value={residual} ok={converged} />
        </div>
      </div>

      {issues.length > 0 && !displayError && (
        <div className="border-b border-amber-200 bg-amber-50 px-3 py-2 text-[11px] leading-relaxed text-amber-900">
          <div className="mb-0.5 flex items-center gap-1.5 font-semibold">
            <AlertTriangle size={12} />
            {issues.length} validation issue{issues.length > 1 ? "s" : ""} — Solve blocked
          </div>
          <ul className="ml-4 list-disc space-y-0.5">
            {issues.slice(0, 4).map((iss, i) => (
              <li key={i} className="text-[10.5px]">
                {iss}
              </li>
            ))}
            {issues.length > 4 && (
              <li className="text-[10px] text-amber-700">
                + {issues.length - 4} more…
              </li>
            )}
          </ul>
        </div>
      )}

      <div
        className="grid border-b border-[var(--line)] bg-white text-[11px]"
        style={{ gridTemplateColumns: `repeat(${tabCount}, minmax(0, 1fr))` }}
      >
        <TabButton active={tab === "overview"} onClick={() => setTab("overview")} icon={<Gauge size={13} />}>
          Overview
        </TabButton>
        <TabButton active={tab === "edges"} onClick={() => setTab("edges")} icon={<GitBranch size={13} />}>
          Edges
        </TabButton>
        <TabButton active={tab === "components"} onClick={() => setTab("components")} icon={<ClipboardList size={13} />}>
          Parts
        </TabButton>
        {showSolverLog && (
          <TabButton active={tab === "solver"} onClick={() => setTab("solver")} icon={<Activity size={13} />}>
            Solver
          </TabButton>
        )}
        <TabButton active={tab === "debug"} onClick={() => setTab("debug")} icon={<TerminalSquare size={13} />}>
          Debug
        </TabButton>
      </div>

      <div className="min-h-0 flex-1 overflow-y-auto px-3 py-3">
        {displayError && <ErrorCard error={displayError} diagnostics={result?.failure_diagnostics ?? null} />}
        {!displayError && !result && <EmptyState />}

        {result && tab === "overview" && (
          <div className="space-y-3">
            {result.dof && (
              <div
                className={`rounded-md border px-2.5 py-2 text-[11px] ${
                  result.dof.n_equations === result.dof.n_unknowns
                    ? "border-emerald-200 bg-emerald-50 text-emerald-900"
                    : "border-red-200 bg-red-50 text-red-900"
                }`}
              >
                <div className="eyebrow mb-1">Server DoF (exact ledger)</div>
                <div className="mono font-semibold">
                  {result.dof.n_equations} eqs{" "}
                  {result.dof.n_equations === result.dof.n_unknowns ? "=" : "≠"}{" "}
                  {result.dof.n_unknowns} unk · {result.dof.balance}
                </div>
              </div>
            )}
            {iterationHistory.length > 0 && (
              <button
                type="button"
                onClick={() => {
                  setShowSolverLog(true);
                  setTab("solver");
                }}
                className="w-full rounded-md border border-[var(--line)] bg-white px-2.5 py-2 text-left text-[11px] hover:border-[var(--accent)]"
              >
                <div className="eyebrow mb-1">Solver log</div>
                <div className="mono font-semibold text-[var(--ink)]">
                  {iterationHistory.length} iterations · residual{" "}
                  {residual != null ? residual.toExponential(2) : "—"}
                </div>
                <div className="mt-0.5 text-[10px] text-[var(--ink-faint)]">
                  Click to open the full Newton / Picard history
                </div>
              </button>
            )}
            <div className="grid grid-cols-2 gap-2">
              <Metric
                label="HX transfer"
                value={formatPower(localHeatExchangeW || null, powerUnit)}
                unit={powerUnit}
                digits={powerDigits(powerUnit)}
                tone="power"
              />
              <Metric
                label="Shaft power"
                value={formatPower(shaftPowerW, powerUnit)}
                unit={powerUnit}
                digits={powerDigits(powerUnit)}
                tone="power"
              />
              <Metric
                label="External cooling"
                value={formatPower(coolingW, powerUnit)}
                unit={powerUnit}
                digits={powerDigits(powerUnit)}
              />
              <Metric
                label="External heating"
                value={formatPower(heatingW, powerUnit)}
                unit={powerUnit}
                digits={powerDigits(powerUnit)}
              />
              <Metric label="COP" value={cop} digits={3} />
              <Metric label="Iterations" value={iterations} digits={0} />
            </div>

            {localHeatExchangeW > 0 && !hasExternalDuty && (
              <div className="rounded-lg border border-[var(--line)] bg-white px-3 py-2 text-[11px] leading-relaxed text-[var(--ink-dim)]">
                <span className="font-semibold text-[var(--ink)]">Internal exchange:</span>{" "}
                this air-water exchanger transfers heat between two modelled streams, so it is
                shown as HX transfer. External cooling/heating and COP stay undefined unless a
                real useful load or sink is modelled.
              </div>
            )}

            <ThermoStrip state={edgeRows} />

            {cycleData.length >= 2 && (
              <MollierPanel cycle={cycleData} mollier={mollier} fluid={fluid} />
            )}
          </div>
        )}

        {result && tab === "edges" && <EdgesTable rows={edgeRows} />}
        {result && tab === "components" && <ComponentsTable rows={componentRows} powerUnit={powerUnit} />}
        {showSolverLog && (result || displayError) && tab === "solver" && (
          <SolverLogView
            result={result}
            iterations={iterations}
            residual={residual}
            strategy={resultSolverStrategy}
            history={iterationHistory}
            tolerance={result?.convergence?.tolerance ?? null}
          />
        )}
        {(result || config || displayError) && tab === "debug" && (
          <DebugView result={result} error={displayError} config={config} />
        )}
      </div>

      <style jsx>{`
        .bench-card {
          border: 1px solid var(--line);
          background:
            linear-gradient(90deg, rgba(27, 111, 224, 0.08), transparent 42%),
            repeating-linear-gradient(90deg, transparent 0 13px, rgba(54, 71, 90, 0.05) 13px 14px),
            #fff;
          border-radius: 10px;
          padding: 10px;
        }
      `}</style>
    </div>
  );
}

function Header({ title, action }: { title: string; action?: React.ReactNode }) {
  return (
    <>
      <div className="flex h-9 items-center justify-between px-3">
        <span className="eyebrow">{title}</span>
        {action ?? <span className="mono text-[10px] text-[var(--ink-faint)]">live diagnostic</span>}
      </div>
      <div className="h-px w-full bg-[var(--line)]" />
    </>
  );
}

function PowerUnitSelect({
  value,
  onChange,
}: {
  value: PowerUnit;
  onChange: (value: PowerUnit) => void;
}) {
  return (
    <label className="flex items-center gap-1.5">
      <span className="eyebrow">power</span>
      <select
        value={value}
        onChange={(event) => onChange(event.target.value as PowerUnit)}
        className="mono rounded border border-[var(--line)] bg-white px-1.5 py-0.5 text-[10px] text-[var(--ink)]"
      >
        <option value="kW">kW</option>
        <option value="W">W</option>
      </select>
    </label>
  );
}

function TabButton({
  active,
  onClick,
  icon,
  children,
}: {
  active: boolean;
  onClick: () => void;
  icon: React.ReactNode;
  children: React.ReactNode;
}) {
  return (
    <button
      onClick={onClick}
      className="flex items-center justify-center gap-1.5 border-r border-[var(--line)] px-2 py-2 transition-colors last:border-r-0"
      style={{
        color: active ? "var(--accent)" : "var(--ink-dim)",
        background: active ? "#eef5ff" : "#fff",
        fontWeight: active ? 700 : 500,
      }}
    >
      {icon}
      {children}
    </button>
  );
}

function EmptyState() {
  return (
    <div className="rounded-lg border border-dashed border-[var(--line-strong)] bg-white p-4 text-center">
      <Zap className="mx-auto text-[var(--ink-faint)]" size={22} />
      <p className="mt-2 text-[12px] font-semibold text-[var(--ink-dim)]">No solution to inspect</p>
      <p className="mt-1 text-[11px] leading-relaxed text-[var(--ink-faint)]">
        Load the air-water exchanger example, choose Newton or Picard, then solve.
      </p>
    </div>
  );
}

function ErrorCard({
  error,
  diagnostics,
}: {
  error: string;
  diagnostics: SimulationResult["failure_diagnostics"] | null;
}) {
  return (
    <div className="mb-3 rounded-lg border border-red-200 bg-red-50 p-3">
      <div className="flex items-center gap-2 text-[12px] font-semibold text-[var(--hot)]">
        <AlertTriangle size={15} />
        Simulation error
      </div>
      <pre className="mono mt-2 max-h-56 overflow-auto whitespace-pre-wrap text-[11px] leading-relaxed text-[var(--ink-dim)]">
        {error}
      </pre>
      {diagnostics && (
        <div className="mt-2 grid grid-cols-2 gap-2">
          <DiagnosticChip label="Residual" value={formatScientific(diagnostics.final_residual_norm)} />
          <DiagnosticChip label="Dominant row" value={formatDiagnosticIndex(diagnostics.dominant_residual_index)} />
          <DiagnosticChip label="Dominant value" value={formatScientific(diagnostics.dominant_residual_value)} />
          <DiagnosticChip label="Last residual" value={formatScientific(diagnostics.last_residual_norm)} />
        </div>
      )}
    </div>
  );
}

function DiagnosticChip({ label, value }: { label: string; value: string }) {
  return (
    <div className="rounded-md border border-red-100 bg-white/75 px-2 py-1">
      <div className="eyebrow">{label}</div>
      <div className="mono mt-0.5 text-[11px] font-semibold text-[var(--ink)]">{value}</div>
    </div>
  );
}

function Metric({
  label,
  value,
  unit,
  digits = 2,
  tone,
  scientific = false,
}: {
  label: string;
  value: number | string | null | undefined;
  unit?: string;
  digits?: number;
  tone?: "power";
  scientific?: boolean;
}) {
  let display = "—";
  let hasUnit = false;
  if (typeof value === "string" && value.trim()) {
    display = value;
  } else if (typeof value === "number" && Number.isFinite(value)) {
    display = scientific ? value.toExponential(digits) : value.toFixed(digits);
    hasUnit = !!unit;
  }
  return (
    <div className="rounded-lg border border-[var(--line)] bg-white px-3 py-2">
      <div className="eyebrow">{label}</div>
      <div className="mono mt-1 text-[17px] font-semibold" style={{ color: tone === "power" ? "var(--liquid)" : "var(--ink)" }}>
        {display}
        {hasUnit ? <span className="ml-1 text-[10px] font-normal text-[var(--ink-faint)]">{unit}</span> : null}
      </div>
    </div>
  );
}

function Panel({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <section className="overflow-hidden rounded-lg border border-[var(--line)] bg-white">
      <div className="border-b border-[var(--line)] bg-[var(--chrome-2)] px-3 py-2">
        <span className="eyebrow">{title}</span>
      </div>
      <div className="p-2">{children}</div>
    </section>
  );
}

type CyclePoint = { idx: number; h: number; p: number; label: string; edgeLabel?: string };
type MollierLinePoint = { h: number; p: number; t?: number; line: "sat. liquid" | "sat. vapor" };

function MollierPanel({
  cycle,
  mollier,
  fluid,
}: {
  cycle: CyclePoint[];
  mollier: MollierResponse | null;
  fluid: string;
}) {
  const liquidLine = useMemo<MollierLinePoint[]>(
    () =>
      mollier?.saturation.map((point) => ({
        h: point.h_liq_kj_kg,
        p: point.p_bar,
        t: point.t_k,
        line: "sat. liquid",
      })) ?? [],
    [mollier],
  );
  const vaporLine = useMemo<MollierLinePoint[]>(
    () =>
      mollier?.saturation.map((point) => ({
        h: point.h_vap_kj_kg,
        p: point.p_bar,
        t: point.t_k,
        line: "sat. vapor",
      })) ?? [],
    [mollier],
  );
  const domain = mollierDomain(cycle, liquidLine, vaporLine);

  return (
    <Panel title={`Mollier diagram · ${fluid || "refrigerant"} · log(P)-h`}>
      <div className="h-72 w-full">
        <ResponsiveContainer width="100%" height="100%">
          <LineChart margin={{ top: 12, right: 12, bottom: 24, left: 2 }}>
            <CartesianGrid strokeDasharray="2 4" stroke="#e7ecf2" />
            <XAxis
              dataKey="h"
              type="number"
              domain={domain.h}
              allowDataOverflow
              tick={{ fontSize: 9, fill: "#6f7d8d" }}
              label={{
                value: "specific enthalpy h · kJ/kg",
                position: "insideBottom",
                offset: -14,
                fontSize: 9,
                fill: "#6f7d8d",
              }}
            />
            <YAxis
              dataKey="p"
              type="number"
              scale="log"
              domain={domain.p}
              allowDataOverflow
              tickFormatter={(value) => `${Number(value).toPrecision(2)}`}
              tick={{ fontSize: 9, fill: "#6f7d8d" }}
              label={{
                value: "P · bar abs",
                angle: -90,
                position: "insideLeft",
                fontSize: 9,
                fill: "#6f7d8d",
              }}
              width={42}
            />
            <Tooltip
              content={<MollierTooltip />}
              cursor={{ stroke: "#94a3b8", strokeDasharray: "2 3" }}
              wrapperStyle={{ outline: "none", pointerEvents: "none" }}
            />
            {liquidLine.length > 0 && (
              <Line
                data={liquidLine}
                type="monotone"
                dataKey="p"
                name="sat. liquid"
                stroke="#1d8f72"
                strokeWidth={1.8}
                dot={false}
                isAnimationActive={false}
              />
            )}
            {vaporLine.length > 0 && (
              <Line
                data={vaporLine}
                type="monotone"
                dataKey="p"
                name="sat. vapor"
                stroke="#1d8f72"
                strokeWidth={1.8}
                dot={false}
                isAnimationActive={false}
              />
            )}
            <Line
              data={cycle}
              type="linear"
              dataKey="p"
              name="cycle"
              stroke="var(--accent)"
              strokeWidth={2.8}
              dot={<MollierDot />}
              isAnimationActive={false}
            />
          </LineChart>
        </ResponsiveContainer>
      </div>
      <div className="mt-2 flex flex-wrap gap-2 text-[10px] text-[var(--ink-faint)]">
        <span className="mono text-[#1d8f72]">━ saturation dome</span>
        <span className="mono text-[var(--accent)]">━ solved cycle</span>
        <span>Only refrigerant-side states are plotted; caloporteur/air branches are excluded.</span>
      </div>
    </Panel>
  );
}

function MollierTooltip(props: unknown) {
  const tooltip = props as { active?: boolean; payload?: Array<{ name?: string; payload?: Partial<CyclePoint & MollierLinePoint> }> };
  if (!tooltip.active || !tooltip.payload?.length) return null;

  const item =
    tooltip.payload.find((entry) => entry.name === "cycle") ??
    tooltip.payload.find((entry) => entry.payload?.line) ??
    tooltip.payload[0];
  const point = item.payload;
  if (typeof point?.h !== "number" || typeof point?.p !== "number") return null;

  const title =
    item.name === "cycle"
      ? `State ${typeof point.label === "string" ? point.label : ""}`
      : point.line ?? item.name ?? "Mollier";
  return (
    <div className="rounded-md border border-[var(--line)] bg-white/95 px-2 py-1.5 shadow-sm">
      <div className="text-[10px] font-semibold text-[var(--ink)]">{title}</div>
      {point.edgeLabel && <div className="mono max-w-44 truncate text-[9px] text-[var(--ink-faint)]">{point.edgeLabel}</div>}
      <div className="mono mt-1 text-[10px] text-[var(--ink-dim)]">
        h {point.h.toFixed(1)} kJ/kg
        <br />P {point.p.toFixed(3)} bar abs
        {typeof point.t === "number" ? <><br />T {(point.t - 273.15).toFixed(1)} °C</> : null}
      </div>
    </div>
  );
}

function MollierDot(props: unknown) {
  const p = props as { cx?: number; cy?: number; payload?: CyclePoint };
  if (typeof p.cx !== "number" || typeof p.cy !== "number") return null;
  const label = p.payload?.label ?? "";
  return (
    <g>
      <circle cx={p.cx} cy={p.cy} r={3.5} fill="#fff" stroke="var(--accent)" strokeWidth={2} />
      <text x={p.cx + 5} y={p.cy - 5} className="mono fill-[var(--ink-dim)] text-[9px]">
        {label}
      </text>
    </g>
  );
}

function ResidualTrace({ value, ok }: { value: number | null; ok: boolean }) {
  const bars = Array.from({ length: 18 }, (_, i) => i);
  const magnitude = value === null ? 0 : Math.max(0, Math.min(1, -Math.log10(Math.max(value, 1e-12)) / 12));
  return (
    <div className="mt-3 flex h-7 items-end gap-[3px]" aria-label="Residual trace">
      {bars.map((bar) => {
        const height = 6 + ((bar * 11) % 19);
        const lit = bar / bars.length < magnitude;
        return (
          <span
            key={bar}
            className="block w-full rounded-sm"
            style={{
              height,
              background: lit ? (ok ? "var(--ok)" : "var(--warn)") : "#e7ecf2",
              opacity: lit ? 0.9 : 1,
            }}
          />
        );
      })}
    </div>
  );
}

function ThermoStrip({ state }: { state: SimulationResult["state"] | undefined }) {
  const rows = state ?? [];
  if (rows.length === 0) return null;
  const pressures = rows.map((s) => edgePressureBar(s) ?? 0).filter((v) => v > 0);
  const min = Math.min(...pressures);
  const max = Math.max(...pressures);
  return (
    <Panel title="Pressure ladder">
      <div className="space-y-2">
        {rows.map((s, i) => {
          const p = edgePressureBar(s) ?? 0;
          const frac = max > min ? (p - min) / (max - min) : 0.5;
          return (
            <div key={i}>
              <div className="mb-1 flex items-center justify-between gap-2 text-[10px]">
                <span className="mono truncate text-[var(--ink-dim)]">
                  {s.source && s.target ? `${s.source} -> ${s.target}` : `edge ${s.edge ?? s.edge_id ?? i}`}
                </span>
                <span className="mono text-[var(--ink-faint)]">{p.toFixed(3)}</span>
              </div>
              <div className="h-2 rounded-full bg-[var(--chrome)]">
                <div
                  className="h-2 rounded-full"
                  style={{
                    width: `${Math.max(4, frac * 100)}%`,
                    background: `linear-gradient(90deg, var(--cold), var(--hot))`,
                  }}
                />
              </div>
            </div>
          );
        })}
      </div>
    </Panel>
  );
}

function EdgesTable({ rows }: { rows: NonNullable<SimulationResult["state"]> }) {
  return (
    <Panel title="Edge state table">
      <div className="overflow-x-auto">
        <table className="w-full text-[11px]">
          <thead>
            <tr className="text-[var(--ink-faint)]">
              <th className="px-2 py-1 text-left font-medium">edge</th>
              <th className="px-2 py-1 text-right font-medium">P bar</th>
              <th className="px-2 py-1 text-right font-medium">h kJ/kg</th>
              <th className="px-2 py-1 text-right font-medium">m kg/s</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((s, i) => (
              <tr key={i} className="border-t border-[var(--line)]">
                <td className="mono max-w-[150px] truncate px-2 py-1 text-[var(--ink-dim)]">
                  {s.source && s.target ? `${s.source} -> ${s.target}` : `edge ${s.edge ?? s.edge_id ?? i}`}
                </td>
                <td className="mono px-2 py-1 text-right">{formatNumber(edgePressureBar(s), 3)}</td>
                <td className="mono px-2 py-1 text-right">{formatNumber(edgeEnthalpyKj(s), 2)}</td>
                <td className="mono px-2 py-1 text-right">{formatNumber(s.mass_flow_kg_s, 4)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </Panel>
  );
}

function ComponentsTable({
  rows,
  powerUnit,
}: {
  rows: NonNullable<SimulationResult["components"]>;
  powerUnit: PowerUnit;
}) {
  if (rows.length === 0) {
    return (
      <div className="text-[12px] text-[var(--ink-faint)]">
        No component ledger was returned by the backend for this run.
      </div>
    );
  }
  return (
    <Panel title="Component ledger">
      <div className="space-y-2">
        {rows.map((c, i) => (
          <div key={`${c.name}-${i}`} className="rounded-md border border-[var(--line)] bg-[var(--chrome-2)] p-2">
            <div className="flex items-center justify-between gap-2">
              <div>
                <div className="text-[12px] font-semibold text-[var(--ink)]">{c.name}</div>
                <div className="mono text-[10px] text-[var(--ink-faint)]">{c.component_type} · circuit {c.circuit}</div>
              </div>
              {c.energy && (
                <div className="mono text-right text-[10px] text-[var(--ink-dim)]">
                  Q {formatNumber(formatPower(c.energy.heat_transfer_w, powerUnit), powerDigits(powerUnit))} {powerUnit}
                  <br />W {formatNumber(formatPower(c.energy.work_w, powerUnit), powerDigits(powerUnit))} {powerUnit}
                </div>
              )}
            </div>
          </div>
        ))}
      </div>
    </Panel>
  );
}

function buildLocalHeatExchangerRows(
  nodes: DiagramNode[],
  edges: DiagramEdge[],
  state: SimulationResult["state"] | undefined,
): ComponentRow[] {
  if (!state) return [];
  return nodes
    .filter((node) => node.data.type === "HeatExchanger")
    .flatMap((node) => {
      const duty = heatExchangerDuty(node, nodes, edges, state);
      if (!duty) return [];
      return [{
        name: node.data.name,
        component_type: "HeatExchanger",
        circuit: node.data.circuit,
        energy: { heat_transfer_w: duty.avg_w, work_w: 0.0 },
      }];
    });
}

function heatExchangerDuty(
  node: DiagramNode,
  nodes: DiagramNode[],
  edges: DiagramEdge[],
  state: SimulationResult["state"],
): { avg_w: number } | null {
  const hotIn = portState(node.id, "hot_inlet", "target", edges, state);
  const hotOut = portState(node.id, "hot_outlet", "source", edges, state);
  const coldIn = portState(node.id, "cold_inlet", "target", edges, state);
  const coldOut = portState(node.id, "cold_outlet", "source", edges, state);
  if (!hotIn?.state || !hotOut?.state || !coldIn?.state || !coldOut?.state) return null;

  const hotMass = massFlowForStream(hotIn.edge, node.id, nodes, edges, state);
  const coldMass = massFlowForStream(coldIn.edge, node.id, nodes, edges, state);
  const hHotIn = edgeEnthalpyKj(hotIn.state);
  const hHotOut = edgeEnthalpyKj(hotOut.state);
  const hColdIn = edgeEnthalpyKj(coldIn.state);
  const hColdOut = edgeEnthalpyKj(coldOut.state);

  if (
    hotMass === null ||
    coldMass === null ||
    hHotIn === null ||
    hHotOut === null ||
    hColdIn === null ||
    hColdOut === null
  ) {
    return null;
  }

  const hotW = Math.abs(hotMass * (hHotIn - hHotOut) * 1000.0);
  const coldW = Math.abs(coldMass * (hColdOut - hColdIn) * 1000.0);
  if (!Number.isFinite(hotW) || !Number.isFinite(coldW)) return null;
  return { avg_w: 0.5 * (hotW + coldW) };
}

function portState(
  nodeId: string,
  port: string,
  side: "source" | "target",
  edges: DiagramEdge[],
  state: SimulationResult["state"],
): { edge: DiagramEdge; state: StateEntry } | null {
  const index = edges.findIndex((edge) =>
    side === "source"
      ? edge.source === nodeId && edge.sourceHandle === port
      : edge.target === nodeId && edge.targetHandle === port,
  );
  if (index < 0) return null;
  const entry = state?.[index];
  return entry ? { edge: edges[index], state: entry } : null;
}

function massFlowForStream(
  inletEdge: DiagramEdge,
  selectedNodeId: string,
  nodes: DiagramNode[],
  edges: DiagramEdge[],
  state: SimulationResult["state"],
): number | null {
  const edgeIndex = edges.findIndex((edge) => edge.id === inletEdge.id);
  const stateMass = edgeIndex >= 0 ? state?.[edgeIndex]?.mass_flow_kg_s : undefined;
  if (typeof stateMass === "number" && Number.isFinite(stateMass)) return Math.abs(stateMass);

  const visited = new Set<string>([selectedNodeId]);
  let cursor = inletEdge.source;
  while (!visited.has(cursor)) {
    visited.add(cursor);
    const source = nodes.find((node) => node.id === cursor);
    const configuredMass = numericParam(source?.data.params.m_flow_kg_s);
    if (configuredMass !== null) return Math.abs(configuredMass);

    const upstream = edges.find((edge) => edge.target === cursor);
    if (!upstream) break;
    cursor = upstream.source;
  }
  return null;
}

function numericParam(value: unknown): number | null {
  return typeof value === "number" && Number.isFinite(value) ? value : null;
}

function SolverLogView({
  result,
  iterations,
  residual,
  strategy,
  history,
  tolerance,
}: {
  result: SimulationResult | null;
  iterations: number | null;
  residual: number | null;
  strategy: string | null;
  history: IterationInfo[];
  tolerance: number | null;
}) {
  const chartData = history.map((row) => ({
    iter: row.iteration,
    residual: row.residual_norm > 0 ? row.residual_norm : 1e-300,
    delta: row.delta_norm,
  }));
  const last = history[history.length - 1];

  return (
    <div className="space-y-3">
      <div className="grid grid-cols-2 gap-2">
        <Metric label="Strategy" value={strategy} />
        <Metric label="Iterations" value={iterations} digits={0} />
        <Metric label="Final residual" value={residual} digits={3} scientific />
        <Metric label="Tolerance" value={tolerance} digits={3} scientific />
      </div>

      {last && (
        <div className="rounded-md border border-[var(--line)] bg-white px-2.5 py-2 text-[11px] text-[var(--ink-dim)]">
          Last step · ‖Δx‖ {formatScientific(last.delta_norm)}
          {typeof last.max_residual_index === "number"
            ? ` · dominant eq[${last.max_residual_index}] = ${formatScientific(last.max_residual)}`
            : ""}
          {typeof last.alpha === "number" ? ` · α=${last.alpha.toFixed(3)}` : ""}
        </div>
      )}

      {chartData.length >= 2 ? (
        <Panel title="Residual vs iteration">
          <div className="h-44 w-full">
            <ResponsiveContainer width="100%" height="100%">
              <LineChart data={chartData} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
                <CartesianGrid strokeDasharray="3 3" stroke="rgba(54,71,90,0.12)" />
                <XAxis dataKey="iter" tick={{ fontSize: 10 }} />
                <YAxis
                  scale="log"
                  domain={["auto", "auto"]}
                  tick={{ fontSize: 10 }}
                  width={52}
                  tickFormatter={(v) => Number(v).toExponential(0)}
                />
                <Tooltip
                  formatter={(value: number) => value.toExponential(3)}
                  labelFormatter={(label) => `Iteration ${label}`}
                />
                <Line
                  type="monotone"
                  dataKey="residual"
                  stroke="var(--accent)"
                  strokeWidth={2}
                  dot={false}
                  name="‖r‖₂"
                />
              </LineChart>
            </ResponsiveContainer>
          </div>
        </Panel>
      ) : (
        <div className="rounded-md border border-dashed border-[var(--line)] px-3 py-4 text-[11px] text-[var(--ink-faint)]">
          No iteration history yet — run Simulate. The API returns residual history when the
          solver captures verbose diagnostics.
        </div>
      )}

      {history.length > 0 && (
        <Panel title="Iteration table">
          <div className="max-h-56 overflow-auto">
            <table className="w-full text-left text-[10px]">
              <thead className="sticky top-0 bg-[var(--chrome)] text-[var(--ink-faint)]">
                <tr>
                  <th className="px-1.5 py-1 font-medium">#</th>
                  <th className="px-1.5 py-1 font-medium">‖r‖₂</th>
                  <th className="px-1.5 py-1 font-medium">‖Δx‖</th>
                  <th className="px-1.5 py-1 font-medium">α</th>
                  <th className="px-1.5 py-1 font-medium">dom</th>
                </tr>
              </thead>
              <tbody>
                {history.map((row) => (
                  <tr key={row.iteration} className="border-t border-[var(--line)] mono">
                    <td className="px-1.5 py-1">{row.iteration}</td>
                    <td className="px-1.5 py-1">{formatScientific(row.residual_norm)}</td>
                    <td className="px-1.5 py-1">{formatScientific(row.delta_norm)}</td>
                    <td className="px-1.5 py-1">
                      {typeof row.alpha === "number" ? row.alpha.toFixed(3) : "—"}
                    </td>
                    <td className="px-1.5 py-1">
                      {typeof row.max_residual_index === "number"
                        ? `eq[${row.max_residual_index}]`
                        : "—"}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </Panel>
      )}

      {result?.dof?.summary && (
        <Panel title="DoF ledger (server)">
          <pre className="mono max-h-40 overflow-auto whitespace-pre-wrap text-[10px] text-[var(--ink-dim)]">
            {result.dof.summary}
          </pre>
        </Panel>
      )}
    </div>
  );
}

function DebugView({
  result,
  error,
  config,
}: {
  result: SimulationResult | null;
  error: string | null;
  config: unknown | null;
}) {
  return (
    <div className="space-y-3">
      {error && <ErrorCard error={error} diagnostics={result?.failure_diagnostics ?? null} />}
      <Panel title="Config sent to solver">
        <pre className="mono max-h-72 overflow-auto whitespace-pre-wrap text-[10px] leading-relaxed text-[var(--ink-dim)]">
          {config ? JSON.stringify(config, null, 2) : "No config has been sent yet."}
        </pre>
      </Panel>
      <Panel title="Raw response">
        <pre className="mono max-h-72 overflow-auto whitespace-pre-wrap text-[10px] leading-relaxed text-[var(--ink-dim)]">
          {result ? JSON.stringify(result, null, 2) : "No response yet."}
        </pre>
      </Panel>
    </div>
  );
}

function buildCycleData(
  result: SimulationResult | null,
  nodes: DiagramNode[],
  edges: DiagramEdge[],
): CyclePoint[] {
  if (!result?.state) return [];
  const nodeById = new Map(nodes.map((node) => [node.id, node]));
  const points = result.state
    .filter((s, i) => isRefrigerantCycleState(s, i, edges, nodeById))
    .filter((s) => edgePressureBar(s) !== null && edgeEnthalpyKj(s) !== null)
    .filter((s) => (edgePressureBar(s) ?? 0) > 0)
    .map((s, i) => ({
      idx: i,
      h: edgeEnthalpyKj(s) ?? 0,
      p: edgePressureBar(s) ?? 0,
      label: `${edgeIndex(s, i)}`,
      edgeLabel: s.source && s.target ? `${s.source} -> ${s.target}` : undefined,
    }));
  if (points.length > 2) {
    const first = points[0];
    points.push({ ...first, label: first.label });
  }

  function isRefrigerantCycleState(
    state: StateEntry,
    index: number,
    edges: DiagramEdge[],
    nodeById: Map<string, DiagramNode>,
  ): boolean {
    const graphEdge = edges[index];
    if (!graphEdge) {
      const label = `${state.source ?? ""} ${state.target ?? ""}`.toLowerCase();
      return label.includes("refrigerant") || label.includes("compressor") || label.includes("condenser") || label.includes("evaporator");
    }

    const source = nodeById.get(graphEdge.source);
    const target = nodeById.get(graphEdge.target);
    const sourcePort = String(graphEdge.sourceHandle ?? "");
    const targetPort = String(graphEdge.targetHandle ?? "");
    if (isSecondaryMollierPort(sourcePort) || isSecondaryMollierPort(targetPort)) return false;
    if (isSecondaryMollierNode(source) || isSecondaryMollierNode(target)) return false;
    return isRefrigerantMollierNode(source) || isRefrigerantMollierNode(target);
  }

  function isSecondaryMollierPort(port: string): boolean {
    return port.startsWith("secondary_") || port.startsWith("hot_") || port.startsWith("cold_");
  }

  function isSecondaryMollierNode(node: DiagramNode | undefined): boolean {
    const type = node?.data.type ?? "";
    return type === "Fan" || type === "Pump" || type.startsWith("Air") || type.startsWith("Brine") || type === "HeatExchanger";
  }

  function isRefrigerantMollierNode(node: DiagramNode | undefined): boolean {
    const type = node?.data.type ?? "";
    if (!type || isSecondaryMollierNode(node) || type === "SaturatedController") return false;
    return (
      type.includes("Compressor") ||
      type.includes("Condenser") ||
      type.includes("Evaporator") ||
      type.includes("Valve") ||
      type === "Pipe" ||
      type === "Drum" ||
      type === "FlowSplitter" ||
      type === "FlowMerger" ||
      type.startsWith("Refrigerant")
    );
  }
  return points;
}

function mollierDomain(
  cycle: CyclePoint[],
  liquid: MollierLinePoint[],
  vapor: MollierLinePoint[],
): { h: [number, number]; p: [number, number] } {
  const all = [...cycle, ...liquid, ...vapor].filter((point) => point.p > 0);
  const hValues = all.map((point) => point.h).filter(Number.isFinite);
  const pValues = all.map((point) => point.p).filter((value) => Number.isFinite(value) && value > 0);
  const hMin = Math.min(...hValues);
  const hMax = Math.max(...hValues);
  const pMin = Math.min(...pValues);
  const pMax = Math.max(...pValues);
  const hPad = Math.max(20, (hMax - hMin) * 0.08);
  return {
    h: [Math.floor(hMin - hPad), Math.ceil(hMax + hPad)],
    p: [Math.max(0.05, pMin * 0.75), Math.max(pMin * 1.1, pMax * 1.25)],
  };
}

function scenarioFluid(config: unknown): string {
  if (config && typeof config === "object" && "fluid" in config) {
    const fluid = (config as { fluid?: unknown }).fluid;
    if (typeof fluid === "string" && fluid.trim()) return fluid;
  }
  return "R134a";
}

function edgeIndex(state: StateEntry, fallback: number): number {
  if (typeof state.edge === "number") return state.edge;
  if (typeof state.edge_id === "number") return state.edge_id;
  return fallback;
}

function totalPower(perf: SimulationResult["performance"] | undefined): number | null {
  if (!perf) return null;
  const values = [kwToW(perf.compressor_power_kw), perf.compressor_power_w, perf.pump_power_w].filter(
    (v): v is number => typeof v === "number" && Number.isFinite(v),
  );
  if (values.length === 0) return null;
  return values.reduce((a, b) => a + b, 0);
}

function isConverged(result: SimulationResult | null): boolean {
  if (!result) return false;
  return String(result.status).toLowerCase() === "converged" || result.convergence?.converged === true;
}

function buildBenchDetail(
  result: SimulationResult | null,
  error: string | null,
  iterations: number | null,
  residual: number | null,
): string {
  if (iterations !== null) {
    return `${iterations} iterations${residual !== null ? ` · residual ${residual.toExponential(2)}` : ""}`;
  }

  const diagnostics = result?.failure_diagnostics;
  if (diagnostics?.final_residual_norm !== undefined) {
    const row = diagnostics.dominant_residual_index;
    const rowText = typeof row === "number" ? ` · dominant row ${row}` : "";
    return `Residual ${diagnostics.final_residual_norm.toExponential(2)}${rowText}`;
  }

  if (error) {
    return firstLine(error);
  }

  return "No calculation launched yet";
}

function statusLabel(status: string): string {
  return status
    .toLowerCase()
    .replace(/_/g, " ")
    .replace(/^\w/, (c) => c.toUpperCase());
}

function firstLine(value: string): string {
  return value.split(/\r?\n/)[0] || value;
}

function formatScientific(value: number | null | undefined): string {
  return typeof value === "number" && Number.isFinite(value) ? value.toExponential(3) : "—";
}

function formatDiagnosticIndex(value: number | null | undefined): string {
  return typeof value === "number" && Number.isFinite(value) ? String(value) : "—";
}

function formatNumber(value: number | null | undefined, digits = 2): string {
  return typeof value === "number" && Number.isFinite(value) ? value.toFixed(digits) : "—";
}

function kwToW(value: number | null | undefined): number | null {
  return typeof value === "number" && Number.isFinite(value) ? value * 1000 : null;
}

function formatPower(valueW: number | null | undefined, unit: PowerUnit): number | null {
  if (typeof valueW !== "number" || !Number.isFinite(valueW)) return null;
  return unit === "kW" ? valueW / 1000.0 : valueW;
}

function powerDigits(unit: PowerUnit): number {
  return unit === "kW" ? 3 : 2;
}

function edgePressureBar(edge: NonNullable<SimulationResult["state"]>[number]): number | null {
  if (typeof edge.pressure_bar === "number") return edge.pressure_bar;
  if (typeof edge.pressure_pa === "number") return edge.pressure_pa / 1e5;
  return null;
}

function edgeEnthalpyKj(edge: NonNullable<SimulationResult["state"]>[number]): number | null {
  if (typeof edge.enthalpy_kj_kg === "number") return edge.enthalpy_kj_kg;
  if (typeof edge.enthalpy_j_kg === "number") return edge.enthalpy_j_kg / 1e3;
  return null;
}
