import { Check, ChevronsUpDown, Search } from 'lucide-react';
import { useMemo, useState } from 'react';
import { Button } from '@/components/ui/button';
import {
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuItem,
    DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';

export type SearchableSelectOption = {
    value: string;
    label: string;
};

type Props = {
    id?: string;
    options: SearchableSelectOption[];
    value: string;
    onChange: (value: string) => void;
    placeholder?: string;
    searchPlaceholder?: string;
    emptyMessage?: string;
    noneLabel?: string;
    noneValue?: string;
    className?: string;
    invalid?: boolean;
};

export function SearchableSelect({
    id,
    options,
    value,
    onChange,
    placeholder = 'Sélectionner…',
    searchPlaceholder = 'Rechercher…',
    emptyMessage = 'Aucun résultat.',
    noneLabel,
    noneValue = 'none',
    className,
    invalid = false,
}: Props) {
    const [open, setOpen] = useState(false);
    const [query, setQuery] = useState('');

    const selectedLabel = useMemo(() => {
        if (noneLabel && value === noneValue) {
            return noneLabel;
        }

        return options.find((option) => option.value === value)?.label;
    }, [noneLabel, noneValue, options, value]);

    const filteredOptions = useMemo(() => {
        const needle = query.trim().toLowerCase();

        if (!needle) {
            return options;
        }

        return options.filter((option) =>
            option.label.toLowerCase().includes(needle),
        );
    }, [options, query]);

    function select(next: string) {
        onChange(next);
        setOpen(false);
        setQuery('');
    }

    return (
        <DropdownMenu
            open={open}
            onOpenChange={(next) => {
                setOpen(next);

                if (!next) {
                    setQuery('');
                }
            }}
        >
            <DropdownMenuTrigger asChild>
                <Button
                    id={id}
                    type="button"
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    aria-invalid={invalid || undefined}
                    className={cn(
                        'h-9 w-full justify-between px-3 font-normal',
                        !selectedLabel && 'text-muted-foreground',
                        className,
                    )}
                >
                    <span className="truncate text-left">
                        {selectedLabel ?? placeholder}
                    </span>
                    <ChevronsUpDown className="size-4 shrink-0 opacity-50" />
                </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent
                className="w-(--radix-dropdown-menu-trigger-width) p-0"
                align="start"
            >
                <div className="flex items-center gap-2 border-b border-border px-2.5 py-2">
                    <Search className="size-3.5 shrink-0 text-muted-foreground" />
                    <Input
                        value={query}
                        onChange={(e) => setQuery(e.target.value)}
                        onKeyDown={(e) => e.stopPropagation()}
                        placeholder={searchPlaceholder}
                        className="h-7 border-0 bg-transparent p-0 shadow-none focus-visible:ring-0"
                        autoFocus
                    />
                </div>
                <div className="max-h-56 overflow-y-auto p-1">
                    {noneLabel && (
                        <DropdownMenuItem
                            onSelect={() => select(noneValue)}
                            className="gap-2"
                        >
                            <Check
                                className={cn(
                                    'size-3.5',
                                    value === noneValue
                                        ? 'opacity-100'
                                        : 'opacity-0',
                                )}
                            />
                            <span className="text-muted-foreground">
                                {noneLabel}
                            </span>
                        </DropdownMenuItem>
                    )}
                    {filteredOptions.length === 0 ? (
                        <p className="px-2 py-2 text-sm text-muted-foreground">
                            {emptyMessage}
                        </p>
                    ) : (
                        filteredOptions.map((option) => (
                            <DropdownMenuItem
                                key={option.value}
                                onSelect={() => select(option.value)}
                                className="gap-2"
                            >
                                <Check
                                    className={cn(
                                        'size-3.5',
                                        value === option.value
                                            ? 'opacity-100'
                                            : 'opacity-0',
                                    )}
                                />
                                <span className="truncate">{option.label}</span>
                            </DropdownMenuItem>
                        ))
                    )}
                </div>
            </DropdownMenuContent>
        </DropdownMenu>
    );
}
