import clsx from "clsx";
import { Search } from "lucide-react";
import * as React from "react";
import {
	Dialog,
	DialogTrigger,
	ListBox,
	ListBoxItem,
	Modal,
	ModalOverlay,
	Radio,
	RadioGroup,
} from "react-aria-components";
import { useTranslation } from "react-i18next";
import { useFetcher, useNavigate } from "react-router";
import { useDebounce } from "react-use";
import { Avatar } from "~/components/Avatar";
import { Image } from "~/components/Image";
import { Input } from "~/components/Input";
import type { SearchLoaderData } from "~/features/search/routes/search";
import {
	navIconUrl,
	teamPage,
	tournamentOrganizationPage,
	userPage,
} from "~/utils/urls";
import styles from "./CommandPalette.module.css";

const SEARCH_TYPES = [
	"users",
	"teams",
	"organizations",
	"tournaments",
] as const;
type SearchType = (typeof SEARCH_TYPES)[number];

const STORAGE_KEY = "command-palette-search-type";

const SEARCH_TYPE_ICONS: Record<SearchType, string> = {
	users: "u",
	teams: "t",
	organizations: "associations",
	tournaments: "calendar",
};

function getInitialSearchType(): SearchType {
	if (typeof window === "undefined") return "users";
	try {
		const stored = localStorage.getItem(STORAGE_KEY);
		if (stored && SEARCH_TYPES.includes(stored as SearchType)) {
			return stored as SearchType;
		}
	} catch {
		// localStorage may be unavailable
	}
	return "users";
}

export function CommandPalette() {
	const { t } = useTranslation(["common"]);
	const [isOpen, setIsOpen] = React.useState(false);
	const [isMac, setIsMac] = React.useState(false);

	React.useEffect(() => {
		setIsMac(/Mac|iPhone|iPad|iPod/.test(navigator.userAgent));
	}, []);

	React.useEffect(() => {
		const handleKeyDown = (e: KeyboardEvent) => {
			const modifierKey = isMac ? e.metaKey : e.ctrlKey;
			if (modifierKey && e.key === "k") {
				e.preventDefault();
				setIsOpen(true);
			}
		};

		document.addEventListener("keydown", handleKeyDown);
		return () => document.removeEventListener("keydown", handleKeyDown);
	}, [isMac]);

	return (
		<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
			<button
				type="button"
				className={styles.searchButton}
				onClick={() => setIsOpen(true)}
			>
				<Search className={styles.searchIcon} />
				<span className={styles.searchPlaceholder}>{t("common:search")}</span>
				<kbd className={styles.searchKbd}>{isMac ? "Cmd+K" : "Ctrl+K"}</kbd>
			</button>
			<ModalOverlay className={styles.overlay}>
				<Modal className={styles.modal}>
					<Dialog className={styles.dialog} aria-label={t("common:search")}>
						<CommandPaletteContent onClose={() => setIsOpen(false)} />
					</Dialog>
				</Modal>
			</ModalOverlay>
		</DialogTrigger>
	);
}

function CommandPaletteContent({ onClose }: { onClose: () => void }) {
	const { t } = useTranslation(["common"]);
	const navigate = useNavigate();
	const [query, setQuery] = React.useState("");
	const [searchType, setSearchType] =
		React.useState<SearchType>(getInitialSearchType);
	const inputRef = React.useRef<HTMLInputElement>(null);

	const fetcher = useFetcher<SearchLoaderData>();

	React.useEffect(() => {
		inputRef.current?.focus();
	}, []);

	React.useEffect(() => {
		try {
			localStorage.setItem(STORAGE_KEY, searchType);
		} catch {
			// localStorage may be unavailable
		}
	}, [searchType]);

	useDebounce(
		() => {
			if (!query) return;
			fetcher.load(
				`/search?q=${encodeURIComponent(query)}&type=${searchType}&limit=10`,
			);
		},
		300,
		[query, searchType],
	);

	const results = fetcher.data?.results ?? [];
	const hasQuery = query.length > 0;

	const handleSelect = (key: React.Key) => {
		const result = results.find((r) => getResultKey(r) === key);
		if (result) {
			navigate(getResultHref(result));
			onClose();
		}
	};

	const handleSearchTypeChange = (value: string) => {
		setSearchType(value as SearchType);
	};

	const handleRadioGroupKeyDown = (e: React.KeyboardEvent) => {
		if (e.key === "Enter") {
			e.preventDefault();
			inputRef.current?.focus();
		}
	};

	return (
		<>
			<Input
				ref={inputRef}
				className={styles.input}
				placeholder={t("common:search.placeholder")}
				value={query}
				onChange={(e) => setQuery(e.target.value)}
				icon={<Search className={styles.inputIcon} />}
			/>
			{/* biome-ignore lint/a11y/noStaticElementInteractions: keydown handler redirects Enter to input */}
			<div
				className={styles.searchTypeContainer}
				onKeyDown={handleRadioGroupKeyDown}
			>
				<RadioGroup
					value={searchType}
					onChange={handleSearchTypeChange}
					orientation="horizontal"
					aria-label="Search type"
					className={styles.searchTypeRadioGroup}
				>
					{SEARCH_TYPES.map((type) => (
						<Radio
							key={type}
							value={type}
							className={styles.searchTypeRadioWrapper}
						>
							{({ isSelected, isHovered, isFocusVisible }) => (
								<span
									className={clsx(styles.searchTypeRadio, {
										[styles.searchTypeRadioSelected]: isSelected,
										[styles.searchTypeRadioHovered]: isHovered && !isSelected,
										[styles.searchTypeRadioFocusVisible]: isFocusVisible,
									})}
								>
									<Image
										path={navIconUrl(SEARCH_TYPE_ICONS[type])}
										size={18}
										alt=""
									/>
									{t(`common:search.type.${type}`)}
								</span>
							)}
						</Radio>
					))}
				</RadioGroup>
			</div>
			<ListBox
				className={styles.listBox}
				aria-label={t("common:search")}
				selectionMode="single"
				onAction={handleSelect}
				renderEmptyState={() =>
					hasQuery ? (
						<div className={styles.emptyState}>
							{t("common:search.noResults")}
						</div>
					) : (
						<div className={styles.emptyState}>{t("common:search.hint")}</div>
					)
				}
			>
				{results.map((result) => (
					<ListBoxItem
						key={getResultKey(result)}
						id={getResultKey(result)}
						className={styles.listBoxItem}
					>
						<ResultItem result={result} />
					</ListBoxItem>
				))}
			</ListBox>
		</>
	);
}

type SearchResult = NonNullable<SearchLoaderData>["results"][number];

function getResultKey(result: SearchResult): string {
	switch (result.type) {
		case "user":
			return `user-${result.id}`;
		case "team":
			return `team-${result.customUrl}`;
		case "organization":
			return `org-${result.id}`;
		case "tournament":
			return `tournament-${result.id}`;
	}
}

function getResultHref(result: SearchResult): string {
	switch (result.type) {
		case "user":
			return userPage({
				discordId: result.discordId,
				customUrl: result.customUrl,
			});
		case "team":
			return teamPage(result.customUrl);
		case "organization":
			return tournamentOrganizationPage({ organizationSlug: result.slug });
		case "tournament":
			return `/to/${result.id}`;
	}
}

function ResultItem({ result }: { result: SearchResult }) {
	switch (result.type) {
		case "user":
			return (
				<div className={styles.resultItem}>
					<Avatar
						user={{
							discordId: result.discordId,
							discordAvatar: result.discordAvatar,
						}}
						size="xxs"
					/>
					<div className={styles.resultTexts}>
						<span className={styles.resultName}>{result.name}</span>
						{result.secondaryName ? (
							<span className={styles.resultSecondary}>
								{result.secondaryName}
							</span>
						) : null}
					</div>
				</div>
			);
		case "team":
			return (
				<div className={styles.resultItem}>
					<Avatar
						url={result.avatarUrl}
						size="xxs"
						identiconInput={result.name}
					/>
					<span className={styles.resultName}>{result.name}</span>
				</div>
			);
		case "organization":
			return (
				<div className={styles.resultItem}>
					<Avatar
						url={result.avatarUrl}
						size="xxs"
						identiconInput={result.name}
					/>
					<span className={styles.resultName}>{result.name}</span>
				</div>
			);
		case "tournament":
			return (
				<div className={styles.resultItem}>
					{result.logoUrl ? (
						<img
							src={result.logoUrl}
							alt=""
							width={24}
							height={24}
							className={styles.resultLogo}
						/>
					) : null}
					<span className={styles.resultName}>{result.name}</span>
				</div>
			);
	}
}
