import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useHistory } from '@docusaurus/router';
import searchConfig from '@docuservix-search/config';
import type { SearchResult } from '../../types';
import styles from './styles.module.css';

const MAX_DROPDOWN_RESULTS = 10;
const DEBOUNCE_MS = 300;

export default function SearchBar(): JSX.Element {
    const [query, setQuery] = useState('');
    const [results, setResults] = useState<SearchResult[]>([]);
    const [notices, setNotices] = useState<string[]>([]);
    const [hasErrors, setHasErrors] = useState(false);
    const [loading, setLoading] = useState(false);
    const [open, setOpen] = useState(false);
    const [selectedIndex, setSelectedIndex] = useState(-1);

    const containerRef = useRef<HTMLDivElement>(null);
    const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
    const abortRef = useRef<AbortController | null>(null);
    const history = useHistory();

    const runSearch = useCallback(async (q: string) => {
        if (q.trim().length < 2) {
            setResults([]);
            setNotices([]);
            setHasErrors(false);
            setOpen(false);
            return;
        }

        if (abortRef.current) {
            abortRef.current.abort();
        }
        const controller = new AbortController();
        abortRef.current = controller;

        const timeout = searchConfig.timeout ?? 5000;
        const timeoutId = setTimeout(() => controller.abort(), timeout);

        setLoading(true);
        setHasErrors(false);

        try {
            const settled = await Promise.allSettled(
                searchConfig.providers.map((provider) => {
                    const signal = controller.signal;
                    return provider.search(q, signal);
                }),
            );

            if (controller.signal.aborted) return;

            const allResults: SearchResult[] = [];
            const allNotices: string[] = [];
            let anyError = false;

            for (const outcome of settled) {
                if (outcome.status === 'fulfilled') {
                    allResults.push(...outcome.value.results);
                    if (outcome.value.notice) {
                        allNotices.push(outcome.value.notice);
                    }
                } else {
                    anyError = true;
                }
            }

            allResults.sort((a, b) => b.relevance - a.relevance);
            const top = allResults.slice(0, MAX_DROPDOWN_RESULTS);

            setResults(top);
            setNotices(allNotices);
            setHasErrors(anyError);
            setOpen(true);
            setSelectedIndex(-1);
        } finally {
            clearTimeout(timeoutId);
            setLoading(false);
        }
    }, []);

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const value = e.target.value;
        setQuery(value);
        if (timerRef.current) clearTimeout(timerRef.current);
        timerRef.current = setTimeout(() => runSearch(value), DEBOUNCE_MS);
    };

    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
        if (!open) {
            if (e.key === 'Enter' && query.trim()) {
                history.push(`/search?q=${encodeURIComponent(query.trim())}`);
            }
            return;
        }

        if (e.key === 'ArrowDown') {
            e.preventDefault();
            setSelectedIndex((prev) => Math.min(prev + 1, results.length - 1));
        } else if (e.key === 'ArrowUp') {
            e.preventDefault();
            setSelectedIndex((prev) => Math.max(prev - 1, -1));
        } else if (e.key === 'Enter') {
            e.preventDefault();
            if (selectedIndex >= 0 && results[selectedIndex]) {
                window.location.href = results[selectedIndex].url;
            } else if (query.trim()) {
                history.push(`/search?q=${encodeURIComponent(query.trim())}`);
            }
            setOpen(false);
        } else if (e.key === 'Escape') {
            setOpen(false);
            setSelectedIndex(-1);
        }
    };

    useEffect(() => {
        const handleClickOutside = (e: MouseEvent) => {
            if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
                setOpen(false);
                setSelectedIndex(-1);
            }
        };
        document.addEventListener('mousedown', handleClickOutside);
        return () => document.removeEventListener('mousedown', handleClickOutside);
    }, []);

    useEffect(() => {
        return () => {
            if (timerRef.current) clearTimeout(timerRef.current);
            if (abortRef.current) abortRef.current.abort();
        };
    }, []);

    return (
        <div
            ref={containerRef}
            className={styles.container}
        >
            <input
                className={styles.input}
                type="search"
                placeholder="Поиск..."
                value={query}
                onChange={handleChange}
                onKeyDown={handleKeyDown}
                aria-label="Поиск по документации"
                aria-expanded={open}
                aria-autocomplete="list"
                role="combobox"
            />
            {loading && (
                <span
                    className={styles.spinner}
                    aria-label="Загрузка..."
                />
            )}
            {open && (
                <div
                    className={styles.dropdown}
                    role="listbox"
                    onMouseDown={(e) => e.preventDefault()}
                >
                    {hasErrors && (
                        <div className={styles.errorBadge}>Некоторые источники недоступны</div>
                    )}
                    {results.length === 0 && !loading && (
                        <div className={styles.empty}>Ничего не найдено</div>
                    )}
                    {results.map((result, i) => (
                        <a
                            key={`${result.url}-${i}`}
                            href={result.url}
                            role="option"
                            aria-selected={i === selectedIndex}
                            className={
                                i === selectedIndex
                                    ? `${styles.item} ${styles.selectedItem}`
                                    : styles.item
                            }
                            onClick={() => setOpen(false)}
                        >
                            <div className={styles.itemHeading}>{result.title}</div>
                            <div className={styles.itemFile}>
                                <span
                                    className={styles.badge}
                                    data-type={result.type}
                                >
                                    {result.type}
                                </span>
                                {result.path}
                                <span className={styles.itemScore}>
                                    {Math.round(result.relevance * 100)}%
                                </span>
                            </div>
                            {result.content && (
                                <div className={styles.itemContent}>{result.content}</div>
                            )}
                        </a>
                    ))}
                    {notices.map((notice, i) => (
                        <div
                            key={i}
                            className={styles.notice}
                        >
                            {notice}
                        </div>
                    ))}
                </div>
            )}
        </div>
    );
}
