import {
  addSensorPoint,
  createSchemeEditorState,
  deleteSensorPoint,
  isSchemeDirty,
  redoSchemeEdit,
  replaceSensorPoint,
  resetSchemeEdit,
  summarizeChanges,
  undoSchemeEdit,
} from "./schemeEditor";
import type { SensorPlacementScheme, SensorPoint } from "./types";

const point = (node_id: string): SensorPoint => ({
  node_id,
  project_x: Number(node_id.slice(1)) * 10,
  project_y: Number(node_id.slice(1)) * 20,
  map_x: 13500000 + Number(node_id.slice(1)) * 10,
  map_y: 3600000 + Number(node_id.slice(1)) * 20,
  longitude: 121,
  latitude: 31,
  elevation: 5,
});

const scheme: SensorPlacementScheme = {
  id: 1,
  scheme_name: "测试方案",
  sensor_number: 2,
  min_diameter: 300,
  username: "alice",
  create_time: "2026-07-30T08:00:00+08:00",
  sensor_location: ["J1", "J2"],
  sensor_points: [point("J1"), point("J2")],
  can_edit: true,
};

describe("scheme editor", () => {
  it("adds unique nodes and can undo", () => {
    const initial = createSchemeEditorState(scheme);
    const added = addSensorPoint(initial, point("J3"));

    expect(added.points.map((item) => item.node_id)).toEqual(["J1", "J2", "J3"]);
    expect(added.statuses.J3).toBe("added");
    expect(isSchemeDirty(added)).toBe(true);
    expect(undoSchemeEdit(added).points).toEqual(initial.points);
  });

  it("can redo an undone edit and clears redo history after a new edit", () => {
    const initial = createSchemeEditorState(scheme);
    const added = addSensorPoint(initial, point("J3"));
    const undone = undoSchemeEdit(added);
    const redone = redoSchemeEdit(undone);

    expect(redone.points.map((item) => item.node_id)).toEqual([
      "J1",
      "J2",
      "J3",
    ]);
    expect(redone.history).toHaveLength(1);
    expect(redone.future).toEqual([]);

    const editedAfterUndo = replaceSensorPoint(undone, "J1", point("J4"));
    expect(editedAfterUndo.future).toEqual([]);
  });

  it("replaces a node without changing row order", () => {
    const initial = createSchemeEditorState(scheme);
    const replaced = replaceSensorPoint(initial, "J1", point("J3"));

    expect(replaced.points.map((item) => item.node_id)).toEqual(["J3", "J2"]);
    expect(replaced.statuses.J3).toBe("replaced");
    expect(summarizeChanges(replaced)).toEqual({
      added: 0,
      removed: 0,
      replaced: 1,
    });
  });

  it("keeps an added status when replacing a newly added node", () => {
    const added = addSensorPoint(createSchemeEditorState(scheme), point("J3"));
    const replaced = replaceSensorPoint(added, "J3", point("J4"));

    expect(replaced.statuses.J3).toBeUndefined();
    expect(replaced.statuses.J4).toBe("added");
  });

  it("rejects duplicate replacements and deleting the final row", () => {
    const initial = createSchemeEditorState(scheme);
    expect(replaceSensorPoint(initial, "J1", point("J2"))).toBe(initial);

    const oneLeft = deleteSensorPoint(initial, "J1");
    expect(deleteSensorPoint(oneLeft, "J2")).toBe(oneLeft);
  });

  it("resets to the loaded baseline", () => {
    const edited = addSensorPoint(createSchemeEditorState(scheme), point("J3"));
    const reset = resetSchemeEdit(edited);

    expect(reset.points.map((item) => item.node_id)).toEqual(["J1", "J2"]);
    expect(isSchemeDirty(reset)).toBe(false);
    expect(reset.history).toEqual([]);
    expect(reset.future).toEqual([]);
  });
});
