import { describe, it, expect } from "vitest";
import type { Edge, Node } from "@xyflow/react";
import { fixedFlagKey } from "./componentMeta";
import {
  adjacentProbeNames,
  calibrateAdjacentActuatorPatches,
  collectCalibHints,
  withDerivedUaEff,
  type CalibNodeData,
} from "./calibAssist";
import type { SolvedVariable } from "./api";

function node(
  id: string,
  type: string,
  name: string,
  params: Record<string, number | string | boolean> = {},
): Node<CalibNodeData> {
  return {
    id,
    type: "entropykNode",
    position: { x: 0, y: 0 },
    data: { type, name, circuit: 0, params },
  };
}

describe("calibAssist — Fixed/Free générique", () => {
  it("lists adjacent Probe names for any component", () => {
    const nodes = [
      node("e", "Evaporator", "evap", { ua: 6000, z_ua: 1 }),
      node("p1", "Probe", "sst", { tsat_c: 5, [fixedFlagKey("tsat_c")]: true }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "e", target: "p1", sourceHandle: "outlet", targetHandle: "inlet" },
    ];
    expect(adjacentProbeNames("evap", nodes, edges)).toEqual(["sst"]);
  });

  it("one-click Free Z_UA when Probe Tsat Fixed", () => {
    const nodes = [
      node("e", "FloodedEvaporator", "evap", {
        ua: 9000,
        z_ua: 1.0,
        [fixedFlagKey("z_ua")]: true,
      }),
      node("p1", "Probe", "sst", {
        tsat_c: 5,
        [fixedFlagKey("tsat_c")]: true,
      }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "e", target: "p1", sourceHandle: "outlet", targetHandle: "inlet" },
    ];
    const patches = calibrateAdjacentActuatorPatches(nodes[1], nodes, edges);
    expect(patches).toHaveLength(1);
    expect(patches[0]).toMatchObject({
      nodeId: "e",
      params: { [fixedFlagKey("z_ua")]: false, z_ua: 1.0 },
    });
  });

  it("one-click Free z_dp when Probe P Fixed", () => {
    const nodes = [
      node("c", "Condenser", "cond", {
        ua: 5000,
        z_dp: 1.0,
        [fixedFlagKey("z_dp")]: true,
        z_ua: 1.0,
        [fixedFlagKey("z_ua")]: true,
      }),
      node("p1", "Probe", "p_probe", {
        p_bar: 12,
        [fixedFlagKey("p_bar")]: true,
      }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "c", target: "p1", sourceHandle: "inlet", targetHandle: "outlet" },
    ];
    const patches = calibrateAdjacentActuatorPatches(nodes[1], nodes, edges);
    expect(patches).toHaveLength(1);
    expect(patches[0].params[fixedFlagKey("z_dp")]).toBe(false);
    // z_ua not compatible with P — stays Fixed
    expect(patches[0].params[fixedFlagKey("z_ua")]).toBe(true);
  });

  it("one-click Free z_flow when Probe Capacity Fixed (compressor)", () => {
    const nodes = [
      node("comp", "IsentropicCompressor", "comp", {
        z_flow: 1.0,
        [fixedFlagKey("z_flow")]: true,
        f_w: 0.1,
        [fixedFlagKey("f_w")]: true,
      }),
      node("p1", "Probe", "cap", {
        capacity_w: 12000,
        [fixedFlagKey("capacity_w")]: true,
      }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "comp", target: "p1", sourceHandle: "outlet", targetHandle: "inlet" },
    ];
    const patches = calibrateAdjacentActuatorPatches(nodes[1], nodes, edges);
    expect(patches).toHaveLength(1);
    expect(patches[0].params[fixedFlagKey("z_flow")]).toBe(false);
    expect(patches[0].params[fixedFlagKey("f_w")]).toBe(true);
  });

  it("one-click Free f_w when Probe T Fixed", () => {
    const nodes = [
      node("comp", "IsentropicCompressor", "comp", {
        f_w: 0.05,
        [fixedFlagKey("f_w")]: true,
        z_flow: 1.0,
        [fixedFlagKey("z_flow")]: true,
      }),
      node("p1", "Probe", "dgt", {
        t_c: 75,
        [fixedFlagKey("t_c")]: true,
      }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "comp", target: "p1", sourceHandle: "outlet", targetHandle: "inlet" },
    ];
    const patches = calibrateAdjacentActuatorPatches(nodes[1], nodes, edges);
    expect(patches[0].params[fixedFlagKey("f_w")]).toBe(false);
    expect(patches[0].params[fixedFlagKey("z_flow")]).toBe(true);
  });

  it("returns empty when compatible actuators already Free", () => {
    const nodes = [
      node("e", "Evaporator", "evap", {
        ua: 6000,
        z_ua: 1.0,
        [fixedFlagKey("z_ua")]: false,
      }),
      node("p1", "Probe", "sst", {
        tsat_c: 5,
        [fixedFlagKey("tsat_c")]: true,
      }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "e", target: "p1", sourceHandle: "outlet", targetHandle: "inlet" },
    ];
    expect(calibrateAdjacentActuatorPatches(nodes[1], nodes, edges)).toHaveLength(0);
  });

  it("tips when Probe Fixed but compatible factor still Fixed", () => {
    const nodes = [
      node("e", "Condenser", "cond", {
        ua: 5000,
        z_ua: 1.0,
        [fixedFlagKey("z_ua")]: true,
      }),
      node("p1", "Probe", "sdt", {
        tsat_c: 40,
        [fixedFlagKey("tsat_c")]: true,
      }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "e", target: "p1", sourceHandle: "inlet", targetHandle: "outlet" },
    ];
    const hints = collectCalibHints(nodes, edges, nodes[1]);
    expect(hints.some((h) => h.level === "tip" && h.message.includes("décoche Fixed"))).toBe(
      true,
    );
  });

  it("ok when Probe Fixed + compatible factor Free", () => {
    const nodes = [
      node("e", "Condenser", "cond", {
        ua: 5000,
        z_dp: 1.0,
        [fixedFlagKey("z_dp")]: false,
      }),
      node("p1", "Probe", "p_probe", {
        p_bar: 12,
        [fixedFlagKey("p_bar")]: true,
      }),
    ];
    const edges: Edge[] = [
      { id: "pe", source: "e", target: "p1", sourceHandle: "inlet", targetHandle: "outlet" },
    ];
    const hints = collectCalibHints(nodes, edges, nodes[0]);
    expect(hints.some((h) => h.level === "ok" && h.message.includes("z_dp"))).toBe(true);
  });

  it("warns Free factor without adjacent compatible Probe", () => {
    const nodes = [
      node("e", "Evaporator", "evap", {
        ua: 6000,
        z_ua: 1.0,
        [fixedFlagKey("z_ua")]: false,
      }),
    ];
    const hints = collectCalibHints(nodes, [], nodes[0]);
    expect(hints.some((h) => h.level === "warn")).toBe(true);
  });

  it("always shows a baseline tip on Probe / HX when nothing paired yet", () => {
    const probe = node("p1", "Probe", "sst", { tsat_c: 5 });
    const hx = node("e", "Evaporator", "evap", { ua: 6000, z_ua: 1.0 });
    expect(collectCalibHints([probe], [], probe).some((h) => h.level === "tip")).toBe(true);
    expect(collectCalibHints([hx], [], hx).some((h) => h.level === "tip")).toBe(true);
  });

  it("appends derived UA_eff = UA × z_ua", () => {
    const nodes = [node("e", "Evaporator", "evap", { ua: 8000, z_ua: 1 })];
    const solved: SolvedVariable[] = [
      {
        id: "evap__z_ua",
        component: "evap",
        variable: "z_ua",
        value: 0.85,
        min: 0.05,
        max: 3,
      },
    ];
    const out = withDerivedUaEff("evap", nodes, solved);
    expect(out).toHaveLength(2);
    expect(out[1]).toMatchObject({
      variable: "UA_eff",
      value: 6800,
      component: "evap",
    });
  });
});
