import {
    useCallback, useEffect, useMemo, useRef, useState,
} from 'react';
import { IconChevronDown, IconSearch, IconX, IconCheck } from '@tabler/icons-react';
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
import { T } from '../../ui/figma';
import {
    FONT_CATALOG, FONT_CATEGORIES, FONT_SCRIPTS,
    ensureFontPreview, previewFamily, applyFontFamily, familyVariants,
    resolveVariant, variantLabel,
} from '../../fonts';

/* ------------------------------------------------------------------ *
 *  Family picker — searchable, every row set in its own face
 *
 *  The list is the whole Google Fonts library, so two things it would be easy
 *  to skip are load-bearing: the rows are WINDOWED (only what fits the
 *  viewport is in the DOM — two thousand nodes janks the popover open), and
 *  each row's preview face is fetched only once it scrolls into view.
 * ------------------------------------------------------------------ */

const ROW_H = 30;
const OVERSCAN = 6;
const LIST_H = 300;

/** One row. Renders in the family's preview alias once that has arrived, and in
 *  the UI font until then — never blank, so scrolling stays readable. */
function FontRow({ fam, selected, active, onPick, onHover }) {
    const [ready, setReady] = useState(false);
    const ref = useRef(null);

    useEffect(() => {
        const node = ref.current;
        if (!node) return undefined;
        let alive = true;
        const io = new IntersectionObserver((entries) => {
            if (!entries.some((e) => e.isIntersecting)) return;
            io.disconnect();
            ensureFontPreview(fam.name).then((ok) => { if (alive && ok) setReady(true); });
        }, { rootMargin: '120px' });
        io.observe(node);
        return () => { alive = false; io.disconnect(); };
    }, [fam.name]);

    return (
        <button
            ref={ref}
            type="button"
            onClick={() => onPick(fam.name)}
            onMouseEnter={onHover}
            title={`${fam.name} — ${fam.category}`}
            className={`w-full h-[30px] px-2 flex items-center gap-2 rounded-[4px] text-left shrink-0
                ${active ? T.selectedRow : ''}
                ${selected ? 'text-[#0d99ff]' : T.text}`}
        >
            <span className="w-3.5 shrink-0 flex items-center justify-center">
                {selected ? <IconCheck size={12} stroke={2.5} /> : null}
            </span>
            <span
                className="truncate text-[13px] leading-none"
                style={{
                    fontFamily: ready
                        ? `"${previewFamily(fam.name)}", "${fam.name}", sans-serif`
                        : `"${fam.name}", sans-serif`,
                }}
            >
                {fam.name}
            </span>
        </button>
    );
}

export function FontFamilyPicker({ value, onChange, weight = 400, italic = false }) {
    const [open, setOpen] = useState(false);
    const [query, setQuery] = useState('');
    const [filter, setFilter] = useState('all');
    const [cursor, setCursor] = useState(0);
    const [scrollTop, setScrollTop] = useState(0);
    const listRef = useRef(null);
    const searchRef = useRef(null);

    const items = useMemo(() => {
        const q = query.trim().toLowerCase();
        return FONT_CATALOG.filter((f) => {
            if (q && !f.name.toLowerCase().includes(q)) return false;
            if (filter === 'all') return true;
            if (filter.startsWith('cat:')) return f.category === filter.slice(4);
            if (filter.startsWith('scr:')) {
                const i = FONT_SCRIPTS.findIndex(([key]) => key === filter.slice(4));
                return i >= 0 && (f.scriptMask & (1 << i)) !== 0;
            }
            return true;
        });
    }, [query, filter]);

    // Re-anchor whenever the result set changes, so the highlight is never left
    // pointing past the end of a freshly-filtered list.
    useEffect(() => { setCursor(0); }, [query, filter]);

    const scrollToRow = useCallback((index) => {
        const node = listRef.current;
        if (!node) return;
        const top = index * ROW_H;
        if (top < node.scrollTop) node.scrollTop = top;
        else if (top + ROW_H > node.scrollTop + node.clientHeight) {
            node.scrollTop = top + ROW_H - node.clientHeight;
        }
    }, []);

    // Open on the current font rather than at A, and put focus in the search box.
    useEffect(() => {
        if (!open) return;
        const i = items.findIndex((f) => f.name === value);
        const start = i >= 0 ? i : 0;
        setCursor(start);
        requestAnimationFrame(() => {
            searchRef.current?.focus();
            if (listRef.current) listRef.current.scrollTop = Math.max(0, (start - 3) * ROW_H);
        });
        // Only on open — re-running on every keystroke would fight the search.
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [open]);

    const pick = useCallback((name) => {
        onChange(name);
        // Fetch the real face straight away so the canvas does not flash a
        // fallback between the click and the first paint.
        const v = resolveVariant(name, weight, italic);
        applyFontFamily(name, v.weight, v.italic);
        setOpen(false);
    }, [onChange, weight, italic]);

    const onKeyDown = (e) => {
        if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
            e.preventDefault();
            const next = Math.max(0, Math.min(items.length - 1, cursor + (e.key === 'ArrowDown' ? 1 : -1)));
            setCursor(next);
            scrollToRow(next);
        } else if (e.key === 'Enter') {
            e.preventDefault();
            if (items[cursor]) pick(items[cursor].name);
        } else if (e.key === 'Escape') {
            setOpen(false);
        }
    };

    const first = Math.max(0, Math.floor(scrollTop / ROW_H) - OVERSCAN);
    const last = Math.min(items.length, Math.ceil((scrollTop + LIST_H) / ROW_H) + OVERSCAN);
    const window_ = items.slice(first, last);

    return (
        <Popover open={open} onOpenChange={setOpen}>
            <PopoverTrigger asChild>
                <button
                    type="button"
                    title="Font family"
                    className={`relative w-full h-7 pl-2 pr-6 text-[11px] leading-none rounded-[5px] border border-transparent
                        flex items-center text-left cursor-pointer transition-colors
                        bg-[#f5f5f5] dark:bg-[#383838] text-black dark:text-white
                        hover:border-[#e6e6e6] dark:hover:border-[#555555]
                        ${open ? '!border-[#0d99ff] ring-1 ring-[#0d99ff]' : ''}`}
                >
                    <span className="truncate">{value || 'Select font'}</span>
                    <IconChevronDown
                        size={12}
                        className="absolute right-1.5 top-1/2 -translate-y-1/2 pointer-events-none text-black/50 dark:text-white/50"
                    />
                </button>
            </PopoverTrigger>
            <PopoverContent
                side="left"
                align="start"
                sideOffset={12}
                onOpenAutoFocus={(e) => e.preventDefault()}
                className="w-[248px] p-0 rounded-[13px] overflow-hidden border-[#e6e6e6] dark:border-[#444444] dark:bg-[#2c2c2c] shadow-xl"
            >
                <div className="px-3 pt-2.5 pb-2 border-b border-[#e6e6e6] dark:border-[#444444]">
                    <div className={`text-[11px] font-semibold mb-2 ${T.text}`}>Fonts</div>
                    <div className="relative mb-2">
                        <IconSearch size={12} className="absolute left-2 top-1/2 -translate-y-1/2 text-black/40 dark:text-white/40" />
                        <input
                            ref={searchRef}
                            value={query}
                            onChange={(e) => setQuery(e.target.value)}
                            onKeyDown={onKeyDown}
                            placeholder="Search all fonts"
                            className="w-full h-7 pl-6 pr-6 text-[11px] rounded-[5px] border border-transparent outline-none
                                bg-[#f5f5f5] dark:bg-[#383838] text-black dark:text-white
                                focus:!border-[#0d99ff] focus:ring-1 focus:ring-[#0d99ff]
                                placeholder:text-black/30 dark:placeholder:text-white/30"
                        />
                        {query ? (
                            <button
                                type="button"
                                onClick={() => { setQuery(''); searchRef.current?.focus(); }}
                                className="absolute right-1.5 top-1/2 -translate-y-1/2 text-black/40 dark:text-white/40 hover:text-black dark:hover:text-white"
                                title="Clear search"
                            >
                                <IconX size={12} />
                            </button>
                        ) : null}
                    </div>
                    <select
                        value={filter}
                        onChange={(e) => setFilter(e.target.value)}
                        className="w-full h-7 pl-2 pr-6 text-[11px] rounded-[5px] border border-transparent outline-none appearance-none cursor-pointer
                            !bg-none bg-[#f5f5f5] dark:bg-[#383838] text-black dark:text-white
                            focus:!border-[#0d99ff] focus:ring-1 focus:ring-[#0d99ff]"
                    >
                        <option value="all">All fonts</option>
                        <optgroup label="Category">
                            {FONT_CATEGORIES.map((c) => (
                                <option key={c} value={`cat:${c}`}>{c}</option>
                            ))}
                        </optgroup>
                        <optgroup label="Writing system">
                            {FONT_SCRIPTS.map(([key, label]) => (
                                <option key={key} value={`scr:${key}`}>{label}</option>
                            ))}
                        </optgroup>
                    </select>
                </div>

                <div
                    ref={listRef}
                    onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
                    onKeyDown={onKeyDown}
                    tabIndex={-1}
                    className="overflow-y-auto px-1.5 py-1.5"
                    style={{ height: LIST_H }}
                >
                    {items.length === 0 ? (
                        <div className={`h-full flex items-center justify-center text-[11px] ${T.textSoft}`}>
                            No fonts match “{query}”
                        </div>
                    ) : (
                        // One tall spacer holds the scrollbar honest while only the
                        // visible slice is actually mounted.
                        <div style={{ height: items.length * ROW_H, position: 'relative' }}>
                            <div style={{ transform: `translateY(${first * ROW_H}px)` }}>
                                {window_.map((fam, i) => (
                                    <FontRow
                                        key={fam.name}
                                        fam={fam}
                                        selected={fam.name === value}
                                        active={first + i === cursor}
                                        onHover={() => setCursor(first + i)}
                                        onPick={pick}
                                    />
                                ))}
                            </div>
                        </div>
                    )}
                </div>

                <div className={`px-3 py-1.5 border-t border-[#e6e6e6] dark:border-[#444444] text-[10px] ${T.textSoft}`}>
                    {items.length.toLocaleString()} font{items.length === 1 ? '' : 's'}
                </div>
            </PopoverContent>
        </Popover>
    );
}

/* ------------------------------------------------------------------ *
 *  Style picker — the cuts THIS family actually ships
 *
 *  A fixed 300…800 list lies about most families: Abril Fatface has only
 *  Regular, Bitter has nine weights in both slants. Offering a cut the family
 *  does not have gets it synthesised by the browser, and the export — which
 *  draws with the real face — then does not match the canvas.
 * ------------------------------------------------------------------ */

export function FontStylePicker({ family, weight, italic, onChange }) {
    const variants = useMemo(() => familyVariants(family), [family]);
    const current = resolveVariant(family, Number(weight) || 400, !!italic);
    const key = (v) => `${v.weight}:${v.italic ? 1 : 0}`;

    return (
        <div className="relative">
            <select
                value={key(current)}
                title="Font style"
                onChange={(e) => {
                    const [w, i] = e.target.value.split(':');
                    onChange({ weight: Number(w), italic: i === '1' });
                }}
                className="w-full h-7 py-0 pl-2 pr-6 text-[11px] leading-none rounded-[5px] border border-transparent outline-none appearance-none cursor-pointer
                    !bg-none bg-[#f5f5f5] dark:bg-[#383838] text-black dark:text-white
                    hover:border-[#e6e6e6] dark:hover:border-[#555555]
                    focus:border-[#0d99ff] focus:ring-1 focus:ring-[#0d99ff] transition-colors"
            >
                {variants.map((v) => (
                    <option key={key(v)} value={key(v)}>{variantLabel(v.weight, v.italic)}</option>
                ))}
            </select>
            <IconChevronDown
                size={12}
                className="absolute right-1.5 top-1/2 -translate-y-1/2 pointer-events-none text-black/50 dark:text-white/50"
            />
        </div>
    );
}
