import assert from "node:assert/strict";
import test from "node:test";
import { inflateSync } from "node:zlib";
import { readFileSync } from "node:fs";
import { calculate } from "../../app/lib/calculator.ts";
import { calculateLogistics, DEFAULT_LOGISTICS } from "../../app/lib/logistics.ts";
import { buildProductionChain } from "../../app/lib/production-chain.ts";
import { MACHINE_BY_ID, PRODUCT_BY_KEY, RECIPE_BY_ID } from "../../app/lib/factorio-data.ts";
import { DEFAULT_BLUEPRINT_BLOCK_SETTINGS as SETTINGS } from "../../app/lib/blueprint-contract.ts";
import { generateBlueprintPlan, encodeBlueprintString, blueprintNodes } from "./generator.ts";
import { aggregateChainNodes, generateChainBlueprintPlan } from "./chain-generator.ts";
import { previewGeometry, previewBounds } from "./preview-geometry.ts";
import { boundaryLines, planPowerGrid } from "./power-grid.ts";
import { placeFlowLabels } from "./flow-labels.ts";

function nodeFor(recipeId, productKey, machineId, patch = {}, inserterId = "fast-inserter") {
  const machine = MACHINE_BY_ID.get(machineId);
  const input = { recipe: RECIPE_BY_ID.get(recipeId), product: PRODUCT_BY_KEY.get(productKey), machine,
    machineQuality: "normal", inputQuality: "normal", outputQuality: "total", maxQuality: "legendary", calculationMode: "normal",
    machineModules: Array(machine.moduleSlots).fill(null), beaconModules: [null, null], beaconQuality: "normal", beaconCount: 0,
    mode: "output", amount: 1, unit: "s", inputConstraints: [], ...patch };
  const result = calculate(input);
  assert.equal(result.valid, true);
  const logisticsSettings = { ...DEFAULT_LOGISTICS, inserterId };
  return { key: "root", depth: 0, input, result, logisticsSettings, logistics: calculateLogistics(result, input, logisticsSettings) };
}
function sourceFor(node, includeChain = false) {
  return { ...node, ...(includeChain ? { chain: buildProductionChain({ rootInput: node.input, rootResult: node.result, logisticsSettings: node.logisticsSettings }) } : {}) };
}
function noCollisions(plan, includeMarkers = false) {
  const tiles = new Map();
  for (const p of plan.layout.placements.filter((p) => includeMarkers || p.kind !== "link")) {
    assert.ok(Number.isInteger(p.x) && Number.isInteger(p.y), "off-grid footprint");
    for (let y = p.y; y < p.y + p.height; y++) for (let x = p.x; x < p.x + p.width; x++) {
      assert.ok(!tiles.has(`${x},${y}`), `${p.entityId} overlaps ${tiles.get(`${x},${y}`)} at ${x},${y}`);
      tiles.set(`${x},${y}`, p.entityId);
    }
  }
}

function noTilingCollisions(plan) {
  const tiles = new Map();
  for (const [dx, dy] of [[0, 0], [plan.grid.x, 0], [0, plan.grid.y], [plan.grid.x, plan.grid.y]]) {
    for (const p of plan.layout.placements.filter((p) => p.kind !== "link")) {
      const identity = `${p.entityId}:${p.direction}:${p.x + dx}:${p.y + dy}:${p.width}:${p.height}`;
      for (let y = p.y; y < p.y + p.height; y++) for (let x = p.x; x < p.x + p.width; x++) {
        const key = `${x + dx},${y + dy}`;
        if (tiles.has(key)) assert.equal(tiles.get(key), identity, `different entities collide when tiled at ${key}`);
        tiles.set(key, identity);
      }
    }
  }
}

function fixedRoboportPositions(plan, side) {
  const entities = plan.blueprint.blueprint.entities;
  const poles = entities.filter((e) => e.name === "substation");
  const ports = entities.filter((e) => e.name === "roboport");
  assert.equal(ports.length, poles.length, "every Substation must retain its fixed Roboport");
  for (const pole of poles) {
    const port = ports.find((p) => p.position.x === pole.position.x && p.position.y === pole.position.y + (side === "north" ? -3 : 3));
    assert.ok(port, "Roboport moved away from the selected side of its Substation");
    const preview = plan.layout.placements.find((p) => p.entityId === "roboport" && p.x + 2 === port.position.x && p.y + 2 === port.position.y);
    assert.ok(preview, "fixed Roboport position differs between preview and export");
    assert.deepEqual([preview.width, preview.height], [4, 4]);
  }
  return ports.map((p) => p.position);
}

test("all inserter tiers pick from a real input belt or machine and drop on the correct target", async () => {
  for (const orientation of ["horizontal", "vertical"]) for (const id of ["inserter", "fast-inserter", "bulk-inserter", "stack-inserter"]) {
    for (const [recipe, product, machine] of [["iron-gear-wheel", "item:iron-gear-wheel", "assembling-machine-3"], ["plastic-bar", "item:plastic-bar", "chemical-plant"]]) {
      const node = nodeFor(recipe, product, machine, {}, id);
      const plan = generateBlueprintPlan(node, { ...SETTINGS, orientation }, "en");
      noCollisions(plan);
      const encoded = await encodeBlueprintString(plan.blueprintJson);
      const entities = JSON.parse(inflateSync(Buffer.from(encoded.slice(1), "base64"))).blueprint.entities;
      const machines = plan.layout.placements.filter((p) => p.kind === "machine");
      const insideMachine = (x, y) => machines.some((m) => x > m.x && x < m.x + m.width && y > m.y && y < m.y + m.height);
      const onBelt = (x, y) => entities.some((e) => e.name.endsWith("transport-belt") && e.position.x === x && e.position.y === y);
      const inserters = entities.filter((e) => e.name === id);
      assert.ok(inserters.length >= 2);
      let inputs = 0, outputs = 0;
      for (const e of inserters) {
        assert.equal(e.direction, orientation === "vertical" ? 4 : 0);
        const [dx, dy] = { 0: [0, -1], 4: [1, 0], 8: [0, 1], 12: [-1, 0] }[e.direction];
        const { x, y } = e.position;
        if (onBelt(x + dx, y + dy) && insideMachine(x - dx, y - dy)) inputs++;
        else if (insideMachine(x + dx, y + dy) && onBelt(x - dx, y - dy)) outputs++;
        else assert.fail(`${id} has no real pickup/drop target`);
      }
      assert.ok(inputs > 0 && outputs > 0);
    }
  }
});

test("a minimum Substation unit has four corners at full quality reach and shares only boundary entities when tiled", () => {
  const node = nodeFor("iron-gear-wheel", "item:iron-gear-wheel", "assembling-machine-3");
  for (const [quality, reach] of Object.entries({ normal: 18, uncommon: 20, rare: 22, epic: 24, legendary: 28 })) {
    const plan = generateBlueprintPlan(node, { ...SETTINGS, substationPriority: true, substationQuality: quality }, "en");
    const poles = plan.blueprint.blueprint.entities.filter((e) => e.name === "substation");
    assert.equal(poles.length, 4);
    assert.deepEqual(plan.grid, { x: reach, y: reach });
    assert.deepEqual(poles.map((p) => p.position), [{ x: 0, y: 0 }, { x: reach, y: 0 }, { x: 0, y: reach }, { x: reach, y: reach }]);
    assert.equal(plan.blueprint.blueprint.wires.length, 4);
    noCollisions(plan);
    for (const [dx, dy] of [[reach, 0], [0, reach]]) {
      const tiles = new Map();
      for (const [tx, ty] of [[0, 0], [dx, dy]]) for (const p of plan.layout.placements.filter((p) => p.kind !== "link")) {
        const identity = `${p.entityId}:${p.x + tx}:${p.y + ty}`;
        for (let x = p.x; x < p.x + p.width; x++) for (let y = p.y; y < p.y + p.height; y++) {
          const key = `${x + tx},${y + ty}`;
          if (tiles.has(key)) assert.equal(tiles.get(key), identity, "different entities collide across the tile boundary");
          tiles.set(key, identity);
        }
      }
    }
  }
});

function checkFluidNetworks(plan) {
  const entities = plan.blueprint.blueprint.entities.filter((e) => e.name === "pipe" || e.name === "pipe-to-ground");
  const byPosition = new Map(entities.map((e) => [`${e.position.x},${e.position.y}`, e]));
  const labels = new Map(plan.layout.placements.filter((p) => p.tone === "fluid" && p.kind !== "link").map((p) => [`${p.x + .5},${p.y + .5}`, p.label]));
  const graph = new Map(entities.map((e) => [e.entity_number, new Set()]));
  const vectors = { 0: [0, -1], 4: [1, 0], 8: [0, 1], 12: [-1, 0] };
  const ports = (e) => e.name === "pipe" ? [0, 4, 8, 12] : [e.direction ?? 0];
  const label = (e) => labels.get(`${e.position.x},${e.position.y}`);
  const join = (a, b) => {
    assert.equal(label(a), label(b), "different fluids join in the exported geometry");
    graph.get(a.entity_number).add(b.entity_number);
    graph.get(b.entity_number).add(a.entity_number);
  };
  for (const e of entities) {
    for (const direction of ports(e)) {
      const [dx, dy] = vectors[direction];
      const n = byPosition.get(`${e.position.x + dx},${e.position.y + dy}`);
      if (n && ports(n).includes((direction + 8) % 16)) join(e, n);
    }
    if (e.name === "pipe-to-ground") {
      const tunnelDirection = ((e.direction ?? 0) + 8) % 16;
      const [dx, dy] = vectors[tunnelDirection];
      let paired = false;
      for (let d = 1; d <= 11; d++) {
        const n = byPosition.get(`${e.position.x + dx * d},${e.position.y + dy * d}`);
        if (n?.name === "pipe-to-ground" && (n.direction ?? 0) === tunnelDirection) { join(e, n); paired = true; break; }
      }
      assert.equal(paired, true, `unpaired underground pipe ${e.entity_number}`);
    }
  }
  for (const route of plan.fluidRoutes) {
    const starts = route.ports.map((p) => byPosition.get(`${p.x + .5},${p.y + .5}`).entity_number);
    assert.equal(starts.length, plan.machineCountInBlock);
    const visited = new Set(), pending = [starts[0]];
    while (pending.length) { const id = pending.pop(); if (visited.has(id)) continue; visited.add(id); pending.push(...graph.get(id)); }
    assert.ok(starts.every((id) => visited.has(id)), `${route.id} machine connections are not on one network`);
    assert.equal(visited.size, entities.filter((e) => label(e) === route.id).length, `${route.id} has isolated bus segments`);
  }
}

test("mirrored compact oil manifolds are connected, fluid-isolated, collision-free and respect tunnel reach", () => {
  for (const beaconCount of [0, 8]) {
    const node = nodeFor("advanced-oil-processing", "fluid:petroleum-gas", "oil-refinery", { amount: 100, beaconCount,
      beaconModules: [{ family: "speed", tier: 3, quality: "normal" }, { family: "speed", tier: 3, quality: "normal" }] });
    const plan = generateBlueprintPlan(node, { ...SETTINGS, substationPriority: true, substationQuality: "legendary" }, "en");
    noCollisions(plan);
    checkFluidNetworks(plan);
    assert.equal(plan.beaconCoverageComplete, true);
  }
});

test("basic oil keeps the explicit second input and third output fluid boxes", () => {
  const node = nodeFor("basic-oil-processing", "fluid:petroleum-gas", "oil-refinery");
  const plan = generateBlueprintPlan(node, SETTINGS, "en");
  const machine = plan.layout.placements.find((p) => p.kind === "machine");
  const input = plan.fluidRoutes.find((r) => r.direction === "input");
  const output = plan.fluidRoutes.find((r) => r.direction === "output");
  assert.equal(input.ports[0].x, machine.x + 3);
  assert.equal(output.ports[0].x, machine.x + 4);
  checkFluidNetworks(plan);
});

test("both alignment axes move exported machines within a full-reach grid and Roboports touch the poles", async () => {
  const node = nodeFor("iron-gear-wheel", "item:iron-gear-wheel", "assembling-machine-3");
  for (const orientation of ["horizontal", "vertical"]) for (const [quality, reach] of Object.entries({ normal: 18, legendary: 28 })) for (const roboportSide of ["north", "south"]) {
    const positions = new Map();
    const strings = new Set();
    let fixedPorts;
    for (const horizontalAlignment of ["left", "center", "right"]) for (const verticalAlignment of ["top", "center", "bottom"]) {
      const settings = { ...SETTINGS, orientation, substationPriority: true, substationQuality: quality,
        roboportCoverage: true, roboportSide, horizontalAlignment, verticalAlignment };
      const plan = generateBlueprintPlan(node, settings, "en");
      const withoutPorts = generateBlueprintPlan(node, { ...settings, roboportCoverage: false }, "en");
      assert.deepEqual(plan.blueprint.blueprint.entities.filter((e) => e.name === "substation"), withoutPorts.blueprint.blueprint.entities.filter((e) => e.name === "substation"), "Substation positions are the fixed alignment reference");
      const ports = fixedRoboportPositions(plan, roboportSide);
      fixedPorts ??= ports;
      assert.deepEqual(ports, fixedPorts, "alignment must never move a Roboport");
      noCollisions(plan, true);
      noTilingCollisions(plan);
      assert.deepEqual(plan.grid, { x: reach, y: reach });
      assert.equal(plan.roboportCoverageComplete, true);
      const encoded = await encodeBlueprintString(plan.blueprintJson);
      strings.add(encoded);
      const entities = JSON.parse(inflateSync(Buffer.from(encoded.slice(1), "base64"))).blueprint.entities;
      const poles = entities.filter((e) => e.name === "substation");
      for (const port of entities.filter((e) => e.name === "roboport")) {
        assert.ok(poles.some((pole) => pole.position.x === port.position.x && Math.abs(pole.position.y - port.position.y) === 3), "a 4-tile Roboport must touch a 2-tile Substation with no empty tile");
      }
      const machine = entities.find((e) => e.name === node.input.machine.id);
      const leftBoundary = Math.min(...poles.map((p) => p.position.x)) + 1;
      const rightBoundary = Math.max(...poles.map((p) => p.position.x)) - 1;
      const topBoundary = Math.min(...poles.map((p) => p.position.y)) + 1;
      const bottomBoundary = Math.max(...poles.map((p) => p.position.y)) - 1;
      assert.ok(machine.position.x - node.input.machine.width / 2 >= leftBoundary);
      assert.ok(machine.position.x + node.input.machine.width / 2 <= rightBoundary);
      assert.ok(machine.position.y - node.input.machine.height / 2 >= topBoundary);
      assert.ok(machine.position.y + node.input.machine.height / 2 <= bottomBoundary);
      if (horizontalAlignment === "center" && verticalAlignment === "bottom") {
        assert.equal(machine.position.y + node.input.machine.height / 2, bottomBoundary, "center/bottom must use the upper edge of the bottom Substations");
      }
      positions.set(`${horizontalAlignment}:${verticalAlignment}`, machine.position);
    }
    assert.equal(strings.size, 9, "every alignment must regenerate the exported positions");
    for (const v of ["top", "center", "bottom"]) {
      assert.ok(positions.get(`left:${v}`).x < positions.get(`center:${v}`).x);
      assert.ok(positions.get(`center:${v}`).x < positions.get(`right:${v}`).x);
      assert.ok(Math.abs(positions.get(`center:${v}`).x - reach / 2) <= 0.5, "center the actual machine within tile precision");
    }
    for (const h of ["left", "center", "right"]) {
      assert.ok(positions.get(`${h}:top`).y < positions.get(`${h}:center`).y);
      assert.ok(positions.get(`${h}:center`).y < positions.get(`${h}:bottom`).y);
      assert.ok(Math.abs(positions.get(`${h}:center`).y - reach / 2) <= 0.5, "center only within the Substation grid, regardless of Roboport side");
    }
  }
});

test("aligned refinery blocks keep isolated fluid networks, Beacon coverage and collision-free flow markers", () => {
  const node = nodeFor("advanced-oil-processing", "fluid:petroleum-gas", "oil-refinery", { amount: 100, beaconCount: 8,
    beaconModules: [{ family: "speed", tier: 3, quality: "normal" }, { family: "speed", tier: 3, quality: "normal" }] });
  for (const orientation of ["horizontal", "vertical"]) for (const [horizontalAlignment, verticalAlignment] of [["left", "top"], ["center", "top"], ["center", "center"], ["center", "bottom"], ["right", "bottom"]]) {
    const plan = generateBlueprintPlan(node, { ...SETTINGS, orientation, substationPriority: true, substationQuality: "legendary",
      roboportCoverage: true, horizontalAlignment, verticalAlignment }, "en");
    noCollisions(plan, true);
    noTilingCollisions(plan);
    checkFluidNetworks(plan);
    assert.equal(plan.beaconCoverageComplete, true);
    assert.equal(plan.substationCoverageComplete, true);
    const machines = plan.layout.placements.filter((p) => p.kind === "machine");
    if (horizontalAlignment === "center") {
      const center = (Math.min(...machines.map((m) => m.x)) + Math.max(...machines.map((m) => m.x + m.width))) / 2;
      assert.ok(Math.abs(center - plan.grid.x / 2) <= .5, "top/bottom alignment must never move a centered production row sideways");
    }
    const markers = plan.layout.placements.filter((p) => p.kind === "link");
    assert.equal(markers.filter((p) => p.flowDirection === "input").length, 2);
    assert.equal(markers.filter((p) => p.flowDirection === "output").length, 3);
    assert.ok(markers.every((p) => p.productType === "fluid" && p.productId && !p.label));
  }
});

test("edge alignment uses the inward Substation faces as hard machine boundaries, independently of labels", () => {
  const node = nodeFor("iron-gear-wheel", "item:iron-gear-wheel", "assembling-machine-3");
  for (const [horizontalAlignment, verticalAlignment] of [["left", "center"], ["right", "center"], ["center", "top"], ["center", "bottom"]]) {
    const settings = { ...SETTINGS, substationPriority: true, substationQuality: "legendary", roboportCoverage: true, horizontalAlignment, verticalAlignment };
    const plan = generateBlueprintPlan(node, settings, "en");
    const machine = plan.layout.placements.find((p) => p.kind === "machine");
    if (horizontalAlignment === "left") assert.equal(machine.x, 1);
    if (horizontalAlignment === "right") assert.equal(machine.x + machine.width, plan.grid.x - 1);
    if (verticalAlignment === "top") assert.equal(machine.y, 1);
    if (verticalAlignment === "bottom") assert.equal(machine.y + machine.height, plan.grid.y - 1);
    const physical = plan.layout.placements.filter((p) => p.kind !== "link" && p.tone !== "power");
    const frame = planPowerGrid(physical, settings);
    const label = { kind: "link", x: machine.x, y: machine.y, width: 40, height: 3, flowDirection: "input", productId: "iron-plate" };
    assert.deepEqual(planPowerGrid([...physical, label], settings), frame, "even a large overlapping label cannot alter alignment or period");
    const placements = structuredClone([...physical, ...frame.placements, label]);
    const before = structuredClone(placements.filter((p) => p.kind !== "link"));
    placeFlowLabels(placements);
    assert.deepEqual(placements.filter((p) => p.kind !== "link"), before);
    noCollisions({ layout: { placements } }, true);
  }
});

test("a fixed Roboport's first facing edge replaces a blocked alignment limit on all four sides", () => {
  const settings = { ...SETTINGS, substationPriority: true, substationQuality: "legendary", roboportCoverage: true };
  // A row/column spanning two full-reach cells crosses the middle support
  // column/row. Its centered axis cannot be sidestepped to evade a Roboport.
  for (const roboportSide of ["north", "south"]) {
    const row = [0, 24, 48].map((x) => ({ kind: "machine", entityId: "assembling-machine-3", x, y: 0, width: 3, height: 3 }));
    for (const verticalAlignment of ["top", "bottom"]) {
      const frame = planPowerGrid(row, { ...settings, roboportSide, horizontalAlignment: "center", verticalAlignment });
      assert.equal(frame.complete, true);
      assert.deepEqual(frame.grid, { x: 56, y: 28 });
      const moved = row.map((m) => ({ ...m, x: m.x + frame.translation.x, y: m.y + frame.translation.y }));
      const bounds = boundaryLines(moved);
      assert.ok(Math.abs((bounds.left + bounds.right) / 2 - 28) <= .5, "do not move a centered row sideways");
      const bottomPorts = frame.placements.filter((p) => p.entityId === "roboport" && p.y > 14);
      const topPorts = frame.placements.filter((p) => p.entityId === "roboport" && p.y < 14);
      if (verticalAlignment === "bottom") assert.equal(bounds.bottom, roboportSide === "north" ? bottomPorts[0].y : 27);
      else assert.equal(bounds.top, roboportSide === "south" ? topPorts[0].y + 4 : 1);
      noCollisions({ layout: { placements: [...moved, ...frame.placements] } });
      noTilingCollisions({ layout: { placements: [...moved, ...frame.placements] }, grid: frame.grid });
    }
    const column = [0, 22, 44].map((y) => ({ kind: "machine", entityId: "recycler", x: 0, y, width: 2, height: 4 }));
    for (const horizontalAlignment of ["left", "right"]) {
      const frame = planPowerGrid(column, { ...settings, roboportSide, horizontalAlignment, verticalAlignment: "center" });
      assert.equal(frame.complete, true);
      assert.deepEqual(frame.grid, { x: 28, y: 56 });
      const moved = column.map((m) => ({ ...m, x: m.x + frame.translation.x, y: m.y + frame.translation.y }));
      const bounds = boundaryLines(moved);
      assert.equal((bounds.top + bounds.bottom) / 2, 28, "do not move a centered column vertically");
      if (horizontalAlignment === "left") assert.equal(bounds.left, 2, "stop at the right edge of the left Roboports");
      else assert.equal(bounds.right, 26, "stop at the left edge of the right Roboports");
      noCollisions({ layout: { placements: [...moved, ...frame.placements] } });
      noTilingCollisions({ layout: { placements: [...moved, ...frame.placements] }, grid: frame.grid });
    }
  }
});

test("belts and splitters end at machine-row edges and always export normal quality", () => {
  const node = nodeFor("iron-gear-wheel", "item:iron-gear-wheel", "assembling-machine-3", { amount: 50, beaconCount: 4 });
  for (const orientation of ["horizontal", "vertical"]) for (const autoUpgradeBelt of [false, true]) {
    const plan = generateBlueprintPlan(node, { ...SETTINGS, orientation, autoUpgradeBelt, beltQuality: "legendary" }, "en");
    const machines = plan.layout.placements.filter((p) => p.kind === "machine");
    const axis = orientation === "vertical" ? "y" : "x", size = orientation === "vertical" ? "height" : "width";
    const start = Math.min(...machines.map((p) => p[axis])), end = Math.max(...machines.map((p) => p[axis] + p[size]));
    const transport = plan.layout.placements.filter((p) => /(?:transport-belt|underground-belt|splitter)$/.test(p.entityId));
    assert.ok(transport.length > 0);
    assert.ok(transport.every((p) => p[axis] >= start && p[axis] + p[size] <= end), "transport extends past a machine-row edge");
    assert.equal(Math.min(...transport.map((p) => p[axis])), start);
    assert.equal(Math.max(...transport.map((p) => p[axis] + p[size])), end);
    const exports = plan.blueprint.blueprint.entities.filter((e) => /(?:transport-belt|underground-belt|splitter)$/.test(e.name));
    assert.ok(exports.every((e) => !e.quality || e.quality === "normal"), "legacy belt quality must not enter the export");
    assert.ok(exports.every((e) => (orientation === "vertical" ? [0, 8] : [4, 12]).includes(e.direction)), "belt and splitter directions must follow the chosen orientation");
    for (const p of transport.filter((p) => p.entityId.endsWith("splitter"))) {
      assert.deepEqual([p.width, p.height], orientation === "vertical" ? [2, 1] : [1, 2]);
    }
    noCollisions(plan, true);
  }
  assert.equal("beltQuality" in SETTINGS, false);
  for (const path of ["./production-geometry.tsx", "../../app/components/advanced-panels.tsx"]) {
    assert.ok(!readFileSync(new URL(path, import.meta.url), "utf8").includes("beltQuality"), "belt quality remains configurable in a calculator panel");
  }
});

test("aligned process chains remain collision-free across adjacent block boundaries", () => {
  const source = sourceFor(nodeFor("advanced-circuit", "item:advanced-circuit", "assembling-machine-3", { amount: 7, beaconCount: 8 }), true);
  for (const roboportSide of ["north", "south"]) for (const orientation of ["horizontal", "vertical"]) for (const [horizontalAlignment, verticalAlignment] of [["left", "top"], ["center", "center"], ["center", "bottom"], ["right", "bottom"]]) {
    const plan = generateChainBlueprintPlan(source, { ...SETTINGS, orientation, includeChain: true, substationPriority: true,
      substationQuality: "legendary", roboportCoverage: true, roboportSide, horizontalAlignment, verticalAlignment }, "en");
    noCollisions(plan, true);
    noTilingCollisions(plan);
    fixedRoboportPositions(plan, roboportSide);
    assert.equal(plan.substationCoverageComplete, true);
    assert.equal(plan.roboportCoverageComplete, true);
    for (const route of plan.fluidRoutes) {
      assert.equal(route.axis, orientation);
      assert.ok(plan.layout.placements.some((p) => p.tone === "fluid" && p.kind !== "link" && p.label === route.id && p[orientation === "vertical" ? "x" : "y"] === route.lane), "translated fluid lanes must match the merged preview");
      assert.ok(route.ports.every((port) => plan.layout.placements.some((p) => p.tone === "fluid" && p.kind !== "link" && p.label === route.id && p.x === port.x && p.y === port.y)), "chain fluid ports must stay attached after rotation and packing");
    }
  }
});

test("vertical refineries preserve their mirrored recipe ports and connected underground manifolds", async () => {
  const node = nodeFor("advanced-oil-processing", "fluid:petroleum-gas", "oil-refinery", { amount: 100 });
  const plan = generateBlueprintPlan(node, { ...SETTINGS, orientation: "vertical" }, "en");
  noCollisions(plan, true);
  checkFluidNetworks(plan);
  const encoded = await encodeBlueprintString(plan.blueprintJson);
  const exported = JSON.parse(inflateSync(Buffer.from(encoded.slice(1), "base64"))).blueprint;
  const machines = exported.entities.filter((e) => e.name === "oil-refinery");
  assert.ok(machines.some((m) => m.mirror));
  for (const [index, machine] of machines.entries()) {
    assert.equal(machine.direction, 4);
    const x = machine.position.x - 2.5, y = machine.position.y - 2.5;
    const mirror = (offset) => machine.mirror ? 4 - offset : offset;
    assert.deepEqual(plan.fluidRoutes.filter((r) => r.direction === "input").map((r) => r.ports[index]), [
      { x: x - 1, y: y + mirror(1) }, { x: x - 1, y: y + mirror(3) },
    ]);
    assert.deepEqual(plan.fluidRoutes.filter((r) => r.direction === "output").map((r) => r.ports[index]), [
      { x: x + 5, y: y + mirror(0) }, { x: x + 5, y: y + mirror(2) }, { x: x + 5, y: y + mirror(4) },
    ]);
  }
  const geometry = previewGeometry(plan.layout.placements);
  for (const route of plan.fluidRoutes) {
    assert.equal(route.axis, "vertical");
    assert.ok(geometry.tunnels.some(({ from, to }) => from.label === route.id && from.x === route.lane && to.x === route.lane && from.y !== to.y), "vertical fluid manifolds must also render vertical underground spans");
  }
  assert.deepEqual(exported["snap-to-grid"], plan.grid);
  assert.ok(plan.layout.placements.filter((p) => p.kind === "link").every((p) => p.width === 3 && p.height === 1));
});

test("rectangular machines swap their tile footprint before applying vertical blueprint alignment", () => {
  const node = nodeFor("metallic-asteroid-crushing", "item:iron-ore", "crusher");
  for (const [horizontalAlignment, verticalAlignment] of [["left", "top"], ["center", "bottom"], ["right", "center"]]) {
    const plan = generateBlueprintPlan(node, { ...SETTINGS, orientation: "vertical", substationPriority: true, substationQuality: "legendary", horizontalAlignment, verticalAlignment }, "en");
    noCollisions(plan, true);
    const machine = plan.layout.placements.find((p) => p.kind === "machine");
    assert.deepEqual([machine.width, machine.height], [3, 2]);
    assert.equal(machine.direction, "east");
    const exported = plan.blueprint.blueprint.entities.find((e) => e.name === "crusher");
    assert.deepEqual(exported.position, { x: machine.x + 1.5, y: machine.y + 1 });
    assert.equal(exported.direction, 4);
    if (horizontalAlignment === "left") assert.equal(machine.x, 1);
    if (horizontalAlignment === "right") assert.equal(machine.x + machine.width, plan.grid.x - 1);
    if (verticalAlignment === "top") assert.equal(machine.y, 1);
    if (verticalAlignment === "bottom") assert.equal(machine.y + machine.height, plan.grid.y - 1);
  }
});

test("only the current product's shared sub-chains sum demand and keep different configurations separate", () => {
  const root = nodeFor("advanced-circuit", "item:advanced-circuit", "assembling-machine-3");
  const source = sourceFor(root, true);
  const combined = aggregateChainNodes(source);
  const shared = combined.find((node) => node.input.product.id === "copper-cable" && node.sources === 2);
  assert.ok(shared, "shared copper cable process missing");
  const rates = blueprintNodes(source.input, source.result, source.logistics, source.logisticsSettings, source.chain).filter((n) => n.input.product.id === "copper-cable").reduce((sum, n) => sum + n.result.ratePerSecond, 0);
  assert.ok(Math.abs(shared.result.ratePerSecond - rates) < 1e-9);
  const low = nodeFor("copper-cable", "item:copper-cable", "assembling-machine-3", { amount: 0.8 });
  const other = nodeFor("copper-cable", "item:copper-cable", "assembling-machine-3", { amount: 0.9 });
  const withChildren = (...children) => ({ ...source, chain: { ...source.chain, root: { ...source.chain.root,
    inputs: children.map((node, i) => ({ ...sourceFor(node, true).chain.root, key: `branch-${i}`, depth: 1, inputs: [] })),
  } } });
  const merged = aggregateChainNodes(withChildren(low, other)).filter((n) => n.input.product.id === "copper-cable");
  assert.equal(merged.length, 1);
  assert.equal(merged[0].result.machinesToBuild, 1, "round only after adding demand");
  const quality = nodeFor("copper-cable", "item:copper-cable", "assembling-machine-3", { machineQuality: "legendary" });
  assert.equal(aggregateChainNodes(withChildren(low, quality)).filter((n) => n.input.product.id === "copper-cable").length, 2);
  assert.equal(aggregateChainNodes(sourceFor(low)).length, 1, "disabled chains must not be added implicitly");
  const unrelated = sourceFor(nodeFor("engine-unit", "item:engine-unit", "assembling-machine-3"), true);
  assert.deepEqual(aggregateChainNodes({ ...source, factorySnapshots: [source, unrelated] }), combined, "unrelated targets cannot enter this generator");
});

test("chain export includes its full demand and final partial blocks, preserving each machine's product and flow icons", async () => {
  const nodes = [nodeFor("advanced-circuit", "item:advanced-circuit", "assembling-machine-3", { amount: 7 }),
    nodeFor("advanced-oil-processing", "fluid:petroleum-gas", "oil-refinery", { amount: 700, beaconCount: 8, beaconModules: [{ family: "speed", tier: 3, quality: "normal" }, { family: "speed", tier: 3, quality: "normal" }] })];
  for (const root of nodes) for (const substationPriority of [false, true]) {
    const source = sourceFor(root, true);
    const plan = generateChainBlueprintPlan(source, { ...SETTINGS, includeChain: true, substationPriority }, "en");
    noCollisions(plan, true);
    const expected = aggregateChainNodes(source);
    for (const node of expected) {
      const machines = plan.blueprint.blueprint.entities.filter((e) => e.name === node.input.machine.id && e.recipe === node.input.recipe.id);
      assert.equal(machines.length, node.result.machinesToBuild);
    }
    for (const p of plan.layout.placements.filter((p) => p.kind === "machine")) {
      const machine = plan.blueprint.blueprint.entities.find((e) => e.position.x === p.x + p.width / 2 && e.position.y === p.y + p.height / 2);
      assert.ok(RECIPE_BY_ID.get(machine.recipe).products.some((product) => product.name === p.productId && product.type === p.productType), "machine icon must match its own recipe output");
    }
    const markers = plan.layout.placements.filter((p) => p.kind === "link");
    assert.ok(markers.some((p) => p.flowDirection === "input"));
    assert.ok(markers.some((p) => p.flowDirection === "output"));
    assert.ok(markers.every((p) => p.productId && p.productType && !p.label));
    assert.equal(plan.blueprint.blueprint.icons[0].signal.name, root.input.product.id);
    assert.ok(plan.blueprint.blueprint.label.includes(root.input.product.name));
    assert.equal(plan.machineCountInBlock, plan.layout.placements.filter((p) => p.kind === "machine").length);
    assert.equal(plan.repeatCount, 1);
    assert.ok(plan.warnings.some((w) => w.code === "chain-connections"));
    const encoded = await encodeBlueprintString(plan.blueprintJson);
    assert.deepEqual(JSON.parse(inflateSync(Buffer.from(encoded.slice(1), "base64"))), plan.blueprint);
    const free = generateChainBlueprintPlan(source, { ...SETTINGS, includeChain: true, snapToGrid: false, substationPriority }, "en");
    assert.equal("snap-to-grid" in free.blueprint.blueprint, false);
  }
});

test("preview grid stays integer-aligned and contiguous belts recognize straight and corner connections", () => {
  assert.deepEqual(previewBounds({ minX: -1, maxX: 29, minY: -1, maxY: 29 }), { x: -2, y: -2, width: 32, height: 32 });
  const belts = [[0, 0, "east"], [1, 0, "east"], [2, 0, "south"], [2, 1, "south"]].map(([x, y, direction]) => ({ kind: "entity", entityId: "transport-belt", width: 1, height: 1, x, y, direction }));
  const { connections } = previewGeometry(belts);
  assert.deepEqual(connections(belts[1]), ["west"]);
  assert.deepEqual(connections(belts[2]), ["west"]);
  assert.deepEqual(connections(belts[3]), ["north"]);
  const ui = readFileSync(new URL("./production-geometry.tsx", import.meta.url), "utf8");
  assert.ok(!ui.includes("First generator version:"));
  assert.ok(ui.includes("settings.includeChain"));
  assert.ok(!ui.includes("factorySnapshots"));
  assert.ok(ui.includes("disabled={!blueprintString}"), "do not copy stale JSON while encoding");
});
