// OrderDraftStore unit tests under JSDOM with `fake-indexeddb`
// standing in for the browser's IndexedDB factory. The store is
// driven directly with a real `IDBCache` so persistence is exercised
// the same way it would be inside the in-game shell layout.
//
// Each case opens a freshly named database so state cannot leak
// across tests; per-game isolation is verified explicitly by mixing
// drafts under different `gameId`s through one shared cache.

import "@testing-library/jest-dom/vitest";
import "fake-indexeddb/auto";
import { waitFor } from "@testing-library/svelte";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import type { IDBPDatabase } from "idb";

import { IDBCache } from "../src/platform/store/idb-cache";
import { openGalaxyDB, type GalaxyDB } from "../src/platform/store/idb";
import type { Cache } from "../src/platform/store/index";
import { OrderDraftStore } from "../src/sync/order-draft.svelte";
import type { OrderCommand } from "../src/sync/order-types";

let db: IDBPDatabase<GalaxyDB>;
let dbName: string;
let cache: Cache;

beforeEach(async () => {
	dbName = `galaxy-order-draft-test-${crypto.randomUUID()}`;
	db = await openGalaxyDB(dbName);
	cache = new IDBCache(db);
});

afterEach(async () => {
	db.close();
	await new Promise<void>((resolve) => {
		const req = indexedDB.deleteDatabase(dbName);
		req.onsuccess = () => resolve();
		req.onerror = () => resolve();
		req.onblocked = () => resolve();
	});
});

const GAME_ID = "11111111-2222-3333-4444-555555555555";

function placeholder(id: string, label: string): OrderCommand {
	return { kind: "placeholder", id, label };
}

describe("OrderDraftStore", () => {
	test("init on empty cache yields ready status with no commands", async () => {
		const store = new OrderDraftStore();
		expect(store.status).toBe("idle");
		await store.init({ cache, gameId: GAME_ID });
		expect(store.status).toBe("ready");
		expect(store.commands).toEqual([]);
		store.dispose();
	});

	test("add appends commands and persists across instances", async () => {
		const a = new OrderDraftStore();
		await a.init({ cache, gameId: GAME_ID });
		await a.add(placeholder("c1", "first"));
		await a.add(placeholder("c2", "second"));
		expect(a.commands.map((c) => c.id)).toEqual(["c1", "c2"]);
		a.dispose();

		const b = new OrderDraftStore();
		await b.init({ cache, gameId: GAME_ID });
		expect(b.commands.map((c) => c.id)).toEqual(["c1", "c2"]);
		expect(b.commands[1]).toEqual(placeholder("c2", "second"));
		b.dispose();
	});

	test("remove drops the matching command and persists the removal", async () => {
		const a = new OrderDraftStore();
		await a.init({ cache, gameId: GAME_ID });
		await a.add(placeholder("c1", "first"));
		await a.add(placeholder("c2", "second"));
		await a.add(placeholder("c3", "third"));
		await a.remove("c2");
		expect(a.commands.map((c) => c.id)).toEqual(["c1", "c3"]);
		a.dispose();

		const b = new OrderDraftStore();
		await b.init({ cache, gameId: GAME_ID });
		expect(b.commands.map((c) => c.id)).toEqual(["c1", "c3"]);
		b.dispose();
	});

	test("remove on a missing id is a silent no-op", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add(placeholder("c1", "first"));
		await store.remove("absent");
		expect(store.commands.map((c) => c.id)).toEqual(["c1"]);
		store.dispose();
	});

	test("move reorders the commands and persists the new order", async () => {
		const a = new OrderDraftStore();
		await a.init({ cache, gameId: GAME_ID });
		await a.add(placeholder("c1", "first"));
		await a.add(placeholder("c2", "second"));
		await a.add(placeholder("c3", "third"));
		await a.move(0, 2);
		expect(a.commands.map((c) => c.id)).toEqual(["c2", "c3", "c1"]);
		a.dispose();

		const b = new OrderDraftStore();
		await b.init({ cache, gameId: GAME_ID });
		expect(b.commands.map((c) => c.id)).toEqual(["c2", "c3", "c1"]);
		b.dispose();
	});

	test("move with out-of-range or identical indices is a no-op", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add(placeholder("c1", "first"));
		await store.add(placeholder("c2", "second"));
		await store.move(1, 1);
		await store.move(-1, 0);
		await store.move(0, 5);
		expect(store.commands.map((c) => c.id)).toEqual(["c1", "c2"]);
		store.dispose();
	});

	test("drafts under different game ids do not bleed through one cache", async () => {
		const otherGame = "99999999-9999-9999-9999-999999999999";

		const a = new OrderDraftStore();
		await a.init({ cache, gameId: GAME_ID });
		await a.add(placeholder("a1", "from-a"));
		a.dispose();

		const b = new OrderDraftStore();
		await b.init({ cache, gameId: otherGame });
		expect(b.commands).toEqual([]);
		await b.add(placeholder("b1", "from-b"));
		b.dispose();

		const reloadA = new OrderDraftStore();
		await reloadA.init({ cache, gameId: GAME_ID });
		expect(reloadA.commands.map((c) => c.id)).toEqual(["a1"]);
		reloadA.dispose();

		const reloadB = new OrderDraftStore();
		await reloadB.init({ cache, gameId: otherGame });
		expect(reloadB.commands.map((c) => c.id)).toEqual(["b1"]);
		reloadB.dispose();
	});

	test("mutations made before init resolves are ignored", async () => {
		const store = new OrderDraftStore();
		await store.add(placeholder("c1", "first"));
		await store.remove("c1");
		await store.move(0, 1);
		expect(store.status).toBe("idle");
		expect(store.commands).toEqual([]);

		await store.init({ cache, gameId: GAME_ID });
		expect(store.commands).toEqual([]);
		store.dispose();
	});

	test("dispose suppresses persistence side effects of in-flight mutations", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add(placeholder("c1", "first"));
		store.dispose();
		// Adding after dispose is a no-op because status remains
		// `ready` but the cache pointer is null and the destroyed flag
		// blocks the persist path.
		await store.add(placeholder("c2", "second"));

		const reload = new OrderDraftStore();
		await reload.init({ cache, gameId: GAME_ID });
		expect(reload.commands.map((c) => c.id)).toEqual(["c1"]);
		reload.dispose();
	});

	test("planetRename validates locally and statuses reflect valid/invalid", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "planetRename",
			id: "id-valid",
			planetNumber: 1,
			name: "Earth",
		});
		await store.add({
			kind: "planetRename",
			id: "id-invalid",
			planetNumber: 2,
			name: "$bad",
		});
		expect(store.statuses["id-valid"]).toBe("valid");
		expect(store.statuses["id-invalid"]).toBe("invalid");
		store.dispose();
	});

	test("setProductionType validates locally per the engine's subject rule", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "setProductionType",
			id: "cap",
			planetNumber: 1,
			productionType: "CAP",
			subject: "",
		});
		await store.add({
			kind: "setProductionType",
			id: "drive",
			planetNumber: 2,
			productionType: "DRIVE",
			subject: "",
		});
		await store.add({
			kind: "setProductionType",
			id: "ship-ok",
			planetNumber: 3,
			productionType: "SHIP",
			subject: "Scout",
		});
		await store.add({
			kind: "setProductionType",
			id: "ship-empty",
			planetNumber: 4,
			productionType: "SHIP",
			subject: "",
		});
		await store.add({
			kind: "setProductionType",
			id: "science-bad",
			planetNumber: 5,
			productionType: "SCIENCE",
			subject: "Bad Name",
		});
		expect(store.statuses["cap"]).toBe("valid");
		expect(store.statuses["drive"]).toBe("valid");
		expect(store.statuses["ship-ok"]).toBe("valid");
		expect(store.statuses["ship-empty"]).toBe("invalid");
		expect(store.statuses["science-bad"]).toBe("invalid");
		store.dispose();
	});

	test("setProductionType collapses to the latest entry per planet", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "setProductionType",
			id: "first",
			planetNumber: 7,
			productionType: "CAP",
			subject: "",
		});
		await store.add({
			kind: "setProductionType",
			id: "second",
			planetNumber: 7,
			productionType: "MAT",
			subject: "",
		});
		await store.add({
			kind: "setProductionType",
			id: "third",
			planetNumber: 7,
			productionType: "DRIVE",
			subject: "",
		});
		expect(store.commands).toHaveLength(1);
		const only = store.commands[0]!;
		expect(only.id).toBe("third");
		if (only.kind !== "setProductionType") {
			throw new Error("expected setProductionType");
		}
		expect(only.productionType).toBe("DRIVE");
		// Old ids are scrubbed from statuses so the order tab does not
		// keep ghost rows.
		expect(store.statuses["first"]).toBeUndefined();
		expect(store.statuses["second"]).toBeUndefined();
		expect(store.statuses["third"]).toBe("valid");
		store.dispose();
	});

	test("setProductionType for different planets stay independent", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "setProductionType",
			id: "p7-cap",
			planetNumber: 7,
			productionType: "CAP",
			subject: "",
		});
		await store.add({
			kind: "setProductionType",
			id: "p9-mat",
			planetNumber: 9,
			productionType: "MAT",
			subject: "",
		});
		expect(store.commands.map((c) => c.id)).toEqual([
			"p7-cap",
			"p9-mat",
		]);
		store.dispose();
	});

	test("planetRename and setProductionType on the same planet keep both", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "planetRename",
			id: "ren",
			planetNumber: 7,
			name: "Earth",
		});
		await store.add({
			kind: "setProductionType",
			id: "prod",
			planetNumber: 7,
			productionType: "CAP",
			subject: "",
		});
		expect(store.commands.map((c) => c.id)).toEqual(["ren", "prod"]);
		expect(store.statuses["ren"]).toBe("valid");
		expect(store.statuses["prod"]).toBe("valid");
		store.dispose();
	});

	test("setCargoRoute collapses by (source, loadType) — newer wins", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "setCargoRoute",
			id: "first",
			sourcePlanetNumber: 1,
			destinationPlanetNumber: 2,
			loadType: "COL",
		});
		await store.add({
			kind: "setCargoRoute",
			id: "second",
			sourcePlanetNumber: 1,
			destinationPlanetNumber: 3,
			loadType: "COL",
		});
		expect(store.commands.map((c) => c.id)).toEqual(["second"]);
		expect(store.statuses["first"]).toBeUndefined();
		expect(store.statuses["second"]).toBe("valid");
		store.dispose();
	});

	test("setCargoRoute and removeCargoRoute share a collapse key", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "setCargoRoute",
			id: "set",
			sourcePlanetNumber: 1,
			destinationPlanetNumber: 2,
			loadType: "MAT",
		});
		await store.add({
			kind: "removeCargoRoute",
			id: "remove",
			sourcePlanetNumber: 1,
			loadType: "MAT",
		});
		expect(store.commands.map((c) => c.id)).toEqual(["remove"]);
		// And remove → set on the same slot collapses again.
		await store.add({
			kind: "setCargoRoute",
			id: "set2",
			sourcePlanetNumber: 1,
			destinationPlanetNumber: 4,
			loadType: "MAT",
		});
		expect(store.commands.map((c) => c.id)).toEqual(["set2"]);
		store.dispose();
	});

	test("cargo routes for different load-types or sources stay independent", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "setCargoRoute",
			id: "p1-col",
			sourcePlanetNumber: 1,
			destinationPlanetNumber: 2,
			loadType: "COL",
		});
		await store.add({
			kind: "setCargoRoute",
			id: "p1-cap",
			sourcePlanetNumber: 1,
			destinationPlanetNumber: 3,
			loadType: "CAP",
		});
		await store.add({
			kind: "setCargoRoute",
			id: "p9-col",
			sourcePlanetNumber: 9,
			destinationPlanetNumber: 2,
			loadType: "COL",
		});
		expect(store.commands.map((c) => c.id)).toEqual([
			"p1-col",
			"p1-cap",
			"p9-col",
		]);
		store.dispose();
	});

	test("setCargoRoute is invalid when source equals destination", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add({
			kind: "setCargoRoute",
			id: "self",
			sourcePlanetNumber: 1,
			destinationPlanetNumber: 1,
			loadType: "EMP",
		});
		expect(store.statuses["self"]).toBe("invalid");
		store.dispose();
	});

	test("hydrateFromServer overwrites the local cache with the server snapshot", async () => {
		const { fakeFetchClient } = await import("./helpers/fake-order-client");
		const { client } = fakeFetchClient(GAME_ID, [
			{
				kind: "planetRename",
				id: "hydr-1",
				planetNumber: 7,
				name: "Hydrated",
			},
		], 7);

		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.hydrateFromServer({ client, turn: 5 });
		expect(store.commands).toHaveLength(1);
		expect(store.commands[0]!.id).toBe("hydr-1");
		expect(store.updatedAt).toBe(7);
		expect(store.statuses["hydr-1"]).toBe("applied");
		expect(store.syncStatus).toBe("synced");
		store.dispose();
	});

	test("hydrate empties the local cache when server returns found=false", async () => {
		// First seed a local draft.
		const seeded = new OrderDraftStore();
		await seeded.init({ cache, gameId: GAME_ID });
		await seeded.add({
			kind: "planetRename",
			id: "stale",
			planetNumber: 1,
			name: "Stale",
		});
		seeded.dispose();

		const { fakeFetchClient } = await import("./helpers/fake-order-client");
		const { client } = fakeFetchClient(GAME_ID, [], 0, false);

		const reload = new OrderDraftStore();
		await reload.init({ cache, gameId: GAME_ID });
		// Local cache shows the stale entry until the server speaks up.
		expect(reload.commands).toHaveLength(1);
		await reload.hydrateFromServer({ client, turn: 5 });
		expect(reload.commands).toEqual([]);
		expect(reload.syncStatus).toBe("synced");
		reload.dispose();
	});
});

describe("OrderDraftStore auto-sync", () => {
	test("add triggers submitOrder with the full draft", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "ok");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client);

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});

		await handle.waitForCalls(1);
		expect(handle.calls).toHaveLength(1);
		expect(handle.calls[0]!.commandIds).toEqual(["id-1"]);
		expect(store.statuses["id-1"]).toBe("applied");
		expect(store.syncStatus).toBe("synced");
		store.dispose();
	});

	test("remove of last command sends an empty cmd[] to the server", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "ok");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client);

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);

		await store.remove("id-1");
		await handle.waitForCalls(2);
		expect(handle.calls[1]!.commandIds).toEqual([]);
		expect(store.commands).toEqual([]);
		expect(store.syncStatus).toBe("synced");
		store.dispose();
	});

	test("rapid mutations coalesce into the latest draft", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "ok", { delayMs: 10 });
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client);

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await store.add({
			kind: "planetRename",
			id: "id-2",
			planetNumber: 2,
			name: "Mars",
		});
		await store.remove("id-1");

		// Wait until the store reaches a steady "synced" state. The
		// in-flight first call carries [id-1], the coalesced retry
		// reflects the post-remove draft.
		await waitFor(() => {
			expect(store.syncStatus).toBe("synced");
			expect(store.statuses["id-2"]).toBe("applied");
		});
		expect(handle.calls.length).toBeGreaterThanOrEqual(2);
		const last = handle.calls[handle.calls.length - 1]!;
		expect(last.commandIds).toEqual(["id-2"]);
		expect(store.statuses["id-1"]).toBeUndefined();
		store.dispose();
	});

	test("non-ok response marks every in-flight command as rejected", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "rejected");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client);

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);

		expect(store.statuses["id-1"]).toBe("rejected");
		expect(store.syncStatus).toBe("error");
		store.dispose();
	});

	test("forceSync re-runs the pipeline after a previous failure", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "rejected");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client);

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);
		expect(store.syncStatus).toBe("error");

		handle.setOutcome("ok");
		store.forceSync();
		await handle.waitForCalls(2);
		expect(store.statuses["id-1"]).toBe("applied");
		expect(store.syncStatus).toBe("synced");
		store.dispose();
	});

	test("mutations made before bindClient still sync once client is bound", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "ok");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });

		// Mutation lands before the client is wired — the layout
		// can't always sequence init → bindClient → mutate, e.g.
		// when bind happens after a slow `Promise.all`.
		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		expect(handle.calls).toHaveLength(0);

		store.bindClient(handle.client);
		store.forceSync();
		await handle.waitForCalls(1);
		expect(store.statuses["id-1"]).toBe("applied");
		store.dispose();
	});
});

describe("OrderDraftStore Phase 25 conflict / paused / offline", () => {
	test("turn_already_closed marks the in-flight commands as conflict", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "turn_already_closed");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client, { getCurrentTurn: () => 7 });

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);

		expect(store.syncStatus).toBe("conflict");
		expect(store.statuses["id-1"]).toBe("conflict");
		expect(store.conflictBanner).not.toBeNull();
		expect(store.conflictBanner?.turn).toBe(7);
		expect(store.conflictBanner?.code).toBe("turn_already_closed");
		store.dispose();
	});

	test("mutating after a conflict clears the banner and revalidates", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "turn_already_closed");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client, { getCurrentTurn: () => 3 });

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);
		expect(store.syncStatus).toBe("conflict");

		// Adding a second command must wipe the conflict banner and
		// re-validate the prior conflict-marked entry. Auto-sync
		// re-fires (still seeing turn_already_closed) and the
		// store ends up back in conflict for the new attempt.
		handle.setOutcome("ok");
		await store.add({
			kind: "planetRename",
			id: "id-2",
			planetNumber: 2,
			name: "Mars",
		});
		await handle.waitForCalls(2);
		expect(store.statuses["id-1"]).toBe("applied");
		expect(store.statuses["id-2"]).toBe("applied");
		expect(store.syncStatus).toBe("synced");
		expect(store.conflictBanner).toBeNull();
		store.dispose();
	});

	test("game_paused outcome surfaces the pause banner and locks sync", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "game_paused");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(handle.client);

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);

		expect(store.syncStatus).toBe("paused");
		expect(store.pausedBanner).not.toBeNull();
		expect(store.statuses["id-1"]).toBe("valid"); // reverted, not in flight

		// While paused, additional mutations should not trigger another
		// submit — the queue would just hit the same wall.
		const before = handle.calls.length;
		store.forceSync();
		await new Promise<void>((resolve) => setTimeout(resolve, 20));
		expect(handle.calls.length).toBe(before);
		store.dispose();
	});

	test("markPaused projects a push event into the store", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.markPaused({ reason: "generation_failed" });
		expect(store.syncStatus).toBe("paused");
		expect(store.pausedBanner?.reason).toBe("generation_failed");
		store.dispose();
	});

	test("resetForNewTurn clears the conflict banner and rehydrates", async () => {
		const { fakeFetchClient, recordingClient } = await import(
			"./helpers/fake-order-client"
		);
		const recHandle = recordingClient(GAME_ID, "turn_already_closed");
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		store.bindClient(recHandle.client, { getCurrentTurn: () => 5 });

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await recHandle.waitForCalls(1);
		expect(store.syncStatus).toBe("conflict");

		const { client: fetchClient } = fakeFetchClient(GAME_ID, [
			{
				kind: "planetRename",
				id: "fresh-1",
				planetNumber: 9,
				name: "Hydrated",
			},
		], 11);
		await store.resetForNewTurn({ client: fetchClient, turn: 6 });
		expect(store.conflictBanner).toBeNull();
		expect(store.syncStatus).toBe("synced");
		expect(store.commands.map((c) => c.id)).toEqual(["fresh-1"]);
		expect(store.updatedAt).toBe(11);
		store.dispose();
	});

	test("offline outcome holds the submit until online flips", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "ok");
		const store = new OrderDraftStore();

		// Stub the browser event surface so we can flip online/offline
		// deterministically and assert the queue's reaction.
		const listeners = new Map<string, Set<() => void>>();
		let online = false;
		await store.init({
			cache,
			gameId: GAME_ID,
			queue: {
				onlineProbe: () => online,
				addEventListener: (event, handler) => {
					let bucket = listeners.get(event);
					if (bucket === undefined) {
						bucket = new Set();
						listeners.set(event, bucket);
					}
					bucket.add(handler);
				},
				removeEventListener: (event, handler) => {
					listeners.get(event)?.delete(handler);
				},
				onOnline: () => undefined,
			},
		});
		store.bindClient(handle.client);

		await store.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});

		// Submission must NOT have left the queue while offline.
		await new Promise<void>((resolve) => setTimeout(resolve, 20));
		expect(handle.calls).toHaveLength(0);
		expect(store.syncStatus).toBe("offline");
		expect(store.statuses["id-1"]).toBe("valid");

		// Flip online and fire the browser `online` event; the queue's
		// onOnline callback (set inside OrderDraftStore) schedules a
		// fresh sync.
		online = true;
		const onlineBucket = listeners.get("online");
		onlineBucket?.forEach((h) => h());
		await handle.waitForCalls(1);
		expect(store.statuses["id-1"]).toBe("applied");
		expect(store.syncStatus).toBe("synced");
		store.dispose();
	});
});

describe("OrderDraftStore Phase 26 history-mode gate", () => {
	test("add is a no-op while getHistoryMode returns true", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add(placeholder("c1", "first"));
		expect(store.commands.map((c) => c.id)).toEqual(["c1"]);

		let history = false;
		// The store would short-circuit even without bindClient (the
		// gate runs before any sync logic). Binding a fake client
		// here mirrors the real layout where `bindClient` is the path
		// that wires `getHistoryMode` in.
		store.bindClient(
			{ executeCommand: async () => ({ resultCode: "ok", payloadBytes: new Uint8Array() }) } as never,
			{ getHistoryMode: () => history },
		);

		history = true;
		await store.add(placeholder("c2", "second"));
		expect(store.commands.map((c) => c.id)).toEqual(["c1"]);

		history = false;
		await store.add(placeholder("c3", "third"));
		expect(store.commands.map((c) => c.id)).toEqual(["c1", "c3"]);

		store.dispose();
	});

	test("remove is a no-op while getHistoryMode returns true", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add(placeholder("c1", "first"));
		await store.add(placeholder("c2", "second"));

		let history = true;
		store.bindClient(
			{ executeCommand: async () => ({ resultCode: "ok", payloadBytes: new Uint8Array() }) } as never,
			{ getHistoryMode: () => history },
		);

		await store.remove("c1");
		expect(store.commands.map((c) => c.id)).toEqual(["c1", "c2"]);

		history = false;
		await store.remove("c1");
		expect(store.commands.map((c) => c.id)).toEqual(["c2"]);

		store.dispose();
	});

	test("move is a no-op while getHistoryMode returns true", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add(placeholder("c1", "first"));
		await store.add(placeholder("c2", "second"));
		await store.add(placeholder("c3", "third"));

		let history = true;
		store.bindClient(
			{ executeCommand: async () => ({ resultCode: "ok", payloadBytes: new Uint8Array() }) } as never,
			{ getHistoryMode: () => history },
		);

		await store.move(0, 2);
		expect(store.commands.map((c) => c.id)).toEqual(["c1", "c2", "c3"]);

		history = false;
		await store.move(0, 2);
		expect(store.commands.map((c) => c.id)).toEqual(["c2", "c3", "c1"]);

		store.dispose();
	});

	test("draft survives entering and leaving history mode untouched", async () => {
		const store = new OrderDraftStore();
		await store.init({ cache, gameId: GAME_ID });
		await store.add(placeholder("c1", "first"));
		await store.add(placeholder("c2", "second"));

		let history = false;
		store.bindClient(
			{ executeCommand: async () => ({ resultCode: "ok", payloadBytes: new Uint8Array() }) } as never,
			{ getHistoryMode: () => history },
		);

		history = true;
		// Inspector affordances try to push commands, gate refuses.
		await store.add(placeholder("c3", "history attempt"));
		expect(store.commands.map((c) => c.id)).toEqual(["c1", "c2"]);

		history = false;
		expect(store.commands.map((c) => c.id)).toEqual(["c1", "c2"]);
		await store.add(placeholder("c4", "back live"));
		expect(store.commands.map((c) => c.id)).toEqual(["c1", "c2", "c4"]);

		store.dispose();
	});
});
