import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Check, Loader2, Search, X } from 'lucide-react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';

// Basic option type
export interface MultiSelectOption {
    value: string | number;
    label: string;
    // Allow passing through any extra meta if needed
    [key: string]: any;
}

export interface CursorResult {
    data: MultiSelectOption[];
    nextCursor: string | null; // null when no more pages
}

export interface MultiSelectFetcherArgs {
    search: string;
    cursor: string | null; // pass null for first page
    pageSize: number;
}

export type MultiSelectFetcher = (args: MultiSelectFetcherArgs) => Promise<CursorResult>;

export interface AppMultiSelectFilterProps {
    label?: string;
    placeholder?: string;
    // currently selected raw values
    value: (string | number)[];
    onChange: (nextValues: (string | number)[], selectedOptions: MultiSelectOption[]) => void;
    fetcher: MultiSelectFetcher; // async loader
    pageSize?: number;
    disabled?: boolean;
    className?: string;
    // Optional: preloaded options (will prepend before loading remote)
    initialOptions?: MultiSelectOption[];
    // When true, show a 'Select All (Loaded)' helper
    enableSelectAllLoaded?: boolean;
    // Debounce milliseconds for search
    searchDebounceMs?: number;
    // Custom render for option row
    renderOption?: (opt: MultiSelectOption, checked: boolean) => React.ReactNode;
    // Empty state text
    emptyText?: string;
}

// Small debounce hook
function useDebounced<T>(value: T, delay: number) {
    const [debounced, setDebounced] = useState(value);
    useEffect(() => {
        const id = setTimeout(() => setDebounced(value), delay);
        return () => clearTimeout(id);
    }, [value, delay]);
    return debounced;
}

export const AppMultiSelectFilter: React.FC<AppMultiSelectFilterProps> = ({
    label,
    placeholder = 'Search…',
    value,
    onChange,
    fetcher,
    pageSize = 20,
    disabled = false,
    className = '',
    initialOptions = [],
    enableSelectAllLoaded = true,
    searchDebounceMs = 300,
    renderOption,
    emptyText = 'No results',
}) => {
    const [search, setSearch] = useState('');
    const debouncedSearch = useDebounced(search, searchDebounceMs);
    const [options, setOptions] = useState<MultiSelectOption[]>(initialOptions);
    const [nextCursor, setNextCursor] = useState<string | null>(null);
    const [loading, setLoading] = useState(false);
    const [initialLoaded, setInitialLoaded] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const listRef = useRef<HTMLDivElement | null>(null);
    const activeRequest = useRef(0); // cancellation token
    const rootRef = useRef<HTMLDivElement | null>(null);
    const [open, setOpen] = useState(false); // collapsed by default

    const selectedSet = useMemo(() => new Set(value.map(String)), [value]);

    const mergeOptions = useCallback((incoming: MultiSelectOption[]) => {
        setOptions((prev) => {
            const map = new Map<string, MultiSelectOption>();
            [...prev, ...incoming].forEach((o) => {
                map.set(String(o.value), o);
            });
            return Array.from(map.values());
        });
    }, []);

    const loadPage = useCallback(
        async (cursor: string | null, replace = false) => {
            setLoading(true);
            setError(null);
            const requestId = ++activeRequest.current;
            try {
                const res = await fetcher({ search: debouncedSearch, cursor, pageSize });
                if (requestId !== activeRequest.current) return; // stale
                if (replace) {
                    setOptions(res.data);
                } else {
                    mergeOptions(res.data);
                }
                setNextCursor(res.nextCursor);
                setInitialLoaded(true);
            } catch (e: any) {
                if (requestId === activeRequest.current) {
                    setError(e?.message || 'Failed to load');
                }
            } finally {
                if (requestId === activeRequest.current) {
                    setLoading(false);
                }
            }
        },
        [debouncedSearch, fetcher, mergeOptions, pageSize],
    );

    // Initial & search-triggered load
    useEffect(() => {
        if (!open) return; // only load when panel open
        setOptions(initialOptions);
        setNextCursor(null);
        loadPage(null, true);
    }, [debouncedSearch, open]); // eslint-disable-line react-hooks/exhaustive-deps

    // Close on outside click
    useEffect(() => {
        if (!open) return;
        const handler = (e: MouseEvent) => {
            if (rootRef.current && !rootRef.current.contains(e.target as Node)) {
                setOpen(false);
            }
        };
        document.addEventListener('mousedown', handler);
        return () => document.removeEventListener('mousedown', handler);
    }, [open]);

    // Infinite scroll handler
    const handleScroll = useCallback(
        (e: React.UIEvent<HTMLDivElement>) => {
            if (loading || !nextCursor) return;
            const target = e.currentTarget;
            const thresholdPx = 120;
            if (target.scrollTop + target.clientHeight + thresholdPx >= target.scrollHeight) {
                loadPage(nextCursor);
            }
        },
        [loading, nextCursor, loadPage],
    );

    const toggleValue = useCallback(
        (val: string | number) => {
            const key = String(val);
            let next: (string | number)[];
            if (selectedSet.has(key)) {
                next = value.filter((v) => String(v) !== key);
            } else {
                next = [...value, val];
            }
            const selectedOptions = options.filter((o) => next.some((v) => String(v) === String(o.value)));
            onChange(next, selectedOptions);
        },
        [onChange, options, selectedSet, value],
    );

    const clearAll = useCallback(() => {
        onChange([], []);
    }, [onChange]);

    const selectAllLoaded = useCallback(() => {
        const allValues = options.map((o) => o.value);
        const selectedOptions = [...options];
        onChange(allValues, selectedOptions);
    }, [onChange, options]);

    const isAllLoadedSelected = options.length > 0 && options.every((o) => selectedSet.has(String(o.value)));

    return (
        <div className={`space-y-2 ${className}`} ref={rootRef}>
            {label && <div className="text-xs font-medium tracking-wide text-muted-foreground uppercase">{label}</div>}
            <div className="flex items-center gap-2">
                <div className="relative flex-1">
                    <Search className="absolute top-1/2 left-2 h-4 w-4 -translate-y-1/2 text-text-gray" />
                    <Input
                        disabled={disabled}
                        value={search}
                        onChange={(e) => {
                            // Open only when user actually types (not just focus trap)
                            if (!open) setOpen(true);
                            setSearch(e.target.value);
                        }}
                        placeholder={placeholder}
                        className="pl-8"
                        onClick={() => setOpen((o) => !o)}
                    />
                    {search && (
                        <button
                            type="button"
                            className="absolute top-1/2 right-2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
                            onClick={() => setSearch('')}
                            aria-label="Clear search"
                        >
                            <X className="h-4 w-4" />
                        </button>
                    )}
                </div>
                {value.length > 0 && (
                    <Button variant="ghost" size="sm" onClick={clearAll} disabled={disabled} title="Clear selected">
                        <X className="h-4 w-4" />
                    </Button>
                )}
            </div>

            {open && (
                <div className="rounded-md border">
                    <ScrollArea className="h-56" onScroll={handleScroll} ref={listRef}>
                        <div className="space-y-0.5 p-1">
                            {options.map((opt) => {
                                const checked = selectedSet.has(String(opt.value));
                                return (
                                    <button
                                        type="button"
                                        key={opt.value}
                                        onClick={() => toggleValue(opt.value)}
                                        className={`flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm transition hover:bg-accent focus:ring-1 focus:ring-ring focus:outline-none ${checked ? 'bg-accent' : ''}`}
                                    >
                                        <Checkbox checked={checked} onCheckedChange={() => toggleValue(opt.value)} />
                                        <div className="flex-1 truncate text-left">{renderOption ? renderOption(opt, checked) : opt.label}</div>
                                        {checked && <Check className="h-4 w-4 text-primary" />}
                                    </button>
                                );
                            })}
                            {!loading && options.length === 0 && initialLoaded && !error && (
                                <div className="px-2 py-4 text-center text-xs text-muted-foreground">{emptyText}</div>
                            )}
                            {error && (
                                <div className="space-y-2 px-2 py-2 text-xs text-destructive">
                                    <div>{error}</div>
                                    <Button size="sm" variant="outline" onClick={() => loadPage(null, true)}>
                                        Retry
                                    </Button>
                                </div>
                            )}
                            {loading && (
                                <div className="flex items-center justify-center gap-2 py-3 text-xs text-muted-foreground">
                                    <Loader2 className="h-4 w-4 animate-spin" /> Loading…
                                </div>
                            )}
                            {!loading && nextCursor && (
                                <div className="flex justify-center py-2">
                                    <Button size="sm" variant="ghost" onClick={() => loadPage(nextCursor)} disabled={loading}>
                                        Load more
                                    </Button>
                                </div>
                            )}
                        </div>
                    </ScrollArea>
                </div>
            )}
            {value.length > 0 && (
                <div className="flex flex-wrap gap-1 pt-1">
                    {options
                        .filter((o) => selectedSet.has(String(o.value)))
                        .map((o) => (
                            <span key={o.value} className="inline-flex items-center gap-1 rounded bg-accent px-2 py-0.5 text-xs">
                                {o.label}
                                <button
                                    type="button"
                                    onClick={() => toggleValue(o.value)}
                                    className="hover:text-destructive"
                                    aria-label={`Remove ${o.label}`}
                                >
                                    <X className="h-3 w-3" />
                                </button>
                            </span>
                        ))}
                </div>
            )}
        </div>
    );
};

export default AppMultiSelectFilter;
