"use client";

import { useCallback, useState } from "react";
import { useDiagramStore } from "@/store/diagramStore";
import { simulate } from "@/lib/api";
import { buildScenarioConfig, validateConfig } from "@/lib/configBuilder";
import { computeDofLedger } from "@/lib/dofLedger";
import type { EntropykNodeData } from "@/lib/configBuilder";
import type { Node } from "@xyflow/react";

/**
 * Shared simulation trigger.
 *
 * Lifted out of `ResultsPanel` so any surface (TopBar Solve button, keyboard
 * shortcut, multi-run, results panel) can fire the same validated solve and
 * surface the same validation issues. The result/error stay in the diagram
 * store; this hook only owns the transient `issues` and `simulating` flags
 * needed by callers.
 */
export function useScenarioSimulation() {
  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 maxIterations = useDiagramStore((s) => s.maxIterations);
  const tolerance = useDiagramStore((s) => s.tolerance);

  const setLastConfig = useDiagramStore((s) => s.setLastConfig);
  const setResult = useDiagramStore((s) => s.setResult);
  const setSimulating = useDiagramStore((s) => s.setSimulating);
  const purgeAutoCalibrationControlNodes = useDiagramStore(
    (s) => s.purgeAutoCalibrationControlNodes,
  );
  const simulating = useDiagramStore((s) => s.simulating);

  const [issues, setIssues] = useState<string[]>([]);

  const run = useCallback(async () => {
    // Fixed/Free calib must never leave SaturatedController blocks on the canvas.
    purgeAutoCalibrationControlNodes();
    const { nodes: liveNodes, edges: liveEdges } = useDiagramStore.getState();
    const problems = validateConfig(liveNodes, liveEdges);
    const ledger = computeDofLedger(liveNodes as Node<EntropykNodeData>[], liveEdges);
    if (ledger.balance === "over-constrained") {
      problems.push(
        `DoF over-constrained: ${ledger.nEquations} equations > ${ledger.nUnknowns} unknowns. ` +
          "Remove a FIX (quality control, extra outlet closure) or FREE an actuator.",
      );
    }
    setIssues(problems);
    if (problems.length > 0) return false;

    const scenarioConfig = buildScenarioConfig(liveNodes, liveEdges, {
      fluid,
      fluidBackend,
      solverStrategy,
      maxIterations,
      tolerance,
    });
    setSimulating(true);
    setLastConfig(scenarioConfig);
    setResult(null, null);
    try {
      const resp = await simulate(scenarioConfig);
      if (resp.ok && resp.result) {
        setResult(resp.result, null);
        if (
          resp.result.dof &&
          resp.result.dof.n_equations !== resp.result.dof.n_unknowns
        ) {
          setIssues([
            `Server DoF: ${resp.result.dof.n_equations} eqs vs ${resp.result.dof.n_unknowns} unk (${resp.result.dof.balance})`,
          ]);
        }
        return true;
      }
      setResult(null, resp.error || "Simulation failed");
      return false;
    } catch (e) {
      setResult(null, e instanceof Error ? e.message : String(e));
      return false;
    } finally {
      setSimulating(false);
    }
  }, [
    nodes,
    edges,
    fluid,
    fluidBackend,
    solverStrategy,
    maxIterations,
    tolerance,
    purgeAutoCalibrationControlNodes,
    setLastConfig,
    setResult,
    setSimulating,
  ]);

  return { run, issues, setIssues, simulating };
}
