// Integration test for the Phase 14 optimistic overlay. Mounts the
// inspector tab against a real `OrderDraftStore` + `GameStateStore`
// + the rendered-report context and walks the full happy path:
// add a `planetRename` command → mark it submitting → applied → the
// inspector picks up the new name through the overlay without a
// re-fetch of the report.

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

import InspectorTab from "../src/lib/sidebar/inspector-tab.svelte";
import {
	GAME_STATE_CONTEXT_KEY,
	GameStateStore,
} from "../src/lib/game-state.svelte";
import {
	SELECTION_CONTEXT_KEY,
	SelectionStore,
} from "../src/lib/selection.svelte";
import {
	ORDER_DRAFT_CONTEXT_KEY,
	OrderDraftStore,
} from "../src/sync/order-draft.svelte";
import {
	RENDERED_REPORT_CONTEXT_KEY,
	createRenderedReportSource,
} from "../src/lib/rendered-report.svelte";
import { i18n } from "../src/lib/i18n/index.svelte";
import type { GameReport, ReportPlanet } from "../src/api/game-state";
import { IDBCache } from "../src/platform/store/idb-cache";
import { openGalaxyDB } from "../src/platform/store/idb";
import { EMPTY_SHIP_GROUPS } from "./helpers/empty-ship-groups";

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

beforeEach(async () => {
	dbName = `galaxy-overlay-${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();
	});
});

function makePlanet(overrides: Partial<ReportPlanet>): ReportPlanet {
	return {
		number: 0,
		name: "",
		x: 0,
		y: 0,
		kind: "local",
		owner: null,
		size: null,
		resources: null,
		industryStockpile: null,
		materialsStockpile: null,
		industry: null,
		population: null,
		colonists: null,
		production: null,
		freeIndustry: null,
		...overrides,
	};
}

function makeReport(planets: ReportPlanet[]): GameReport {
	return {
		turn: 4,
		mapWidth: 1000,
		mapHeight: 1000,
		planetCount: planets.length,
		planets,
		race: "",
		localShipClass: [],
		routes: [],
		localPlayerDrive: 0,
		localPlayerWeapons: 0,
		localPlayerShields: 0,
		localPlayerCargo: 0,
		...EMPTY_SHIP_GROUPS,
	};
}

describe("inspector overlay reactivity", () => {
	test("applied planetRename swaps the name without a report refresh", async () => {
		const cache = new IDBCache(db);
		const draft = new OrderDraftStore();
		await draft.init({
			cache,
			gameId: "00000000-0000-0000-0000-000000000abc",
		});
		const gameState = new GameStateStore();
		gameState.report = makeReport([
			makePlanet({ number: 7, name: "Earth", kind: "local", size: 100 }),
		]);
		gameState.status = "ready";
		const selection = new SelectionStore();
		selection.selectPlanet(7);
		const renderedReport = createRenderedReportSource(gameState, draft);

		const context = new Map<unknown, unknown>([
			[GAME_STATE_CONTEXT_KEY, gameState],
			[SELECTION_CONTEXT_KEY, selection],
			[ORDER_DRAFT_CONTEXT_KEY, draft],
			[RENDERED_REPORT_CONTEXT_KEY, renderedReport],
		]);

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

		await waitFor(() => {
			expect(ui.getByTestId("inspector-planet-name")).toHaveTextContent("Earth");
		});

		const cmdId = "00000000-0000-0000-0000-000000000001";
		await draft.add({
			kind: "planetRename",
			id: cmdId,
			planetNumber: 7,
			name: "New-Earth",
		});

		// `valid` already participates in the overlay (auto-sync may
		// not have fired yet, but the player's intent is committed).
		expect(draft.statuses[cmdId]).toBe("valid");
		await waitFor(() => {
			expect(ui.getByTestId("inspector-planet-name")).toHaveTextContent(
				"New-Earth",
			);
		});

		// A simulated server refresh that returns the *un-renamed*
		// snapshot must not erase the overlay (turn cutoff has not
		// run yet, the engine still reports the old name).
		gameState.report = makeReport([
			makePlanet({ number: 7, name: "Earth", kind: "local", size: 100 }),
		]);
		await waitFor(() => {
			expect(ui.getByTestId("inspector-planet-name")).toHaveTextContent(
				"New-Earth",
			);
		});

		draft.dispose();
	});

	test("auto-sync after add applies the overlay end-to-end", async () => {
		const { recordingClient } = await import("./helpers/fake-order-client");
		const handle = recordingClient(GAME_ID, "ok");
		const cache = new IDBCache(db);
		const draft = new OrderDraftStore();
		await draft.init({ cache, gameId: GAME_ID });
		draft.bindClient(handle.client);

		const gameState = new GameStateStore();
		gameState.gameId = GAME_ID;
		gameState.report = makeReport([
			makePlanet({ number: 7, name: "Earth", kind: "local", size: 100 }),
		]);
		gameState.status = "ready";

		const selection = new SelectionStore();
		selection.selectPlanet(7);
		const renderedReport = createRenderedReportSource(gameState, draft);

		const context = new Map<unknown, unknown>([
			[GAME_STATE_CONTEXT_KEY, gameState],
			[SELECTION_CONTEXT_KEY, selection],
			[ORDER_DRAFT_CONTEXT_KEY, draft],
			[RENDERED_REPORT_CONTEXT_KEY, renderedReport],
		]);

		const inspector = render(InspectorTab, { context });
		await waitFor(() => {
			expect(inspector.getByTestId("inspector-planet-name")).toHaveTextContent(
				"Earth",
			);
		});

		const cmdId = "00000000-0000-0000-0000-000000000abc";
		await draft.add({
			kind: "planetRename",
			id: cmdId,
			planetNumber: 7,
			name: "New-Earth",
		});
		// Overlay applies on `valid` immediately — auto-sync hasn't
		// landed yet but the player's intent is committed.
		await waitFor(() => {
			expect(inspector.getByTestId("inspector-planet-name")).toHaveTextContent(
				"New-Earth",
			);
		});

		await handle.waitForCalls(1);
		await waitFor(() => {
			expect(draft.statuses[cmdId]).toBe("applied");
		});
		expect(handle.calls).toHaveLength(1);
		expect(handle.calls[0]!.commandIds).toEqual([cmdId]);

		// Inspector still shows the new name after auto-sync.
		expect(inspector.getByTestId("inspector-planet-name")).toHaveTextContent(
			"New-Earth",
		);

		draft.dispose();
	});
});

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