/**
 * L4 — behavioural tests for {@link MockLayoutGateway}: it defaults to a single
 * empty leaf, applies mutations and returns the new tree, and keeps a separate
 * in-memory tree per project.
 */
import { describe, it, expect } from "vitest";

import { leaves } from "@/features/layout/layout";
import { MockLayoutGateway } from "./index";

describe("MockLayoutGateway", () => {
  it("loadLayout defaults to a single empty leaf", async () => {
    const gw = new MockLayoutGateway();
    const tree = await gw.loadLayout("p1");
    expect(tree.root.type).toBe("leaf");
    const ls = leaves(tree);
    expect(ls).toHaveLength(1);
    expect(ls[0].session ?? null).toBeNull();
  });

  it("loadLayout returns a stable tree for the same project", async () => {
    const gw = new MockLayoutGateway();
    const a = await gw.loadLayout("p1");
    const b = await gw.loadLayout("p1");
    expect(b).toEqual(a); // same persisted tree (id preserved)
  });

  it("mutateLayout applies the op and returns the mutated tree", async () => {
    const gw = new MockLayoutGateway();
    const initial = await gw.loadLayout("p1");
    const leafId = leaves(initial)[0].id;

    const next = await gw.mutateLayout("p1", {
      type: "split",
      target: leafId,
      direction: "row",
      newLeaf: "b",
      container: "c",
    });
    expect(next.root.type).toBe("split");
    expect(leaves(next)).toHaveLength(2);
  });

  it("persists the mutation in memory (next load reflects it)", async () => {
    const gw = new MockLayoutGateway();
    const initial = await gw.loadLayout("p1");
    const leafId = leaves(initial)[0].id;
    await gw.mutateLayout("p1", {
      type: "setSession",
      target: leafId,
      session: "s9",
    });
    const reloaded = await gw.loadLayout("p1");
    expect(leaves(reloaded)[0].session).toBe("s9");
  });

  it("keeps a separate tree per project", async () => {
    const gw = new MockLayoutGateway();
    const p1 = await gw.loadLayout("p1");
    const p2 = await gw.loadLayout("p2");
    await gw.mutateLayout("p1", {
      type: "split",
      target: leaves(p1)[0].id,
      direction: "row",
      newLeaf: "b",
      container: "c",
    });
    // p2 is untouched.
    const p2After = await gw.loadLayout("p2");
    expect(p2After).toEqual(p2);
    expect(leaves(p2After)).toHaveLength(1);
  });

  it("returns clones, so mutating a returned tree does not corrupt the store", async () => {
    const gw = new MockLayoutGateway();
    const tree = await gw.loadLayout("p1");
    if (tree.root.type === "leaf") tree.root.node.session = "tampered";
    const fresh = await gw.loadLayout("p1");
    expect(leaves(fresh)[0].session ?? null).toBeNull();
  });

  // ── Multi-layout tests (#4) ────────────────────────────────────────────────

  it("listLayouts returns a 'Default' layout on first call", async () => {
    const gw = new MockLayoutGateway();
    const { layouts, activeId } = await gw.listLayouts("p1");
    expect(layouts).toHaveLength(1);
    expect(layouts[0].name).toBe("Default");
    expect(activeId).toBe(layouts[0].id);
  });

  it("createLayout adds a new named layout", async () => {
    const gw = new MockLayoutGateway();
    const { layoutId } = await gw.createLayout("p1", "Beta");
    const { layouts } = await gw.listLayouts("p1");
    expect(layouts).toHaveLength(2);
    expect(layouts.some((l) => l.id === layoutId && l.name === "Beta")).toBe(true);
  });

  it("renameLayout changes the layout name", async () => {
    const gw = new MockLayoutGateway();
    const { layouts } = await gw.listLayouts("p1");
    await gw.renameLayout("p1", layouts[0].id, "Renamed");
    const { layouts: updated } = await gw.listLayouts("p1");
    expect(updated[0].name).toBe("Renamed");
  });

  it("deleteLayout removes a layout and adjusts the active id", async () => {
    const gw = new MockLayoutGateway();
    const { layoutId: secondId } = await gw.createLayout("p1", "Second");
    await gw.setActiveLayout("p1", secondId);
    const { activeId } = await gw.deleteLayout("p1", secondId);
    const { layouts, activeId: newActive } = await gw.listLayouts("p1");
    expect(layouts).toHaveLength(1);
    expect(layouts.some((l) => l.id === secondId)).toBe(false);
    // active should have switched back to the Default.
    expect(activeId).toBe(newActive);
  });

  it("setActiveLayout switches the active layout and returns the authoritative id", async () => {
    const gw = new MockLayoutGateway();
    const { layoutId } = await gw.createLayout("p1", "Alt");
    const result = await gw.setActiveLayout("p1", layoutId);
    expect(result.activeId).toBe(layoutId);
    const { activeId } = await gw.listLayouts("p1");
    expect(activeId).toBe(layoutId);
  });

  it("setActiveLayout self-heals: a stale id keeps (and returns) the current active id", async () => {
    const gw = new MockLayoutGateway();
    const { activeId: initial } = await gw.listLayouts("p1");
    const result = await gw.setActiveLayout("p1", "ghost-layout-id");
    // The requested id does not exist → active id is unchanged and returned.
    expect(result.activeId).toBe(initial);
    const { activeId } = await gw.listLayouts("p1");
    expect(activeId).toBe(initial);
  });

  it("loadLayout with a specific layoutId loads that layout's tree", async () => {
    const gw = new MockLayoutGateway();
    const { layoutId } = await gw.createLayout("p1", "Named");
    // Mutate the named layout.
    const tree = await gw.loadLayout("p1", layoutId);
    const leafId = leaves(tree)[0].id;
    await gw.mutateLayout("p1", { type: "setSession", target: leafId, session: "s-named" }, layoutId);

    // The default (active) layout should be untouched.
    const defaultTree = await gw.loadLayout("p1");
    expect(leaves(defaultTree)[0].session ?? null).toBeNull();

    // The named layout should have the session.
    const namedTree = await gw.loadLayout("p1", layoutId);
    expect(leaves(namedTree)[0].session).toBe("s-named");
  });

  it("setCellAgent persists and clears correctly in applyOperation", async () => {
    const gw = new MockLayoutGateway();
    const tree = await gw.loadLayout("p1");
    const leafId = leaves(tree)[0].id;

    const withAgent = await gw.mutateLayout("p1", {
      type: "setCellAgent",
      target: leafId,
      agent: "agent-99",
    });
    expect(leaves(withAgent)[0].agent).toBe("agent-99");

    const cleared = await gw.mutateLayout("p1", {
      type: "setCellAgent",
      target: leafId,
      agent: null,
    });
    expect(leaves(cleared)[0].agent).toBeUndefined();
  });
});
