import {
	rollAllInitiativeUseCase,
	rollInitiativeUseCase,
} from "@initiative/application";
import type { CombatantId, Creature, CreatureId } from "@initiative/domain";
import {
	useCallback,
	useEffect,
	useLayoutEffect,
	useRef,
	useState,
} from "react";
import { ActionBar } from "./components/action-bar";
import { CombatantRow } from "./components/combatant-row";
import { CreatePlayerModal } from "./components/create-player-modal";
import { PlayerManagement } from "./components/player-management";
import { SourceManager } from "./components/source-manager";
import { StatBlockPanel } from "./components/stat-block-panel";
import { Toast } from "./components/toast";
import { TurnNavigation } from "./components/turn-navigation";
import { type SearchResult, useBestiary } from "./hooks/use-bestiary";
import { useBulkImport } from "./hooks/use-bulk-import";
import { useEncounter } from "./hooks/use-encounter";
import { usePlayerCharacters } from "./hooks/use-player-characters";

function rollDice(): number {
	return Math.floor(Math.random() * 20) + 1;
}

function useActionBarAnimation(combatantCount: number) {
	const wasEmptyRef = useRef(combatantCount === 0);
	const [settling, setSettling] = useState(false);
	const [rising, setRising] = useState(false);
	const [topBarExiting, setTopBarExiting] = useState(false);

	useLayoutEffect(() => {
		const nowEmpty = combatantCount === 0;
		if (wasEmptyRef.current && !nowEmpty) {
			setSettling(true);
		} else if (!wasEmptyRef.current && nowEmpty) {
			setRising(true);
			setTopBarExiting(true);
		}
		wasEmptyRef.current = nowEmpty;
	}, [combatantCount]);

	const empty = combatantCount === 0;
	const risingClass = rising ? " animate-rise-to-center" : "";
	const settlingClass = settling ? " animate-settle-to-bottom" : "";
	const topBarClass = settling
		? " animate-slide-down-in"
		: topBarExiting
			? " absolute inset-x-0 top-0 z-10 px-4 animate-slide-up-out"
			: "";
	const showTopBar = !empty || topBarExiting;

	return {
		risingClass,
		settlingClass,
		topBarClass,
		showTopBar,
		onSettleEnd: () => setSettling(false),
		onRiseEnd: () => setRising(false),
		onTopBarExitEnd: () => setTopBarExiting(false),
	};
}

export function App() {
	const {
		encounter,
		advanceTurn,
		retreatTurn,
		addCombatant,
		clearEncounter,
		removeCombatant,
		editCombatant,
		setInitiative,
		setHp,
		adjustHp,
		setAc,
		toggleCondition,
		toggleConcentration,
		addFromBestiary,
		addFromPlayerCharacter,
		makeStore,
	} = useEncounter();

	const {
		characters: playerCharacters,
		createCharacter: createPlayerCharacter,
		editCharacter: editPlayerCharacter,
		deleteCharacter: deletePlayerCharacter,
	} = usePlayerCharacters();

	const [createPlayerOpen, setCreatePlayerOpen] = useState(false);
	const [managementOpen, setManagementOpen] = useState(false);
	const [editingPlayer, setEditingPlayer] = useState<
		(typeof playerCharacters)[number] | undefined
	>(undefined);

	const {
		search,
		getCreature,
		isLoaded,
		isSourceCached,
		fetchAndCacheSource,
		uploadAndCacheSource,
		refreshCache,
	} = useBestiary();

	const bulkImport = useBulkImport();

	const [selectedCreatureId, setSelectedCreatureId] =
		useState<CreatureId | null>(null);
	const [bulkImportMode, setBulkImportMode] = useState(false);
	const [sourceManagerOpen, setSourceManagerOpen] = useState(false);
	const [isRightPanelFolded, setIsRightPanelFolded] = useState(false);
	const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>(
		null,
	);
	const [isWideDesktop, setIsWideDesktop] = useState(
		() => window.matchMedia("(min-width: 1280px)").matches,
	);

	useEffect(() => {
		const mq = window.matchMedia("(min-width: 1280px)");
		const handler = (e: MediaQueryListEvent) => setIsWideDesktop(e.matches);
		mq.addEventListener("change", handler);
		return () => mq.removeEventListener("change", handler);
	}, []);

	const selectedCreature: Creature | null = selectedCreatureId
		? (getCreature(selectedCreatureId) ?? null)
		: null;

	const pinnedCreature: Creature | null = pinnedCreatureId
		? (getCreature(pinnedCreatureId) ?? null)
		: null;

	const handleAddFromBestiary = useCallback(
		(result: SearchResult) => {
			addFromBestiary(result);
		},
		[addFromBestiary],
	);

	const handleCombatantStatBlock = useCallback((creatureId: string) => {
		setSelectedCreatureId(creatureId as CreatureId);
		setIsRightPanelFolded(false);
	}, []);

	const handleRollInitiative = useCallback(
		(id: CombatantId) => {
			rollInitiativeUseCase(makeStore(), id, rollDice(), getCreature);
		},
		[makeStore, getCreature],
	);

	const handleRollAllInitiative = useCallback(() => {
		rollAllInitiativeUseCase(makeStore(), rollDice, getCreature);
	}, [makeStore, getCreature]);

	const handleViewStatBlock = useCallback((result: SearchResult) => {
		const slug = result.name
			.toLowerCase()
			.replace(/[^a-z0-9]+/g, "-")
			.replace(/(^-|-$)/g, "");
		const cId = `${result.source.toLowerCase()}:${slug}` as CreatureId;
		setSelectedCreatureId(cId);
		setIsRightPanelFolded(false);
	}, []);

	const handleBulkImport = useCallback(() => {
		setBulkImportMode(true);
		setSelectedCreatureId(null);
	}, []);

	const handleStartBulkImport = useCallback(
		(baseUrl: string) => {
			bulkImport.startImport(
				baseUrl,
				fetchAndCacheSource,
				isSourceCached,
				refreshCache,
			);
		},
		[bulkImport.startImport, fetchAndCacheSource, isSourceCached, refreshCache],
	);

	const handleBulkImportDone = useCallback(() => {
		setBulkImportMode(false);
		bulkImport.reset();
	}, [bulkImport.reset]);

	const handleDismissBrowsePanel = useCallback(() => {
		setSelectedCreatureId(null);
		setBulkImportMode(false);
	}, []);

	const handleToggleFold = useCallback(() => {
		setIsRightPanelFolded((f) => !f);
	}, []);

	const handlePin = useCallback(() => {
		if (selectedCreatureId) {
			setPinnedCreatureId((prev) =>
				prev === selectedCreatureId ? null : selectedCreatureId,
			);
		}
	}, [selectedCreatureId]);

	const handleUnpin = useCallback(() => {
		setPinnedCreatureId(null);
	}, []);

	const actionBarInputRef = useRef<HTMLInputElement>(null);
	const actionBarAnim = useActionBarAnimation(encounter.combatants.length);

	// Auto-scroll to the active combatant when the turn changes
	const activeRowRef = useRef<HTMLDivElement>(null);
	useEffect(() => {
		activeRowRef.current?.scrollIntoView({
			block: "nearest",
			behavior: "smooth",
		});
	}, [encounter.activeIndex]);

	// Auto-show stat block for the active combatant when turn changes,
	// but only when the viewport is wide enough to show it alongside the tracker.
	// Only react to activeIndex changes — not combatant reordering (e.g. Roll All).
	const prevActiveIndexRef = useRef(encounter.activeIndex);
	useEffect(() => {
		if (prevActiveIndexRef.current === encounter.activeIndex) return;
		prevActiveIndexRef.current = encounter.activeIndex;
		if (!window.matchMedia("(min-width: 1024px)").matches) return;
		const active = encounter.combatants[encounter.activeIndex];
		if (!active?.creatureId || !isLoaded) return;
		setSelectedCreatureId(active.creatureId as CreatureId);
	}, [encounter.activeIndex, encounter.combatants, isLoaded]);

	const isEmpty = encounter.combatants.length === 0;
	const showRollAllInitiative = encounter.combatants.some(
		(c) => c.creatureId != null && c.initiative == null,
	);

	return (
		<div className="flex h-screen flex-col">
			<div className="relative mx-auto flex w-full max-w-2xl flex-1 flex-col gap-3 px-4 min-h-0">
				{actionBarAnim.showTopBar && (
					<div
						className={`shrink-0 pt-8${actionBarAnim.topBarClass}`}
						onAnimationEnd={actionBarAnim.onTopBarExitEnd}
					>
						<TurnNavigation
							encounter={encounter}
							onAdvanceTurn={advanceTurn}
							onRetreatTurn={retreatTurn}
							onClearEncounter={clearEncounter}
						/>
					</div>
				)}

				{isEmpty ? (
					/* Empty state — ActionBar centered */
					<div className="flex flex-1 items-center justify-center min-h-0 pb-[15%] pt-8">
						<div
							className={`w-full${actionBarAnim.risingClass}`}
							onAnimationEnd={actionBarAnim.onRiseEnd}
						>
							<ActionBar
								onAddCombatant={addCombatant}
								onAddFromBestiary={handleAddFromBestiary}
								bestiarySearch={search}
								bestiaryLoaded={isLoaded}
								onViewStatBlock={handleViewStatBlock}
								onBulkImport={handleBulkImport}
								bulkImportDisabled={bulkImport.state.status === "loading"}
								inputRef={actionBarInputRef}
								playerCharacters={playerCharacters}
								onAddFromPlayerCharacter={addFromPlayerCharacter}
								onManagePlayers={() => setManagementOpen(true)}
								onRollAllInitiative={handleRollAllInitiative}
								showRollAllInitiative={showRollAllInitiative}
								onOpenSourceManager={() => setSourceManagerOpen((o) => !o)}
								autoFocus
							/>
						</div>
					</div>
				) : (
					<>
						{sourceManagerOpen && (
							<div className="shrink-0 rounded-md border border-border bg-card px-4 py-3">
								<SourceManager onCacheCleared={refreshCache} />
							</div>
						)}

						{/* Scrollable area — combatant list */}
						<div className="flex-1 overflow-y-auto min-h-0">
							<div className="flex flex-col px-2 py-2">
								{encounter.combatants.map((c, i) => (
									<CombatantRow
										key={c.id}
										ref={i === encounter.activeIndex ? activeRowRef : null}
										combatant={c}
										isActive={i === encounter.activeIndex}
										onRename={editCombatant}
										onSetInitiative={setInitiative}
										onRemove={removeCombatant}
										onSetHp={setHp}
										onAdjustHp={adjustHp}
										onSetAc={setAc}
										onToggleCondition={toggleCondition}
										onToggleConcentration={toggleConcentration}
										onShowStatBlock={
											c.creatureId
												? () => handleCombatantStatBlock(c.creatureId as string)
												: undefined
										}
										onRollInitiative={
											c.creatureId ? handleRollInitiative : undefined
										}
									/>
								))}
							</div>
						</div>

						{/* Action Bar — fixed at bottom */}
						<div
							className={`shrink-0 pb-8${actionBarAnim.settlingClass}`}
							onAnimationEnd={actionBarAnim.onSettleEnd}
						>
							<ActionBar
								onAddCombatant={addCombatant}
								onAddFromBestiary={handleAddFromBestiary}
								bestiarySearch={search}
								bestiaryLoaded={isLoaded}
								onViewStatBlock={handleViewStatBlock}
								onBulkImport={handleBulkImport}
								bulkImportDisabled={bulkImport.state.status === "loading"}
								inputRef={actionBarInputRef}
								playerCharacters={playerCharacters}
								onAddFromPlayerCharacter={addFromPlayerCharacter}
								onManagePlayers={() => setManagementOpen(true)}
								onRollAllInitiative={handleRollAllInitiative}
								showRollAllInitiative={showRollAllInitiative}
								onOpenSourceManager={() => setSourceManagerOpen((o) => !o)}
							/>
						</div>
					</>
				)}
			</div>

			{/* Pinned Stat Block Panel (left) */}
			{pinnedCreatureId && isWideDesktop && (
				<StatBlockPanel
					creatureId={pinnedCreatureId}
					creature={pinnedCreature}
					isSourceCached={isSourceCached}
					fetchAndCacheSource={fetchAndCacheSource}
					uploadAndCacheSource={uploadAndCacheSource}
					refreshCache={refreshCache}
					panelRole="pinned"
					isFolded={false}
					onToggleFold={() => {}}
					onPin={() => {}}
					onUnpin={handleUnpin}
					showPinButton={false}
					side="left"
					onDismiss={() => {}}
				/>
			)}

			{/* Browse Stat Block Panel (right) */}
			<StatBlockPanel
				creatureId={selectedCreatureId}
				creature={selectedCreature}
				isSourceCached={isSourceCached}
				fetchAndCacheSource={fetchAndCacheSource}
				uploadAndCacheSource={uploadAndCacheSource}
				refreshCache={refreshCache}
				panelRole="browse"
				isFolded={isRightPanelFolded}
				onToggleFold={handleToggleFold}
				onPin={handlePin}
				onUnpin={() => {}}
				showPinButton={isWideDesktop && !!selectedCreature}
				side="right"
				onDismiss={handleDismissBrowsePanel}
				bulkImportMode={bulkImportMode}
				bulkImportState={bulkImport.state}
				onStartBulkImport={handleStartBulkImport}
				onBulkImportDone={handleBulkImportDone}
			/>

			{/* Toast for bulk import progress when panel is closed */}
			{bulkImport.state.status === "loading" && !bulkImportMode && (
				<Toast
					message={`Loading sources... ${bulkImport.state.completed + bulkImport.state.failed}/${bulkImport.state.total}`}
					progress={
						bulkImport.state.total > 0
							? (bulkImport.state.completed + bulkImport.state.failed) /
								bulkImport.state.total
							: 0
					}
					onDismiss={() => {}}
				/>
			)}
			{bulkImport.state.status === "complete" && !bulkImportMode && (
				<Toast
					message="All sources loaded"
					onDismiss={bulkImport.reset}
					autoDismissMs={3000}
				/>
			)}
			{bulkImport.state.status === "partial-failure" && !bulkImportMode && (
				<Toast
					message={`Loaded ${bulkImport.state.completed}/${bulkImport.state.total} sources (${bulkImport.state.failed} failed)`}
					onDismiss={bulkImport.reset}
				/>
			)}

			<CreatePlayerModal
				open={createPlayerOpen}
				onClose={() => {
					setCreatePlayerOpen(false);
					setEditingPlayer(undefined);
				}}
				onSave={(name, ac, maxHp, color, icon) => {
					if (editingPlayer) {
						editPlayerCharacter?.(editingPlayer.id, {
							name,
							ac,
							maxHp,
							color,
							icon,
						});
					} else {
						createPlayerCharacter(name, ac, maxHp, color, icon);
					}
				}}
				playerCharacter={editingPlayer}
			/>

			<PlayerManagement
				open={managementOpen}
				onClose={() => setManagementOpen(false)}
				characters={playerCharacters}
				onEdit={(pc) => {
					setEditingPlayer(pc);
					setCreatePlayerOpen(true);
					setManagementOpen(false);
				}}
				onDelete={(id) => deletePlayerCharacter?.(id)}
				onCreate={() => {
					setEditingPlayer(undefined);
					setCreatePlayerOpen(true);
					setManagementOpen(false);
				}}
			/>
		</div>
	);
}
