// @vitest-environment jsdom
import "@testing-library/jest-dom/vitest";

import { type CreatureId, combatantId } from "@initiative/domain";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { AllProviders } from "../../__tests__/test-providers.js";
import { CombatantRow } from "../combatant-row.js";
import { PLAYER_COLOR_HEX } from "../player-icon-map.js";

// Mock persistence — no localStorage interaction
vi.mock("../../persistence/encounter-storage.js", () => ({
	loadEncounter: () => null,
	saveEncounter: () => {},
}));

vi.mock("../../persistence/player-character-storage.js", () => ({
	loadPlayerCharacters: () => [],
	savePlayerCharacters: () => {},
}));

// Mock bestiary — no IndexedDB or JSON index
vi.mock("../../adapters/bestiary-cache.js", () => ({
	loadAllCachedCreatures: () => Promise.resolve(new Map()),
	isSourceCached: () => Promise.resolve(false),
	cacheSource: () => Promise.resolve(),
	getCachedSources: () => Promise.resolve([]),
	clearSource: () => Promise.resolve(),
	clearAll: () => Promise.resolve(),
}));

vi.mock("../../adapters/bestiary-index-adapter.js", () => ({
	loadBestiaryIndex: () => ({ sources: {}, creatures: [] }),
	getAllSourceCodes: () => [],
	getDefaultFetchUrl: () => "",
	getSourceDisplayName: (code: string) => code,
}));

// DOM API stubs
beforeAll(() => {
	Object.defineProperty(globalThis, "matchMedia", {
		writable: true,
		value: vi.fn().mockImplementation((query: string) => ({
			matches: false,
			media: query,
			onchange: null,
			addListener: vi.fn(),
			removeListener: vi.fn(),
			addEventListener: vi.fn(),
			removeEventListener: vi.fn(),
			dispatchEvent: vi.fn(),
		})),
	});
});

afterEach(cleanup);

function renderRow(
	overrides: Partial<{
		combatant: Parameters<typeof CombatantRow>[0]["combatant"];
		isActive: boolean;
	}> = {},
) {
	const combatant = overrides.combatant ?? {
		id: combatantId("1"),
		name: "Goblin",
		initiative: 15,
		maxHp: 10,
		currentHp: 10,
		ac: 13,
	};
	return render(
		<CombatantRow
			combatant={combatant}
			isActive={overrides.isActive ?? false}
		/>,
		{ wrapper: AllProviders },
	);
}

describe("CombatantRow", () => {
	it("renders combatant name", () => {
		renderRow();
		expect(screen.getByText("Goblin")).toBeInTheDocument();
	});

	it("renders initiative value", () => {
		renderRow();
		expect(screen.getByText("15")).toBeInTheDocument();
	});

	it("renders current HP", () => {
		renderRow({
			combatant: {
				id: combatantId("1"),
				name: "Goblin",
				maxHp: 10,
				currentHp: 7,
			},
		});
		expect(screen.getByText("7")).toBeInTheDocument();
	});

	it("active combatant gets active border styling", () => {
		const { container } = renderRow({ isActive: true });
		const row = container.firstElementChild;
		expect(row?.className).toContain("border-active-row-border");
	});

	it("unconscious combatant (currentHp === 0) gets dimmed styling", () => {
		renderRow({
			combatant: {
				id: combatantId("1"),
				name: "Goblin",
				maxHp: 10,
				currentHp: 0,
			},
		});
		// The name area should have opacity-50
		const nameEl = screen.getByText("Goblin");
		const nameContainer = nameEl.closest(".opacity-50");
		expect(nameContainer).not.toBeNull();
	});

	it("shows '--' for current HP when no maxHp is set", () => {
		renderRow({
			combatant: {
				id: combatantId("1"),
				name: "Goblin",
			},
		});
		expect(screen.getByLabelText("No HP set")).toBeInTheDocument();
	});

	it("shows concentration icon when isConcentrating is true", () => {
		renderRow({
			combatant: {
				id: combatantId("1"),
				name: "Goblin",
				isConcentrating: true,
			},
		});
		const concButton = screen.getByRole("button", {
			name: "Toggle concentration",
		});
		expect(concButton.className).toContain("text-purple-400");
	});

	it("shows player character icon and color when set", () => {
		const { container } = renderRow({
			combatant: {
				id: combatantId("1"),
				name: "Aragorn",
				color: "red",
				icon: "sword",
			},
		});
		// The icon should be rendered with the player color
		const svgIcon = container.querySelector("svg[style]");
		expect(svgIcon).not.toBeNull();
		expect(svgIcon).toHaveStyle({ color: PLAYER_COLOR_HEX.red });
	});

	it("remove button removes after confirmation", async () => {
		const user = userEvent.setup();
		renderRow();
		const removeBtn = screen.getByRole("button", {
			name: "Remove combatant",
		});
		// First click enters confirm state
		await user.click(removeBtn);
		// Second click confirms
		const confirmBtn = screen.getByRole("button", {
			name: "Confirm remove combatant",
		});
		await user.click(confirmBtn);
		// After confirming, the button returns to its initial state
		expect(
			screen.queryByRole("button", { name: "Confirm remove combatant" }),
		).not.toBeInTheDocument();
	});

	it("shows d20 roll button when initiative is undefined and combatant has creatureId", () => {
		renderRow({
			combatant: {
				id: combatantId("1"),
				name: "Goblin",
				creatureId: "srd:goblin" as CreatureId,
			},
		});
		expect(
			screen.getByRole("button", { name: "Roll initiative" }),
		).toBeInTheDocument();
	});
});
