import test from "node:test";
import assert from "node:assert/strict";
import fixture from "../frontend/sample.schemeta.json" with { type: "json" };
import { createSchemetaStore } from "../frontend-react/src/state/store.js";

function baseModel() {
  return {
    ...fixture,
    instances: fixture.instances.map((instance) => ({
      ...instance,
      placement: {
        ...instance.placement
      }
    }))
  };
}

test("setSelection normalizes and sorts refs deterministically", () => {
  const store = createSchemetaStore();

  store.actions.setSelection({
    selectedRefs: ["U7", "U1", "U2", "U1", "U3"],
    selectedNet: "I2C_SCL",
    selectedPin: { ref: "U1", pin: "GPIO9" }
  });

  const { selection } = store.getState();
  assert.deepEqual(selection.selectedRefs, ["U1", "U2", "U3", "U7"]);
  assert.equal(selection.selectedNet, "I2C_SCL");
  assert.deepEqual(selection.selectedPin, { ref: "U1", pin: "GPIO9" });
});

test("moveComponent only updates targeted instance and is deterministic", () => {
  const model = baseModel();

  const storeA = createSchemetaStore();
  const storeB = createSchemetaStore();

  storeA.actions.setModel(model);
  storeA.actions.moveComponent("U1", { x: 121.7, y: 333.2 });

  storeB.actions.setModel(model);
  storeB.actions.moveComponent("U1", { x: 121.7, y: 333.2 });

  const stateA = storeA.getState();
  const stateB = storeB.getState();

  assert.deepEqual(stateA.model, stateB.model);

  const moved = stateA.model.instances.find((x) => x.ref === "U1");
  assert.equal(moved.placement.x, 122);
  assert.equal(moved.placement.y, 333);

  const untouched = stateA.model.instances.find((x) => x.ref === "U2");
  assert.equal(untouched.placement.x, null);
  assert.equal(untouched.placement.y, null);
});

test("applyJsonText updates model on valid JSON and clears jsonError", () => {
  const store = createSchemetaStore({
    uiFlags: { jsonError: "Old error" }
  });

  const payload = JSON.stringify(baseModel());
  const result = store.actions.applyJsonText(payload);

  assert.equal(result.ok, true);
  assert.equal(store.getState().uiFlags.jsonError, null);
  assert.equal(store.getState().model.instances.length > 0, true);
});

test("applyJsonText reports parse errors without mutating model", () => {
  const store = createSchemetaStore();
  store.actions.setModel(baseModel());

  const before = store.getState().model;
  const result = store.actions.applyJsonText("{ bad json }");

  assert.equal(result.ok, false);
  assert.equal(typeof result.error, "string");
  assert.equal(store.getState().uiFlags.jsonError.length > 0, true);
  assert.deepEqual(store.getState().model, before);
});

test("undo/redo restores deterministic snapshots", () => {
  const store = createSchemetaStore();
  store.actions.setModel(baseModel());
  store.actions.setSelection({ selectedRefs: ["U3", "U1"] });

  const beforeMove = store.getState().model;
  store.actions.moveComponent("U1", { x: 80, y: 60 });

  assert.equal(store.getState().history.past.length >= 3, true);

  store.actions.undo();
  assert.deepEqual(store.getState().model, beforeMove);

  store.actions.redo();
  const moved = store.getState().model.instances.find((x) => x.ref === "U1");
  assert.equal(moved.placement.x, 80);
  assert.equal(moved.placement.y, 60);
});

test("transaction scaffold groups multiple actions into one undo step", () => {
  const store = createSchemetaStore();
  store.actions.setModel(baseModel());

  const pastBefore = store.getState().history.past.length;
  store.actions.beginTransaction("drag-and-select");
  store.actions.moveComponent("U1", { x: 240, y: 140 });
  store.actions.setSelection({ selectedRefs: ["U2", "U1"] });
  store.actions.commitTransaction();

  assert.equal(store.getState().history.past.length, pastBefore + 1);
  assert.deepEqual(store.getState().selection.selectedRefs, ["U1", "U2"]);

  store.actions.undo();
  assert.deepEqual(store.getState().selection.selectedRefs, []);
  const u1 = store.getState().model.instances.find((x) => x.ref === "U1");
  assert.equal(u1.placement.x, null);
  assert.equal(u1.placement.y, null);
});

test("compile lifecycle state transitions are deterministic", () => {
  const storeA = createSchemetaStore();
  const storeB = createSchemetaStore();

  storeA.actions.beginCompile("Compile");
  storeA.actions.failCompile("Compile", "Bad payload");
  storeA.actions.beginCompile("Analyze");
  storeA.actions.completeCompile("Analyze");

  storeB.actions.beginCompile("Compile");
  storeB.actions.failCompile("Compile", "Bad payload");
  storeB.actions.beginCompile("Analyze");
  storeB.actions.completeCompile("Analyze");

  assert.deepEqual(storeA.getState().lifecycle, storeB.getState().lifecycle);
  assert.equal(storeA.getState().lifecycle.isCompiling, false);
  assert.equal(storeA.getState().lifecycle.lastError, null);
  assert.equal(storeA.getState().lifecycle.lastAction, "Analyze");
});

test("compile lifecycle updates do not alter undo/redo history", () => {
  const store = createSchemetaStore();
  store.actions.setModel(baseModel());
  store.actions.moveComponent("U1", { x: 90, y: 70 });

  const beforeUndo = store.getState();
  const pastBeforeLifecycle = beforeUndo.history.past.length;

  store.actions.beginCompile("Compile");
  store.actions.failCompile("Compile", "Synthetic error");
  store.actions.beginCompile("Compile");
  store.actions.completeCompile("Compile");

  assert.equal(store.getState().history.past.length, pastBeforeLifecycle);
  assert.equal(store.getState().lifecycle.lastAction, "Compile");
  assert.equal(store.getState().lifecycle.lastError, null);

  store.actions.undo();
  const u1 = store.getState().model.instances.find((x) => x.ref === "U1");
  assert.equal(u1.placement.x, null);
  assert.equal(u1.placement.y, null);
});
