// Component coverage for the Phase 14 order tab. The Submit button
// has been retired — every successful `add` / `remove` triggers
// `OrderDraftStore.scheduleSync`, so the tab is mostly a status
// surface. Tests assert the per-row status badge transitions and
// the bottom-bar sync state.

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

import OrderTab from "../src/lib/sidebar/order-tab.svelte";
import {
	ORDER_DRAFT_CONTEXT_KEY,
	OrderDraftStore,
} from "../src/sync/order-draft.svelte";
import { i18n } from "../src/lib/i18n/index.svelte";
import type { OrderCommand } from "../src/sync/order-types";
import { IDBCache } from "../src/platform/store/idb-cache";
import { openGalaxyDB } from "../src/platform/store/idb";
import { recordingClient } from "./helpers/fake-order-client";

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

let db: Awaited<ReturnType<typeof openGalaxyDB>>;
let dbName: string;

beforeEach(async () => {
	dbName = `galaxy-order-tab-${crypto.randomUUID()}`;
	db = await openGalaxyDB(dbName);
	i18n.resetForTests("en");
});

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

async function makeDraft(
	commands: OrderCommand[],
): Promise<{ draft: OrderDraftStore; context: Map<unknown, unknown> }> {
	const cache = new IDBCache(db);
	const draft = new OrderDraftStore();
	await draft.init({ cache, gameId: GAME_ID });
	for (const cmd of commands) {
		await draft.add(cmd);
	}
	const context = new Map<unknown, unknown>([
		[ORDER_DRAFT_CONTEXT_KEY, draft],
	]);
	return { draft, context };
}

describe("order-tab", () => {
	test("renders the empty state when the draft has no commands", async () => {
		const { draft, context } = await makeDraft([]);
		const ui = render(OrderTab, { context });
		expect(ui.getByTestId("order-empty")).toBeVisible();
		expect(ui.queryByTestId("order-list")).toBeNull();
		// The sync bar still renders so the user can see the
		// idle / synced / error transitions.
		expect(ui.getByTestId("order-sync")).toBeVisible();
		draft.dispose();
	});

	test("invalid command shows the invalid status badge", async () => {
		const { draft, context } = await makeDraft([
			{ kind: "planetRename", id: "id-1", planetNumber: 1, name: "" },
		]);
		const ui = render(OrderTab, { context });
		expect(ui.getByTestId("order-command-status-0")).toHaveTextContent(
			"invalid",
		);
		draft.dispose();
	});

	test("auto-sync flips the row to applied and the sync bar to synced", async () => {
		const handle = recordingClient(GAME_ID, "ok");
		const { draft, context } = await makeDraft([]);
		draft.bindClient(handle.client);

		const ui = render(OrderTab, { context });

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

		await handle.waitForCalls(1);
		await waitFor(() => {
			expect(ui.getByTestId("order-command-status-0")).toHaveTextContent(
				"applied",
			);
		});
		await waitFor(() => {
			expect(ui.getByTestId("order-sync")).toHaveAttribute(
				"data-sync-status",
				"synced",
			);
		});
		expect(handle.calls).toHaveLength(1);
		expect(handle.calls[0]!.commandIds).toEqual(["id-1"]);
		draft.dispose();
	});

	test("removing the last command sends an empty cmd[] PUT", async () => {
		const handle = recordingClient(GAME_ID, "ok");
		const { draft, context } = await makeDraft([]);
		draft.bindClient(handle.client);

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

		const ui = render(OrderTab, { context });
		await fireEvent.click(ui.getByTestId("order-command-delete-0"));

		await handle.waitForCalls(2);
		expect(handle.calls[1]!.commandIds).toEqual([]);
		expect(draft.commands).toEqual([]);
		await waitFor(() => {
			expect(ui.getByTestId("order-empty")).toBeVisible();
		});
		draft.dispose();
	});

	test("non-ok response surfaces the sync error and a retry button", async () => {
		const handle = recordingClient(GAME_ID, "rejected");
		const { draft, context } = await makeDraft([]);
		draft.bindClient(handle.client);

		const ui = render(OrderTab, { context });
		await draft.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});

		await handle.waitForCalls(1);
		await waitFor(() => {
			expect(ui.getByTestId("order-sync")).toHaveAttribute(
				"data-sync-status",
				"error",
			);
		});
		expect(ui.getByTestId("order-sync-retry")).toBeVisible();
		expect(ui.getByTestId("order-command-status-0")).toHaveTextContent(
			"rejected",
		);

		// Retry the call now that the fixture answers ok.
		handle.setOutcome("ok");
		await fireEvent.click(ui.getByTestId("order-sync-retry"));
		await handle.waitForCalls(2);
		await waitFor(() => {
			expect(draft.statuses["id-1"]).toBe("applied");
		});
		await waitFor(() => {
			expect(ui.getByTestId("order-sync")).toHaveAttribute(
				"data-sync-status",
				"synced",
			);
		});
		draft.dispose();
	});

	test("turn_already_closed surfaces the conflict banner with the turn", async () => {
		const handle = recordingClient(GAME_ID, "turn_already_closed");
		const { draft, context } = await makeDraft([]);
		draft.bindClient(handle.client, { getCurrentTurn: () => 12 });

		const ui = render(OrderTab, { context });
		await draft.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);

		await waitFor(() => {
			const banner = ui.getByTestId("order-conflict-banner");
			expect(banner).toBeVisible();
			expect(banner).toHaveTextContent("Turn 12");
			expect(banner).toHaveAttribute("data-conflict-turn", "12");
		});
		expect(ui.getByTestId("order-command-status-0")).toHaveTextContent("conflict");
		expect(ui.getByTestId("order-sync")).toHaveAttribute(
			"data-sync-status",
			"conflict",
		);
		draft.dispose();
	});

	test("game_paused surfaces the paused banner and blocks retry", async () => {
		const handle = recordingClient(GAME_ID, "game_paused");
		const { draft, context } = await makeDraft([]);
		draft.bindClient(handle.client);

		const ui = render(OrderTab, { context });
		await draft.add({
			kind: "planetRename",
			id: "id-1",
			planetNumber: 1,
			name: "Earth",
		});
		await handle.waitForCalls(1);

		await waitFor(() => {
			expect(ui.getByTestId("order-paused-banner")).toBeVisible();
		});
		expect(ui.getByTestId("order-sync")).toHaveAttribute(
			"data-sync-status",
			"paused",
		);
		// No retry button is shown for paused state.
		expect(ui.queryByTestId("order-sync-retry")).toBeNull();
		draft.dispose();
	});
});
