// IMP-90 (#90) u12 — vitest coverage for `computeEditModeGates`, the pure
// helper that drives SlideCanvas's mutually-exclusive gesture gating.
// u11 introduced the `EditMode` enum + toolbar; u12 splits the prior
// `isEditMode` shim (which fired ALL gates whenever any edit mode was
// active) into 5 per-gate booleans:
//   textEditing       — designMode + contentEditable (text mode only).
//   imageSelection    — in-iframe user-content image click listener
//                       (image-zone mode only).
//   iframePointerAuto — iframe pointer-events:auto so in-iframe gestures
//                       (text caret OR image click) can reach the doc.
//                       text mode + image-zone mode; structure stays
//                       pe:none because u14 will overlay React controls.
//   zoneGestures      — zone resize 8-handle ring + drag perimeter strips
//                       + canDrag in handleZoneMouseDown
//                       (image-zone mode only).
//   imageOverlay      — React-side image edit overlay (image-zone only).
//
// Mutually-exclusive contract (from the issue body's "discriminated edit
// mode"): no editMode value enables both `textEditing` and either
// `imageSelection` or `zoneGestures` simultaneously. structure mode is
// the no-op placeholder — u14 will plant the structure overlay there.
// pendingLayout fully suppresses every gate (mirrors the existing
// useEffect that forces editMode='off' on pendingLayout entry).
//
// Scope guard: this test exercises the pure helper only — no React
// rendering, no DOM. testing-library/react is NOT in devDependencies
// (verified in Front/package.json); helper-level coverage is the
// established u11 pattern.

import { describe, it, expect } from "vitest";
import {
  computeEditModeGates,
  type EditMode,
  type EditModeGates,
} from "../src/components/SlideCanvas";

const ALL_MODES: EditMode[] = ["off", "text", "structure", "image-zone"];

describe("computeEditModeGates (IMP-90 u12) — pendingLayout suppression", () => {
  it.each<EditMode>(ALL_MODES)(
    "pendingLayout=true forces every gate false (editMode=%s)",
    (mode) => {
      const g = computeEditModeGates(mode, true);
      expect(g).toEqual<EditModeGates>({
        textEditing: false,
        imageSelection: false,
        iframePointerAuto: false,
        zoneGestures: false,
        imageOverlay: false,
      });
    }
  );
});

describe("computeEditModeGates (IMP-90 u12) — off baseline", () => {
  it("editMode=off pendingLayout=false: every gate false", () => {
    expect(computeEditModeGates("off", false)).toEqual<EditModeGates>({
      textEditing: false,
      imageSelection: false,
      iframePointerAuto: false,
      zoneGestures: false,
      imageOverlay: false,
    });
  });
});

describe("computeEditModeGates (IMP-90 u12) — text mode", () => {
  const g = computeEditModeGates("text", false);

  it("textEditing = true (designMode + contentEditable activate)", () => {
    expect(g.textEditing).toBe(true);
  });
  it("iframePointerAuto = true (caret needs to reach the doc)", () => {
    expect(g.iframePointerAuto).toBe(true);
  });
  it("imageSelection = false (no in-iframe image click listener)", () => {
    expect(g.imageSelection).toBe(false);
  });
  it("zoneGestures = false (no zone resize / drag affordances)", () => {
    expect(g.zoneGestures).toBe(false);
  });
  it("imageOverlay = false (no React-side image overlay)", () => {
    expect(g.imageOverlay).toBe(false);
  });
});

describe("computeEditModeGates (IMP-90 u12) — structure mode", () => {
  const g = computeEditModeGates("structure", false);

  // structure mode is the u14 placeholder — no gestures here yet. All five
  // gates stay false so the iframe and React overlays remain quiescent
  // until u14 plants the structure overlay on the React layer.
  it("every gate false (u14 will plant the structure overlay later)", () => {
    expect(g).toEqual<EditModeGates>({
      textEditing: false,
      imageSelection: false,
      iframePointerAuto: false,
      zoneGestures: false,
      imageOverlay: false,
    });
  });
});

describe("computeEditModeGates (IMP-90 u12) — image-zone mode", () => {
  const g = computeEditModeGates("image-zone", false);

  it("textEditing = false (contentEditable would steal image clicks)", () => {
    expect(g.textEditing).toBe(false);
  });
  it("imageSelection = true (in-iframe img click → selectedImageId)", () => {
    expect(g.imageSelection).toBe(true);
  });
  it("iframePointerAuto = true (so image clicks reach the doc)", () => {
    expect(g.iframePointerAuto).toBe(true);
  });
  it("zoneGestures = true (zone resize + drag affordances visible)", () => {
    expect(g.zoneGestures).toBe(true);
  });
  it("imageOverlay = true (React-side overlay renders the drag handles)", () => {
    expect(g.imageOverlay).toBe(true);
  });
});

describe("computeEditModeGates (IMP-90 u12) — mutually exclusive contract", () => {
  it("text mode never co-activates image-zone gates (imageSelection / zoneGestures / imageOverlay)", () => {
    const g = computeEditModeGates("text", false);
    expect(g.textEditing).toBe(true);
    expect(g.imageSelection).toBe(false);
    expect(g.zoneGestures).toBe(false);
    expect(g.imageOverlay).toBe(false);
  });

  it("image-zone mode never co-activates text gates (textEditing)", () => {
    const g = computeEditModeGates("image-zone", false);
    expect(g.imageSelection).toBe(true);
    expect(g.textEditing).toBe(false);
  });

  it.each<EditMode>(ALL_MODES)(
    "for every editMode (%s), textEditing AND zoneGestures are NEVER both true",
    (mode) => {
      const g = computeEditModeGates(mode, false);
      expect(g.textEditing && g.zoneGestures).toBe(false);
    }
  );

  it.each<EditMode>(ALL_MODES)(
    "for every editMode (%s), textEditing AND imageOverlay are NEVER both true",
    (mode) => {
      const g = computeEditModeGates(mode, false);
      expect(g.textEditing && g.imageOverlay).toBe(false);
    }
  );

  it.each<EditMode>(ALL_MODES)(
    "for every editMode (%s), textEditing AND imageSelection are NEVER both true",
    (mode) => {
      const g = computeEditModeGates(mode, false);
      expect(g.textEditing && g.imageSelection).toBe(false);
    }
  );
});

describe("computeEditModeGates (IMP-90 u12) — iframePointerAuto coupling", () => {
  // pe:auto is the iframe-side prerequisite for ANY in-iframe gesture
  // (text caret OR image click). The helper must NOT advertise an
  // in-iframe gate as active while pe is none, or those gestures would
  // be silently swallowed by the wrapper.
  it.each<EditMode>(ALL_MODES)(
    "textEditing → iframePointerAuto (editMode=%s)",
    (mode) => {
      const g = computeEditModeGates(mode, false);
      if (g.textEditing) expect(g.iframePointerAuto).toBe(true);
    }
  );
  it.each<EditMode>(ALL_MODES)(
    "imageSelection → iframePointerAuto (editMode=%s)",
    (mode) => {
      const g = computeEditModeGates(mode, false);
      if (g.imageSelection) expect(g.iframePointerAuto).toBe(true);
    }
  );
});

describe("computeEditModeGates (IMP-90 u12) — referential transparency", () => {
  it("multiple calls with the same inputs return equal output", () => {
    const a = computeEditModeGates("image-zone", false);
    const b = computeEditModeGates("image-zone", false);
    const c = computeEditModeGates("image-zone", false);
    expect(a).toEqual(b);
    expect(b).toEqual(c);
  });

  it("does not mutate captured state across calls (independent invocations)", () => {
    const a = computeEditModeGates("text", false);
    const _b = computeEditModeGates("image-zone", false);
    // a must still reflect text mode after b's call.
    expect(a.textEditing).toBe(true);
    expect(a.imageSelection).toBe(false);
  });
});

describe("computeEditModeGates (IMP-90 u12) — gate truthtable snapshot", () => {
  // Snapshot for human-readable inspection — the per-mode flag layout
  // is the contract u13 (text capture) and u14 (structure overlay)
  // will build against. Any change requires updating both this test
  // AND the consuming gates in SlideCanvas.tsx.
  it("non-pendingLayout truthtable matches the u12 contract", () => {
    const rows = (["off", "text", "structure", "image-zone"] as EditMode[]).map(
      (m) => ({ mode: m, ...computeEditModeGates(m, false) })
    );
    expect(rows).toEqual([
      { mode: "off", textEditing: false, imageSelection: false, iframePointerAuto: false, zoneGestures: false, imageOverlay: false },
      { mode: "text", textEditing: true, imageSelection: false, iframePointerAuto: true, zoneGestures: false, imageOverlay: false },
      { mode: "structure", textEditing: false, imageSelection: false, iframePointerAuto: false, zoneGestures: false, imageOverlay: false },
      { mode: "image-zone", textEditing: false, imageSelection: true, iframePointerAuto: true, zoneGestures: true, imageOverlay: true },
    ]);
  });
});
