import { Button } from '@/components/ui/button';
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
    CommandList,
    CommandSeparator,
} from '@/components/ui/command';
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { useQuery } from '@tanstack/react-query';
import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';

// ── Types ─────────────────────────────────────────────────────────────────────

export type SelectOption = { value: string; label: string };
export type SelectOptionGroup = { group: string; options: SelectOption[] };

export interface SearchableSelectProps {
    value: string;
    onChange: (value: string) => void;

    // Static options — flat list or grouped. Mutually exclusive with queryFn.
    options?: SelectOption[];
    groups?: SelectOptionGroup[];

    // API mode — fetches via TanStack Query with caching.
    queryKey?: unknown[];
    queryFn?: (search: string) => Promise<SelectOption[]>;
    staleTime?: number;

    // When true and search has no exact match, shows "+ Add '…'" option.
    allowCreate?: boolean;
    onCreateOption?: (value: string) => void;

    // Display
    placeholder?: string;
    searchPlaceholder?: string;
    emptyText?: string;

    // Style
    className?: string;
    disabled?: boolean;
    error?: boolean;
}

// ── Component ─────────────────────────────────────────────────────────────────

export function SearchableSelect({
    value,
    onChange,
    options: staticOptions,
    groups,
    queryKey,
    queryFn,
    staleTime = 5 * 60 * 1000,
    allowCreate = false,
    onCreateOption,
    placeholder = 'Select…',
    searchPlaceholder = 'Search…',
    emptyText = 'No results found.',
    className,
    disabled,
    error,
}: SearchableSelectProps) {
    const [open, setOpen] = useState(false);
    const [search, setSearch] = useState('');
    const [debouncedSearch, setDebouncedSearch] = useState('');
    const timerRef = useRef<ReturnType<typeof setTimeout>>();

    // Debounce search → only used in API mode to avoid request on every keystroke
    useEffect(() => {
        clearTimeout(timerRef.current);
        timerRef.current = setTimeout(() => setDebouncedSearch(search), 300);
        return () => clearTimeout(timerRef.current);
    }, [search]);

    const handleOpenChange = (next: boolean) => {
        setOpen(next);
        if (!next) {
            setSearch('');
            setDebouncedSearch('');
        }
    };

    // ── API mode query ────────────────────────────────────────────────────────

    const { data: apiOptions = [], isFetching } = useQuery({
        queryKey: [...(queryKey ?? ['searchable-select']), debouncedSearch],
        queryFn: () => queryFn!(debouncedSearch),
        enabled: !!queryFn && open,
        staleTime,
        placeholderData: (prev) => prev,
    });

    // ── Flat option pool (for label lookup, create-check, API mode) ───────────

    const flatOptions = useMemo<SelectOption[]>(() => {
        if (queryFn) return apiOptions;
        if (groups) return groups.flatMap((g) => g.options);
        return staticOptions ?? [];
    }, [queryFn, apiOptions, groups, staticOptions]);

    // ── Visible options (flat mode) ───────────────────────────────────────────

    const options = useMemo<SelectOption[]>(() => {
        if (groups) return []; // handled by filteredGroups below
        if (queryFn) return apiOptions;
        if (!search) return flatOptions;
        const q = search.toLowerCase();
        return flatOptions.filter(
            (o) =>
                o.label.toLowerCase().includes(q) ||
                o.value.toLowerCase().includes(q),
        );
    }, [groups, queryFn, apiOptions, flatOptions, search]);

    // ── Visible groups (grouped mode) ─────────────────────────────────────────

    const filteredGroups = useMemo<SelectOptionGroup[] | null>(() => {
        if (!groups) return null;
        if (!search) return groups;
        const q = search.toLowerCase();
        return groups
            .map((g) => ({
                ...g,
                options: g.options.filter(
                    (o) =>
                        o.label.toLowerCase().includes(q) ||
                        o.value.toLowerCase().includes(q),
                ),
            }))
            .filter((g) => g.options.length > 0);
    }, [groups, search]);

    // ── Trigger label ─────────────────────────────────────────────────────────

    const selectedLabel = useMemo(() => {
        return flatOptions.find((o) => o.value === value)?.label ?? value;
    }, [flatOptions, value]);

    // ── Create option ─────────────────────────────────────────────────────────

    const showCreate =
        allowCreate &&
        search.trim().length > 0 &&
        !flatOptions.some(
            (o) => o.label.toLowerCase() === search.trim().toLowerCase(),
        );

    // ── Handlers ──────────────────────────────────────────────────────────────

    const handleSelect = (optValue: string) => {
        onChange(optValue);
        handleOpenChange(false);
    };

    const handleCreate = () => {
        onCreateOption?.(search.trim());
        handleOpenChange(false);
    };

    // ── Render ────────────────────────────────────────────────────────────────

    return (
        <Popover open={open} onOpenChange={handleOpenChange}>
            <PopoverTrigger asChild>
                <Button
                    type="button"
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    disabled={disabled}
                    className={cn(
                        'border-border flex h-10 w-full items-center justify-between rounded-lg border',
                        'bg-card dark:bg-muted/30 px-3 py-2.5 text-sm font-normal',
                        'hover:bg-muted/50 transition-colors',
                        'focus-visible:border-primary/50 focus-visible:ring-primary/20 focus-visible:ring-1',
                        'disabled:cursor-not-allowed disabled:opacity-50',
                        value
                            ? 'text-foreground'
                            : 'text-muted-foreground dark:text-muted-foreground/40',
                        error &&
                            'border-destructive focus-visible:ring-destructive/30',
                        className,
                    )}
                >
                    <span className="truncate">
                        {value ? selectedLabel : placeholder}
                    </span>
                    {isFetching ? (
                        <Loader2 className="text-muted-foreground dark:text-muted-foreground/40 ml-2 h-4 w-4 shrink-0 animate-spin" />
                    ) : (
                        <ChevronsUpDown className="text-muted-foreground dark:text-muted-foreground/40 ml-2 h-4 w-4 shrink-0" />
                    )}
                </Button>
            </PopoverTrigger>

            <PopoverContent
                align="start"
                sideOffset={4}
                className="p-0"
                style={{ width: 'var(--radix-popover-trigger-width)' }}
            >
                <Command shouldFilter={false}>
                    <CommandInput
                        placeholder={searchPlaceholder}
                        value={search}
                        onValueChange={setSearch}
                        onClear={search ? () => setSearch('') : undefined}
                    />
                    <CommandList className="[&::-webkit-scrollbar-thumb]:bg-border/70 scrollbar-thin [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-transparent">
                        {/* Loading spinner on first fetch (no previous data) */}
                        {isFetching && options.length === 0 && (
                            <div className="flex items-center justify-center py-6">
                                <Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
                            </div>
                        )}

                        {/* Empty state */}
                        {!isFetching &&
                            (filteredGroups
                                ? filteredGroups.length === 0
                                : options.length === 0) &&
                            !showCreate && (
                                <CommandEmpty>{emptyText}</CommandEmpty>
                            )}

                        {/* Grouped options */}
                        {filteredGroups &&
                            filteredGroups.map((group) => (
                                <CommandGroup
                                    key={group.group}
                                    heading={group.group}
                                >
                                    {group.options.map((option) => (
                                        <CommandItem
                                            key={option.value}
                                            value={option.value}
                                            onSelect={handleSelect}
                                        >
                                            <Check
                                                className={cn(
                                                    'mr-2 h-4 w-4 shrink-0',
                                                    value === option.value
                                                        ? 'opacity-100'
                                                        : 'opacity-0',
                                                )}
                                            />
                                            {option.label}
                                        </CommandItem>
                                    ))}
                                </CommandGroup>
                            ))}

                        {/* Flat options */}
                        {!filteredGroups && options.length > 0 && (
                            <CommandGroup>
                                {options.map((option) => (
                                    <CommandItem
                                        key={option.value}
                                        value={option.value}
                                        onSelect={handleSelect}
                                    >
                                        <Check
                                            className={cn(
                                                'mr-2 h-4 w-4 shrink-0',
                                                value === option.value
                                                    ? 'opacity-100'
                                                    : 'opacity-0',
                                            )}
                                        />
                                        {option.label}
                                    </CommandItem>
                                ))}
                            </CommandGroup>
                        )}

                        {/* Create new option */}
                        {showCreate && (
                            <>
                                {options.length > 0 && <CommandSeparator />}
                                <CommandGroup>
                                    <CommandItem
                                        value={`__create__${search}`}
                                        onSelect={handleCreate}
                                        className="text-primary data-[selected=true]:text-primary"
                                    >
                                        <Plus className="mr-2 h-4 w-4 shrink-0" />
                                        Add &quot;{search.trim()}&quot;
                                    </CommandItem>
                                </CommandGroup>
                            </>
                        )}
                    </CommandList>
                </Command>
            </PopoverContent>
        </Popover>
    );
}
