import { describe, it, expect } from "vitest";
import {
  COMPONENTS,
  COMPONENT_BY_TYPE,
  COMPONENT_CATEGORIES,
  defaultParams,
  isSecondaryPort,
  nodeSize,
  portLabel,
  portRole,
  portSide,
} from "./componentMeta";

describe("portSide", () => {
  it("places inlet/suction on the left", () => {
    expect(portSide("inlet")).toBe("left");
    expect(portSide("inlet_a")).toBe("left");
    expect(portSide("suction")).toBe("left");
  });

  it("places outlet/discharge on the right", () => {
    expect(portSide("outlet")).toBe("right");
    expect(portSide("outlet_b")).toBe("right");
    expect(portSide("discharge")).toBe("right");
  });

  it("places liquid/vapor outlets on the bottom and economizer on top", () => {
    expect(portSide("liquid_outlet")).toBe("bottom");
    expect(portSide("vapor_outlet")).toBe("bottom");
    expect(portSide("economizer")).toBe("top");
  });

  it("places the caloporteur inlet at the bottom and outlet at the top", () => {
    expect(portSide("secondary_inlet")).toBe("bottom");
    expect(portSide("secondary_outlet")).toBe("top");
  });

  it("defaults unknown ports to the left", () => {
    expect(portSide("mystery")).toBe("left");
  });
});

describe("secondary (caloporteur) ports", () => {
  it("identifies the secondary ports", () => {
    expect(isSecondaryPort("secondary_inlet")).toBe(true);
    expect(isSecondaryPort("secondary_outlet")).toBe(true);
    expect(isSecondaryPort("inlet")).toBe(false);
    expect(isSecondaryPort("outlet")).toBe(false);
  });

  it("treats the secondary inlet as a target and outlet as a source", () => {
    expect(portRole("secondary_inlet")).toBe("target");
    expect(portRole("secondary_outlet")).toBe("source");
  });

  it("labels the secondary ports as htf", () => {
    expect(portLabel("secondary_inlet")).toBe("htf·in");
    expect(portLabel("secondary_outlet")).toBe("htf·out");
  });

  it("exposes caloporteur ports on the main heat exchangers", () => {
    for (const type of [
      "Condenser",
      "Evaporator",
      "FloodedEvaporator",
      "FinCoilCondenser",
      "BphxEvaporator",
      "BphxCondenser",
    ]) {
      const meta = COMPONENT_BY_TYPE[type];
      expect(meta.ports, type).toContain("secondary_inlet");
      expect(meta.ports, type).toContain("secondary_outlet");
    }
  });
});

describe("portRole", () => {
  it("treats outlets/discharge/*_outlet as sources", () => {
    expect(portRole("outlet")).toBe("source");
    expect(portRole("discharge")).toBe("source");
    expect(portRole("liquid_outlet")).toBe("source");
    expect(portRole("hot_outlet")).toBe("source");
  });

  it("treats inlets/suction as targets", () => {
    expect(portRole("inlet")).toBe("target");
    expect(portRole("suction")).toBe("target");
    expect(portRole("cold_inlet")).toBe("target");
  });
});

describe("nodeSize", () => {
  it("gives compressors a square footprint", () => {
    const s = nodeSize("IsentropicCompressor");
    expect(s.w).toBe(s.h);
  });

  it("makes heat exchangers wider than tall", () => {
    const s = nodeSize("Condenser");
    expect(s.w).toBeGreaterThan(s.h);
  });

  it("makes the separator (drum) taller than wide", () => {
    const s = nodeSize("Drum");
    expect(s.h).toBeGreaterThan(s.w);
  });

  it("returns positive dimensions for unknown types", () => {
    const s = nodeSize("Whatever");
    expect(s.w).toBeGreaterThan(0);
    expect(s.h).toBeGreaterThan(0);
  });
});

describe("portLabel", () => {
  it("abbreviates known ports", () => {
    expect(portLabel("inlet")).toBe("in");
    expect(portLabel("discharge")).toBe("dis");
    expect(portLabel("economizer")).toBe("eco");
  });

  it("falls back to the raw port name", () => {
    expect(portLabel("custom_port")).toBe("custom_port");
  });
});

describe("defaultParams", () => {
  it("returns the declared default for each param", () => {
    const params = defaultParams("IsentropicCompressor");
    expect(params.isentropic_efficiency).toBe(0.75);
    expect(params.t_cond_k).toBe(323.15);
  });

  it("returns an empty object for an unknown type", () => {
    expect(defaultParams("DoesNotExist")).toEqual({});
  });
});

describe("COMPONENTS catalogue integrity", () => {
  it("has a unique type for every component", () => {
    const types = COMPONENTS.map((c) => c.type);
    expect(new Set(types).size).toBe(types.length);
  });

  it("indexes every component in COMPONENT_BY_TYPE", () => {
    for (const c of COMPONENTS) {
      expect(COMPONENT_BY_TYPE[c.type]).toBe(c);
    }
  });

  it("declares at least one port per component", () => {
    for (const c of COMPONENTS) {
      if (c.category === "Controls" || c.category === "Advanced") continue;
      if (c.type === "SaturatedController") continue;
      expect(c.ports.length).toBeGreaterThan(0);
    }
  });

  it("uses only declared categories", () => {
    for (const c of COMPONENTS) {
      expect(COMPONENT_CATEGORIES).toContain(c.category);
    }
  });

  it("has unique param keys within each component", () => {
    for (const c of COMPONENTS) {
      const keys = c.params.map((p) => p.key);
      expect(new Set(keys).size, `duplicate param in ${c.type}`).toBe(keys.length);
    }
  });

  it("uses hex colours", () => {
    for (const c of COMPONENTS) {
      expect(c.color, c.type).toMatch(/^#[0-9a-fA-F]{6}$/);
    }
  });

  it("exposes detailed fin-coil engineering geometry", () => {
    const keys = COMPONENT_BY_TYPE.FinCoilCondenser.params.map((param) => param.key);
    expect(keys).toEqual(
      expect.arrayContaining([
        "manufacturer",
        "model",
        "face_width_m",
        "face_height_m",
        "n_rows",
        "fin_type",
        "fin_pitch_fpi",
        "tube_od_mm",
        "tube_pitch_mm",
        "air_face_velocity_m_s",
      ]),
    );
  });

  it("exposes manufacturer and plate-pack data for both BPHX models", () => {
    for (const type of ["BphxEvaporator", "BphxCondenser"]) {
      const keys = COMPONENT_BY_TYPE[type].params.map((param) => param.key);
      expect(keys).toEqual(
        expect.arrayContaining([
          "manufacturer",
          "model",
          "n_plates",
          "plate_length_m",
          "plate_width_m",
          "channel_spacing_mm",
          "chevron_angle_deg",
          "correlation",
          "dp_correlation",
        ]),
      );
      const dp = COMPONENT_BY_TYPE[type].params.find((p) => p.key === "dp_correlation");
      expect(dp?.options?.map((o) => o.value)).toEqual([
        "SimplifiedChannel",
        "Martin1996",
      ]);
    }
  });

  it("handles undefined or empty string type without throwing in nodeSize", () => {
    expect(() => nodeSize(undefined as unknown as string)).not.toThrow();
    expect(nodeSize(undefined as unknown as string)).toEqual({ w: 88, h: 58 });
    expect(nodeSize("")).toEqual({ w: 88, h: 58 });
  });
});
