import test from "node:test";
import assert from "node:assert/strict";
import path from "node:path";
import { compile } from "../src/compile.js";
import fixture from "../examples/esp32-audio.json" with { type: "json" };

test("compile returns svg and topology for valid model", () => {
  const result = compile(fixture);
  assert.equal(result.ok, true);
  assert.ok(result.svg.includes("<svg"));
  assert.ok(result.topology.power_domains.includes("3V3"));
  assert.ok(result.topology.clock_sources.includes("U1"));
  assert.ok(result.layout_metrics);
  assert.equal(result.layout_metrics.overlap_edges, 0);
  assert.equal(result.layout_metrics.crossings, 0);
  assert.equal(typeof result.layout_metrics.total_bends, "number");
  assert.equal(typeof result.layout_metrics.detour_ratio, "number");
  assert.equal(typeof result.layout_metrics.label_tie_fallbacks, "number");
  assert.equal(typeof result.layout_metrics.label_tie_routes, "number");
  assert.ok(Array.isArray(result.bus_groups));
  assert.ok(result.render_mode_used);
});

test("compile fails on invalid model", () => {
  const bad = { symbols: {}, instances: [], nets: [] };
  const result = compile(bad);
  assert.equal(result.ok, false);
  assert.ok(result.errors.length > 0);
  assert.equal(result.layout_metrics.total_bends, 0);
  assert.equal(result.layout_metrics.detour_ratio, 1);
  assert.equal(result.layout_metrics.label_tie_routes, 0);
});

test("compile accepts render mode options", () => {
  const result = compile(fixture, { render_mode: "explicit" });
  assert.equal(result.ok, true);
  assert.equal(result.render_mode_used, "explicit");
});

test("compile default layout engine path remains stable", () => {
  const baseline = compile(fixture);
  const explicitDefault = compile(fixture, { use_elk_layout: false });

  assert.equal(baseline.ok, true);
  assert.deepEqual(explicitDefault.layout, baseline.layout);
  assert.deepEqual(explicitDefault.layout_metrics, baseline.layout_metrics);
  assert.equal(baseline.layout_engine_requested, "schemeta-v2");
  assert.equal(baseline.layout_engine_used, "schemeta-v2");
  assert.deepEqual(baseline.layout_warnings, []);
});

test("compile accepts ELK layout flag option", () => {
  const result = compile(fixture, {
    use_elk_layout: true,
    elk_runtime_module: path.resolve(process.cwd(), "tests/fixtures/mock-elk-runtime.cjs")
  });

  assert.equal(result.ok, true);
  assert.equal(result.layout_engine_requested, "elk");
  assert.equal(result.layout_engine_used, "elk");
  assert.deepEqual(result.layout_warnings, []);
});

test("compile ELK fallback is deterministic when runtime is unavailable", () => {
  const options = {
    use_elk_layout: true,
    elk_runtime_module: "__missing_elk_runtime_for_test__"
  };
  const runA = compile(fixture, options);
  const runB = compile(fixture, options);

  assert.equal(runA.ok, true);
  assert.deepEqual(runB.layout, runA.layout);
  assert.deepEqual(runB.layout_metrics, runA.layout_metrics);
  assert.equal(runA.layout_engine_requested, "elk");
  assert.equal(runA.layout_engine_used, "schemeta-v2");
  assert.equal(runA.layout_warnings.length, 1);
  assert.equal(runA.layout_warnings[0].code, "elk_layout_unavailable_fallback");
  assert.deepEqual(runB.layout_warnings, runA.layout_warnings);
});

test("compile auto-creates generic symbols for unknown instances", () => {
  const model = {
    meta: { title: "Generic Demo" },
    symbols: {},
    instances: [
      {
        ref: "X1",
        symbol: "mystery_block",
        properties: { value: "Mystery" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      },
      {
        ref: "X2",
        symbol: "mystery_sensor",
        properties: { value: "Sensor" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      }
    ],
    nets: [
      {
        name: "SIG_A",
        class: "signal",
        nodes: [
          { ref: "X1", pin: "IN" },
          { ref: "X2", pin: "OUT" }
        ]
      },
      {
        name: "GND",
        class: "ground",
        nodes: [
          { ref: "X1", pin: "GND" },
          { ref: "X2", pin: "GND" }
        ]
      }
    ],
    constraints: {},
    annotations: []
  };

  const result = compile(model);
  assert.equal(result.ok, true);
  assert.ok(result.svg.includes("<svg"));
  assert.ok(result.warnings.some((w) => w.code === "auto_generic_symbol_created"));
});

test("compile auto-creates passive templates for common refs", () => {
  const model = {
    meta: { title: "Passive Template Demo" },
    symbols: {},
    instances: [
      {
        ref: "R1",
        symbol: "resistor_generic",
        properties: { value: "10k resistor" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      },
      {
        ref: "U1",
        symbol: "mystery_logic",
        properties: { value: "Logic" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      }
    ],
    nets: [
      {
        name: "SIG",
        class: "signal",
        nodes: [
          { ref: "R1", pin: "1" },
          { ref: "U1", pin: "IN" }
        ]
      },
      {
        name: "GND",
        class: "ground",
        nodes: [
          { ref: "R1", pin: "2" },
          { ref: "U1", pin: "GND" }
        ]
      }
    ],
    constraints: {},
    annotations: []
  };

  const result = compile(model);
  assert.equal(result.ok, true);
  assert.ok(result.warnings.some((w) => w.code === "auto_template_symbol_created"));
});

test("compile accepts minimal shorthand symbols and hydrates fields", () => {
  const model = {
    meta: { title: "Shorthand Symbols" },
    symbols: {
      resistor_short: {
        template_name: "resistor"
      },
      generic_short: {
        category: "generic"
      }
    },
    instances: [
      {
        ref: "R1",
        symbol: "resistor_short",
        properties: { value: "1k" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      },
      {
        ref: "X1",
        symbol: "generic_short",
        properties: { value: "Mystery" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      }
    ],
    nets: [
      {
        name: "SIG",
        class: "signal",
        nodes: [
          { ref: "R1", pin: "1" },
          { ref: "X1", pin: "IO" }
        ]
      },
      {
        name: "GND",
        class: "ground",
        nodes: [
          { ref: "R1", pin: "2" },
          { ref: "X1", pin: "GND" }
        ]
      }
    ],
    constraints: {},
    annotations: []
  };

  const result = compile(model);
  assert.equal(result.ok, true);
  assert.ok(result.svg.includes("<svg"));
  assert.ok(result.warnings.some((w) => w.code === "auto_template_symbol_hydrated"));
  assert.ok(result.warnings.some((w) => w.code === "auto_generic_symbol_hydrated"));
});

test("compile supports instance.part without explicit symbols", () => {
  const model = {
    meta: { title: "Part Shortcut" },
    symbols: {},
    instances: [
      {
        ref: "R1",
        part: "resistor",
        properties: { value: "10k" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      },
      {
        ref: "C1",
        part: "capacitor",
        properties: { value: "100nF" },
        placement: { x: null, y: null, rotation: 0, locked: false }
      }
    ],
    nets: [
      {
        name: "SIG",
        class: "signal",
        nodes: [
          { ref: "R1", pin: "1" },
          { ref: "C1", pin: "1" }
        ]
      },
      {
        name: "GND",
        class: "ground",
        nodes: [
          { ref: "R1", pin: "2" },
          { ref: "C1", pin: "2" }
        ]
      }
    ],
    constraints: {},
    annotations: []
  };

  const result = compile(model);
  assert.equal(result.ok, true);
  assert.ok(result.svg.includes("<svg"));
  assert.equal(result.errors.length, 0);
});

test("grouped auto-layout avoids tall single-column collapse", () => {
  const model = {
    meta: { title: "Group packing" },
    symbols: {
      q: {
        symbol_id: "q",
        category: "analog",
        body: { width: 90, height: 70 },
        pins: [
          { name: "B", number: "1", side: "left", offset: 35, type: "analog" },
          { name: "C", number: "2", side: "top", offset: 45, type: "analog" },
          { name: "E", number: "3", side: "bottom", offset: 45, type: "analog" }
        ]
      },
      adc: {
        symbol_id: "adc",
        category: "generic",
        body: { width: 120, height: 60 },
        pins: [
          { name: "IN", number: "1", side: "left", offset: 30, type: "analog" },
          { name: "3V3", number: "2", side: "top", offset: 30, type: "power_in" },
          { name: "GND", number: "3", side: "bottom", offset: 30, type: "ground" }
        ]
      }
    },
    instances: [
      { ref: "Q1", symbol: "q", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "R1", part: "resistor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "R2", part: "resistor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "C1", part: "capacitor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "U1", symbol: "adc", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "R3", part: "resistor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "R4", part: "resistor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "C2", part: "capacitor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } }
    ],
    nets: [
      { name: "N1", class: "analog", nodes: [{ ref: "Q1", pin: "C" }, { ref: "U1", pin: "IN" }] },
      { name: "N2", class: "ground", nodes: [{ ref: "Q1", pin: "E" }, { ref: "U1", pin: "GND" }] },
      { name: "N3", class: "power", nodes: [{ ref: "R1", pin: "1" }, { ref: "U1", pin: "3V3" }] }
    ],
    constraints: {
      groups: [
        { name: "front", members: ["Q1", "R1", "R2", "C1"], layout: "cluster" },
        { name: "adc", members: ["U1", "R3", "R4", "C2"], layout: "cluster" }
      ]
    },
    annotations: []
  };

  const result = compile(model);
  assert.equal(result.ok, true);
  const xs = result.layout.placed.map((p) => p.x);
  const ys = result.layout.placed.map((p) => p.y);
  const widthSpread = Math.max(...xs) - Math.min(...xs);
  const heightSpread = Math.max(...ys) - Math.min(...ys);

  assert.ok(widthSpread > 420);
  assert.ok(heightSpread < 900);
});

test("multi-node signal nets render explicit junction dots", () => {
  const model = {
    meta: { title: "Junction coverage" },
    symbols: {},
    instances: [
      { ref: "R1", part: "resistor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "R2", part: "resistor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "R3", part: "resistor", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } }
    ],
    nets: [
      {
        name: "SIG",
        class: "signal",
        nodes: [
          { ref: "R1", pin: "1" },
          { ref: "R2", pin: "1" },
          { ref: "R3", pin: "1" }
        ]
      },
      {
        name: "GND",
        class: "ground",
        nodes: [
          { ref: "R1", pin: "2" },
          { ref: "R2", pin: "2" },
          { ref: "R3", pin: "2" }
        ]
      }
    ],
    constraints: {},
    annotations: []
  };

  const result = compile(model, { render_mode: "explicit" });
  assert.equal(result.ok, true);
  assert.ok(result.svg.includes('data-net-junction="SIG"'));
});

test("auto-rotation chooses non-zero orientation when it improves pin alignment", () => {
  const model = {
    meta: { title: "Rotation heuristic" },
    symbols: {
      n1: {
        symbol_id: "n1",
        category: "generic",
        body: { width: 120, height: 80 },
        pins: [
          { name: "L", number: "1", side: "left", offset: 40, type: "passive" },
          { name: "R", number: "2", side: "right", offset: 40, type: "passive" }
        ]
      },
      n2: {
        symbol_id: "n2",
        category: "generic",
        body: { width: 120, height: 80 },
        pins: [
          { name: "T", number: "1", side: "top", offset: 60, type: "passive" },
          { name: "B", number: "2", side: "bottom", offset: 60, type: "passive" }
        ]
      }
    },
    instances: [
      { ref: "A1", symbol: "n1", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } },
      { ref: "A2", symbol: "n2", properties: {}, placement: { x: null, y: null, rotation: 0, locked: false } }
    ],
    nets: [
      { name: "N1", class: "signal", nodes: [{ ref: "A1", pin: "R" }, { ref: "A2", pin: "T" }] },
      { name: "N2", class: "ground", nodes: [{ ref: "A1", pin: "L" }, { ref: "A2", pin: "B" }] }
    ],
    constraints: {},
    annotations: []
  };

  const result = compile(model, { render_mode: "explicit" });
  assert.equal(result.ok, true);
  assert.ok(result.layout.placed.some((p) => (p.rotation ?? 0) % 360 !== 0));
});
