import {
	type CombatantId,
	type ConditionId,
	deriveHpStatus,
	type PlayerIcon,
} from "@initiative/domain";
import { Book, BookOpen, Brain, X } from "lucide-react";
import { type Ref, useCallback, useEffect, useRef, useState } from "react";
import { cn } from "../lib/utils";
import { AcShield } from "./ac-shield";
import { ConditionPicker } from "./condition-picker";
import { ConditionTags } from "./condition-tags";
import { D20Icon } from "./d20-icon";
import { HpAdjustPopover } from "./hp-adjust-popover";
import { PLAYER_COLOR_HEX, PLAYER_ICON_MAP } from "./player-icon-map";
import { ConfirmButton } from "./ui/confirm-button";
import { Input } from "./ui/input";

interface Combatant {
	readonly id: CombatantId;
	readonly name: string;
	readonly initiative?: number;
	readonly maxHp?: number;
	readonly currentHp?: number;
	readonly ac?: number;
	readonly conditions?: readonly ConditionId[];
	readonly isConcentrating?: boolean;
	readonly color?: string;
	readonly icon?: string;
}

interface CombatantRowProps {
	combatant: Combatant;
	isActive: boolean;
	onRename: (id: CombatantId, newName: string) => void;
	onSetInitiative: (id: CombatantId, value: number | undefined) => void;
	onRemove: (id: CombatantId) => void;
	onSetHp: (id: CombatantId, maxHp: number | undefined) => void;
	onAdjustHp: (id: CombatantId, delta: number) => void;
	onSetAc: (id: CombatantId, value: number | undefined) => void;
	onToggleCondition: (id: CombatantId, conditionId: ConditionId) => void;
	onToggleConcentration: (id: CombatantId) => void;
	onShowStatBlock?: () => void;
	isStatBlockOpen?: boolean;
	onRollInitiative?: (id: CombatantId) => void;
}

function EditableName({
	name,
	combatantId,
	onRename,
	color,
}: Readonly<{
	name: string;
	combatantId: CombatantId;
	onRename: (id: CombatantId, newName: string) => void;
	color?: string;
}>) {
	const [editing, setEditing] = useState(false);
	const [draft, setDraft] = useState(name);
	const inputRef = useRef<HTMLInputElement>(null);

	const commit = useCallback(() => {
		const trimmed = draft.trim();
		if (trimmed !== "" && trimmed !== name) {
			onRename(combatantId, trimmed);
		}
		setEditing(false);
	}, [draft, name, combatantId, onRename]);

	const startEditing = useCallback(() => {
		setDraft(name);
		setEditing(true);
		requestAnimationFrame(() => inputRef.current?.select());
	}, [name]);

	if (editing) {
		return (
			<Input
				ref={inputRef}
				type="text"
				value={draft}
				className="h-7 max-w-48 text-sm"
				onChange={(e) => setDraft(e.target.value)}
				onBlur={commit}
				onKeyDown={(e) => {
					if (e.key === "Enter") commit();
					if (e.key === "Escape") setEditing(false);
				}}
			/>
		);
	}

	return (
		<button
			type="button"
			onClick={startEditing}
			className="cursor-text truncate text-left text-foreground text-sm transition-colors hover:text-hover-neutral"
			style={color ? { color } : undefined}
		>
			{name}
		</button>
	);
}

function MaxHpDisplay({
	maxHp,
	onCommit,
}: Readonly<{
	maxHp: number | undefined;
	onCommit: (value: number | undefined) => void;
}>) {
	const [editing, setEditing] = useState(false);
	const [draft, setDraft] = useState(maxHp?.toString() ?? "");
	const inputRef = useRef<HTMLInputElement>(null);

	const commit = useCallback(() => {
		if (draft === "") {
			onCommit(undefined);
		} else {
			const n = Number.parseInt(draft, 10);
			if (!Number.isNaN(n) && n >= 1) {
				onCommit(n);
			}
		}
		setEditing(false);
	}, [draft, onCommit]);

	const startEditing = useCallback(() => {
		setDraft(maxHp?.toString() ?? "");
		setEditing(true);
		requestAnimationFrame(() => inputRef.current?.select());
	}, [maxHp]);

	if (editing) {
		return (
			<Input
				ref={inputRef}
				type="text"
				inputMode="numeric"
				value={draft}
				placeholder="Max"
				className="h-7 w-[7ch] text-center text-sm tabular-nums"
				onChange={(e) => setDraft(e.target.value)}
				onBlur={commit}
				onKeyDown={(e) => {
					if (e.key === "Enter") commit();
					if (e.key === "Escape") setEditing(false);
				}}
			/>
		);
	}

	return (
		<button
			type="button"
			onClick={startEditing}
			className="inline-block h-7 min-w-[3ch] text-center text-muted-foreground text-sm tabular-nums leading-7 transition-colors hover:text-hover-neutral"
		>
			{maxHp ?? "Max"}
		</button>
	);
}

function ClickableHp({
	currentHp,
	maxHp,
	onAdjust,
	dimmed,
}: Readonly<{
	currentHp: number | undefined;
	maxHp: number | undefined;
	onAdjust: (delta: number) => void;
	dimmed?: boolean;
}>) {
	const [popoverOpen, setPopoverOpen] = useState(false);
	const status = deriveHpStatus(currentHp, maxHp);

	if (maxHp === undefined) {
		return (
			<span
				className={cn(
					"inline-block h-7 w-[4ch] text-center text-muted-foreground text-sm tabular-nums leading-7",
					dimmed && "opacity-50",
				)}
				role="status"
				aria-label="No HP set"
			>
				--
			</span>
		);
	}

	return (
		<div className="relative">
			<button
				type="button"
				onClick={() => setPopoverOpen(true)}
				aria-label={`Current HP: ${currentHp} (${status})`}
				className={cn(
					"inline-block h-7 min-w-[3ch] text-center font-medium text-sm tabular-nums leading-7 transition-colors hover:text-hover-neutral",
					status === "bloodied" && "text-amber-400",
					status === "unconscious" && "text-red-400",
					status === "healthy" && "text-foreground",
					dimmed && "opacity-50",
				)}
			>
				{currentHp}
			</button>
			{!!popoverOpen && (
				<HpAdjustPopover
					onAdjust={onAdjust}
					onClose={() => setPopoverOpen(false)}
				/>
			)}
		</div>
	);
}

function AcDisplay({
	ac,
	onCommit,
}: Readonly<{
	ac: number | undefined;
	onCommit: (value: number | undefined) => void;
}>) {
	const [editing, setEditing] = useState(false);
	const [draft, setDraft] = useState(ac?.toString() ?? "");
	const inputRef = useRef<HTMLInputElement>(null);

	const commit = useCallback(() => {
		if (draft === "") {
			onCommit(undefined);
		} else {
			const n = Number.parseInt(draft, 10);
			if (!Number.isNaN(n) && n >= 0) {
				onCommit(n);
			}
		}
		setEditing(false);
	}, [draft, onCommit]);

	const startEditing = useCallback(() => {
		setDraft(ac?.toString() ?? "");
		setEditing(true);
		requestAnimationFrame(() => inputRef.current?.select());
	}, [ac]);

	if (editing) {
		return (
			<Input
				ref={inputRef}
				type="text"
				inputMode="numeric"
				value={draft}
				placeholder="AC"
				className="h-7 w-[6ch] text-center text-sm tabular-nums"
				onChange={(e) => setDraft(e.target.value)}
				onBlur={commit}
				onKeyDown={(e) => {
					if (e.key === "Enter") commit();
					if (e.key === "Escape") setEditing(false);
				}}
			/>
		);
	}

	return <AcShield value={ac} onClick={startEditing} />;
}

function InitiativeDisplay({
	initiative,
	combatantId,
	dimmed,
	onSetInitiative,
	onRollInitiative,
}: Readonly<{
	initiative: number | undefined;
	combatantId: CombatantId;
	dimmed: boolean;
	onSetInitiative: (id: CombatantId, value: number | undefined) => void;
	onRollInitiative?: (id: CombatantId) => void;
}>) {
	const [editing, setEditing] = useState(false);
	const [draft, setDraft] = useState(initiative?.toString() ?? "");
	const inputRef = useRef<HTMLInputElement>(null);

	const commit = useCallback(() => {
		if (draft === "") {
			onSetInitiative(combatantId, undefined);
		} else {
			const n = Number.parseInt(draft, 10);
			if (!Number.isNaN(n)) {
				onSetInitiative(combatantId, n);
			}
		}
		setEditing(false);
	}, [draft, combatantId, onSetInitiative]);

	const startEditing = useCallback(() => {
		setDraft(initiative?.toString() ?? "");
		setEditing(true);
		requestAnimationFrame(() => inputRef.current?.select());
	}, [initiative]);

	if (editing) {
		return (
			<Input
				ref={inputRef}
				type="text"
				inputMode="numeric"
				value={draft}
				placeholder="--"
				className={cn(
					"h-7 w-[6ch] text-center text-sm tabular-nums",
					dimmed && "opacity-50",
				)}
				onChange={(e) => setDraft(e.target.value)}
				onBlur={commit}
				onKeyDown={(e) => {
					if (e.key === "Enter") commit();
					if (e.key === "Escape") setEditing(false);
				}}
			/>
		);
	}

	// Empty + bestiary creature → d20 roll button
	if (initiative === undefined && onRollInitiative) {
		return (
			<button
				type="button"
				onClick={() => onRollInitiative(combatantId)}
				className={cn(
					"flex h-7 w-full items-center justify-center text-muted-foreground transition-colors hover:text-hover-neutral",
					dimmed && "opacity-50",
				)}
				title="Roll initiative"
				aria-label="Roll initiative"
			>
				<D20Icon className="h-7 w-7" />
			</button>
		);
	}

	// Has value → bold number, click to edit
	// Empty + manual → "--" placeholder, click to edit
	return (
		<button
			type="button"
			onClick={startEditing}
			className={cn(
				"h-7 w-full text-center text-sm tabular-nums leading-7 transition-colors",
				initiative === undefined
					? "text-muted-foreground hover:text-hover-neutral"
					: "font-medium text-foreground hover:text-hover-neutral",
				dimmed && "opacity-50",
			)}
		>
			{initiative ?? "--"}
		</button>
	);
}

function rowBorderClass(
	isActive: boolean,
	isConcentrating: boolean | undefined,
): string {
	if (isActive && isConcentrating)
		return "border border-l-2 border-accent/40 border-l-purple-400 bg-accent/10 card-glow";
	if (isActive)
		return "border border-l-2 border-accent/40 bg-accent/10 card-glow";
	if (isConcentrating)
		return "border border-l-2 border-transparent border-l-purple-400";
	return "border border-l-2 border-transparent";
}

function concentrationIconClass(
	isConcentrating: boolean | undefined,
	dimmed: boolean,
): string {
	if (!isConcentrating)
		return "opacity-0 group-hover:opacity-50 text-muted-foreground";
	return dimmed ? "opacity-50 text-purple-400" : "opacity-100 text-purple-400";
}

export function CombatantRow({
	ref,
	combatant,
	isActive,
	onRename,
	onSetInitiative,
	onRemove,
	onSetHp,
	onAdjustHp,
	onSetAc,
	onToggleCondition,
	onToggleConcentration,
	onShowStatBlock,
	isStatBlockOpen,
	onRollInitiative,
}: CombatantRowProps & { ref?: Ref<HTMLDivElement> }) {
	const { id, name, initiative, maxHp, currentHp } = combatant;
	const status = deriveHpStatus(currentHp, maxHp);
	const dimmed = status === "unconscious";
	const [pickerOpen, setPickerOpen] = useState(false);

	const prevHpRef = useRef(currentHp);
	const [isPulsing, setIsPulsing] = useState(false);
	const pulseTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);

	useEffect(() => {
		const prevHp = prevHpRef.current;
		prevHpRef.current = currentHp;

		if (
			prevHp !== undefined &&
			currentHp !== undefined &&
			currentHp < prevHp &&
			combatant.isConcentrating
		) {
			setIsPulsing(true);
			clearTimeout(pulseTimerRef.current);
			pulseTimerRef.current = setTimeout(() => setIsPulsing(false), 1200);
		}
	}, [currentHp, combatant.isConcentrating]);

	useEffect(() => {
		if (!combatant.isConcentrating) {
			clearTimeout(pulseTimerRef.current);
			setIsPulsing(false);
		}
	}, [combatant.isConcentrating]);

	const pcColor = combatant.color
		? PLAYER_COLOR_HEX[combatant.color as keyof typeof PLAYER_COLOR_HEX]
		: undefined;

	return (
		<div
			ref={ref}
			className={cn(
				"group rounded-lg pr-3 transition-colors",
				rowBorderClass(isActive, combatant.isConcentrating),
				isPulsing && "animate-concentration-pulse",
			)}
		>
			<div className="grid grid-cols-[2rem_3rem_1fr_auto_auto_2rem] items-center gap-3 py-2">
				{/* Concentration */}
				<button
					type="button"
					onClick={() => onToggleConcentration(id)}
					title="Concentrating"
					aria-label="Toggle concentration"
					className={cn(
						"-my-2 -ml-[2px] flex w-full items-center justify-center self-stretch pl-[14px] transition-opacity hover:text-hover-neutral hover:opacity-100",
						concentrationIconClass(combatant.isConcentrating, dimmed),
					)}
				>
					<Brain size={16} />
				</button>

				{/* Initiative */}
				<InitiativeDisplay
					initiative={initiative}
					combatantId={id}
					dimmed={dimmed}
					onSetInitiative={onSetInitiative}
					onRollInitiative={onRollInitiative}
				/>

				{/* Name + Conditions */}
				<div
					className={cn(
						"relative flex min-w-0 flex-wrap items-center gap-1",
						dimmed && "opacity-50",
					)}
				>
					{!!onShowStatBlock && (
						<button
							type="button"
							onClick={onShowStatBlock}
							title="View stat block"
							aria-label="View stat block"
							className="shrink-0 text-foreground transition-colors hover:text-hover-neutral"
						>
							{isStatBlockOpen ? <BookOpen size={16} /> : <Book size={16} />}
						</button>
					)}
					{!!combatant.icon &&
						!!combatant.color &&
						(() => {
							const PcIcon = PLAYER_ICON_MAP[combatant.icon as PlayerIcon];
							const iconColor =
								PLAYER_COLOR_HEX[
									combatant.color as keyof typeof PLAYER_COLOR_HEX
								];
							return PcIcon ? (
								<PcIcon
									size={16}
									style={{ color: iconColor }}
									className="shrink-0"
								/>
							) : null;
						})()}
					<EditableName
						name={name}
						combatantId={id}
						onRename={onRename}
						color={pcColor}
					/>
					<ConditionTags
						conditions={combatant.conditions}
						onRemove={(conditionId) => onToggleCondition(id, conditionId)}
						onOpenPicker={() => setPickerOpen((prev) => !prev)}
					/>
					{!!pickerOpen && (
						<ConditionPicker
							activeConditions={combatant.conditions}
							onToggle={(conditionId) => onToggleCondition(id, conditionId)}
							onClose={() => setPickerOpen(false)}
						/>
					)}
				</div>

				{/* AC */}
				<div className={cn(dimmed && "opacity-50")}>
					<AcDisplay ac={combatant.ac} onCommit={(v) => onSetAc(id, v)} />
				</div>

				{/* HP */}
				<div className="flex items-center gap-1">
					<ClickableHp
						currentHp={currentHp}
						maxHp={maxHp}
						onAdjust={(delta) => onAdjustHp(id, delta)}
						dimmed={dimmed}
					/>
					{maxHp !== undefined && (
						<span
							className={cn(
								"text-muted-foreground text-sm tabular-nums",
								dimmed && "opacity-50",
							)}
						>
							/
						</span>
					)}
					<div className={cn(dimmed && "opacity-50")}>
						<MaxHpDisplay maxHp={maxHp} onCommit={(v) => onSetHp(id, v)} />
					</div>
				</div>

				{/* Actions */}
				<ConfirmButton
					icon={<X size={16} />}
					label="Remove combatant"
					onConfirm={() => onRemove(id)}
					className="pointer-events-none pointer-coarse:pointer-events-auto h-7 w-7 text-muted-foreground opacity-0 pointer-coarse:opacity-100 transition-opacity focus:pointer-events-auto focus:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100"
				/>
			</div>
		</div>
	);
}
