import React, { useState, useRef, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { dataAPI } from '../../infrastructure/api/data';
import type { SearchResult, SObject } from '../../types';
import { Search, Loader2, X, ArrowRight } from 'lucide-react';
import { getRecordDisplayName } from '../../infrastructure/api/data/recordUtils';
import { COMMON_FIELDS } from '../../core/constants';
import { DEBOUNCE_FAST_MS } from '@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui';
import { SearchScopeSelector } from './components/SearchScopeSelector';
import { SearchInsights } from './components/SearchInsights';
import { Logger } from '../../core/services/Logger';
import {
    getSearchExecutionPlan,
    getSuggestionScopeObjectTypesAsync,
    getSearchResultIcon,
    getSearchResultPath,
    getSearchScopeLabel,
} from './searchScopeRegistry';
import {
    isLegacyNaturalLanguageSearchQuery,
    UNSUPPORTED_NL_SEARCH_DESCRIPTION,
    UNSUPPORTED_NL_SEARCH_TITLE,
} from './searchExperience';
import { useApp } from '@nexuscrm/host-sdk';

/**
 * System metadata object types that cannot be opened in the record detail view.
 * These use internal/system APIs and are not supported by the UI API.
 * They must be filtered from global search results.
 */
const NON_NAVIGABLE_OBJECTS = new Set([
    'ListView',
]);

function addMatchToGroupedResults(
    resultsByObject: Map<string, SearchResult>,
    objectApiName: string,
    objectLabel: string,
    icon: string,
    match: SObject,
) {
    if (!resultsByObject.has(objectApiName)) {
        resultsByObject.set(objectApiName, {
            ObjectApiName: objectApiName,
            ObjectLabel: objectLabel,
            Icon: icon,
            Matches: []
        });
    }
    const group = resultsByObject.get(objectApiName)!;
    if (!group.Matches?.some(existing => existing.Id === match.Id)) {
        group.Matches?.push(match);
    }
}

function mergeSearchResults(
    suggestionRecords: readonly { Id: string; Name: string; ObjectApiName: string; Icon?: string; Email?: string }[],
    fullSearchResults: SearchResult[],
    supplementalGroups: SearchResult[],
): SearchResult[] {
    const seenIds = new Set<string>();
    const resultsByObject = new Map<string, SearchResult>();

    for (const suggestion of suggestionRecords) {
        if (!suggestion.Id || seenIds.has(suggestion.Id)) continue;
        seenIds.add(suggestion.Id);

        const fullMatchGroup = fullSearchResults.find(result => result.ObjectApiName === suggestion.ObjectApiName);
        let matchData: SObject = { Id: suggestion.Id, Name: suggestion.Name };
        if (suggestion.Email) matchData.Email = suggestion.Email;
        const objectLabel = fullMatchGroup?.ObjectLabel || suggestion.ObjectApiName;
        const icon = fullMatchGroup?.Icon || suggestion.Icon || '';

        if (fullMatchGroup) {
            const richMatch = fullMatchGroup.Matches?.find(match => match.Id === suggestion.Id);
            if (richMatch) {
                matchData = richMatch;
            }
        }

        addMatchToGroupedResults(resultsByObject, suggestion.ObjectApiName, objectLabel, icon, matchData);
    }

    for (const result of fullSearchResults) {
        for (const match of (result.Matches || [])) {
            const id = String(match.Id || '');
            if (!id || seenIds.has(id)) continue;
            seenIds.add(id);
            addMatchToGroupedResults(resultsByObject, result.ObjectApiName, result.ObjectLabel, result.Icon, match);
        }
    }

    for (const flowGroup of supplementalGroups) {
        if (!resultsByObject.has(flowGroup.ObjectApiName)) {
            resultsByObject.set(flowGroup.ObjectApiName, flowGroup);
        }
    }

    return Array.from(resultsByObject.values())
        .filter(group => !NON_NAVIGABLE_OBJECTS.has(group.ObjectApiName));
}

function filterSearchResultsByScope(results: SearchResult[], searchScope: string): SearchResult[] {
    if (searchScope === 'all') {
        return results;
    }

    return results.filter(group => group.ObjectApiName === searchScope);
}

// Helper to highlight matching text
const HighlightMatch: React.FC<{ text: string; highlight: string }> = ({ text, highlight }) => {
    // Memoize regex to prevent re-creation on every render if highlight hasn't changed
    const regex = useMemo(() => {
        if (!highlight.trim()) return null;
        return new RegExp(`(${highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
    }, [highlight]);

    if (!regex) {
        return <span>{text}</span>;
    }

    const parts = text.split(regex);
    return (
        <span>
            {parts.map((part, i) =>
                regex.test(part) ? <mark key={i} className="bg-transparent font-bold text-slate-900">{part}</mark> : <span key={i}>{part}</span>
            )}
        </span>
    );
};

export const GlobalSearch: React.FC = () => {
    const [searchTerm, setSearchTerm] = useState('');
    const [scope, setScope] = useState('all');
    const [results, setResults] = useState<SearchResult[]>([]);
    const [loading, setLoading] = useState(false);
    const [isOpen, setIsOpen] = useState(false);
    const [showUnsupportedQueryState, setShowUnsupportedQueryState] = useState(false);
    // True when the backend search request itself failed (5xx / network), as
    // opposed to succeeding with zero matches. Without this, a backend outage is
    // silently rendered as "No results found".
    const [searchError, setSearchError] = useState(false);

    // Keyboard Navigation State
    const [selectedIndex, setSelectedIndex] = useState(-1);

    const searchRef = useRef<HTMLDivElement>(null);
    const inputRef = useRef<HTMLInputElement>(null);
    const resultRefs = useRef<(HTMLButtonElement | null)[]>([]);
    const navigate = useNavigate();
    const { currentFrontendAppId } = useApp();

    // Flatten results for navigation
    const flatResults = useMemo((): (SObject & { _objectApiName: string; _objectLabel: string })[] => {
        return results.flatMap(group =>
            group.Matches?.map(record => ({
                ...record,
                _objectApiName: group.ObjectApiName,
                _objectLabel: group.ObjectLabel
            })) || []
        );
    }, [results]);

    // Close dropdown on outside click
    useEffect(() => {
        const handleClickOutside = (event: MouseEvent) => {
            if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
                setIsOpen(false);
            }
        };

        if (isOpen) {
            document.addEventListener('mousedown', handleClickOutside);
        }

        return () => {
            document.removeEventListener('mousedown', handleClickOutside);
        };
    }, [isOpen]);

    // Reset selection when results change
    useEffect(() => {
        setSelectedIndex(-1);
        resultRefs.current = resultRefs.current.slice(0, flatResults.length);
    }, [flatResults.length]);

    // Debounced search
    useEffect(() => {
        if (!searchTerm || searchTerm.length < 2) {
            setResults([]);
            setIsOpen(false);
            setShowUnsupportedQueryState(false);
            setSearchError(false);
            return;
        }

        const timer = setTimeout(() => {
            performSearch(searchTerm, scope);
        }, DEBOUNCE_FAST_MS);

        return () => clearTimeout(timer);
    }, [searchTerm, scope]);

    // Track latest parameters for race conditions
    const currentParamsRef = useRef({ term: searchTerm, scope });
    const currentSearchRequestRef = useRef(0);
    useEffect(() => {
        currentParamsRef.current = { term: searchTerm, scope };
    }, [searchTerm, scope]);

    const performSearch = async (term: string, searchScope: string) => {
        const requestId = ++currentSearchRequestRef.current;
        setLoading(true);
        setIsOpen(true);
        setResults([]);
        setShowUnsupportedQueryState(false);
        setSearchError(false);

        try {
            if (isLegacyNaturalLanguageSearchQuery(term)) {
                if (currentSearchRequestRef.current !== requestId) {
                    return;
                }
                setShowUnsupportedQueryState(true);
                return;
            }

            const executionPlan = getSearchExecutionPlan(searchScope);
            const standardObjectTypes = executionPlan.standardScope === 'none'
                ? []
                : executionPlan.standardScope === 'all'
                    ? await getSuggestionScopeObjectTypesAsync()
                    : executionPlan.standardScope;

            if (
                currentSearchRequestRef.current !== requestId ||
                currentParamsRef.current.term !== term ||
                currentParamsRef.current.scope !== searchScope
            ) {
                return;
            }

            // Suggestions-first (issue #1598): while typing, the dropdown runs
            // ONLY the fast prefix `searchSuggestions` (plus the fast supplemental
            // flow search). The full multi-object SOSL search (dataAPI.search) is
            // NOT run per keystroke — it scans every searchable object with a
            // no-index LIKE and is the source of the ~20s typing latency. The full
            // search runs on submit / the results page (Enter -> /search ->
            // SearchResultsPage), which already executes it with its own error UX.
            const suggestionsPromise = executionPlan.standardScope === 'none'
                ? Promise.resolve([])
                : dataAPI.searchSuggestions(term, standardObjectTypes);
            const supplementalSearchPromise = Promise.all(
                executionPlan.supplementalTargets.map(target => target.search(term))
            ).then(supplementalResults => supplementalResults.flat()).catch((err: unknown) => {
                Logger.warn('[GlobalSearch] Supplemental search failed', {}, err);
                return [];
            });

            // The dropdown's "temporarily unavailable" error (issue #1568) is now
            // driven by the suggestions call, since the full search no longer runs
            // here. Capture the failure and apply it only AFTER the staleness gate
            // below, so a superseded request cannot poison the current term.
            let suggestionsFailed = false;
            const suggestionRecords = await suggestionsPromise.catch((err: unknown) => {
                Logger.warn('[GlobalSearch] Search suggestions failed', {}, err);
                suggestionsFailed = true;
                return [];
            });

            if (
                currentSearchRequestRef.current !== requestId ||
                currentParamsRef.current.term !== term ||
                currentParamsRef.current.scope !== searchScope
            ) {
                return;
            }

            // A standard-suggestions outage is terminal for the dropdown: surface
            // it as "temporarily unavailable" rather than rendering a misleading
            // partial dropdown of only supplemental (flow) hits while the normal
            // object suggestions are missing (#1568 / #1598). Do NOT merge
            // supplemental results on top of an outage — the error UI only renders
            // when results are empty, so a non-empty flow-only merge would mask it.
            if (suggestionsFailed) {
                setResults([]);
                setSearchError(true);
                return;
            }

            const partialResults = filterSearchResultsByScope(
                mergeSearchResults(suggestionRecords, [], []),
                searchScope,
            );
            if (partialResults.length > 0) {
                setResults(partialResults);
            }

            const flowSearchResults = await supplementalSearchPromise;

            if (
                currentSearchRequestRef.current !== requestId ||
                currentParamsRef.current.term !== term ||
                currentParamsRef.current.scope !== searchScope
            ) {
                return;
            }

            setResults(filterSearchResultsByScope(
                mergeSearchResults(suggestionRecords, [], flowSearchResults),
                searchScope,
            ));
        } catch (err: unknown) {
            Logger.error('[GlobalSearch] Search failed', {}, err);
            // Only the latest request may own UI state — a stale rejection must
            // not flip the current term into the backend-outage view.
            if (currentSearchRequestRef.current === requestId) {
                setResults([]);
                setSearchError(true);
            }
        } finally {
            if (currentSearchRequestRef.current === requestId) {
                setLoading(false);
            }
        }
    };

    const closeSearch = () => {
        setSearchTerm('');
        setResults([]);
        setIsOpen(false);
        setShowUnsupportedQueryState(false);
        setSearchError(false);
        inputRef.current?.blur();
        setSelectedIndex(-1);
    };

    const navigateToRecord = async (objectApiName: string, recordId: string) => {
        // Special handling for ListView
        if (objectApiName === 'ListView') {
            try {
                const listView = await dataAPI.getRecord<{ SObjectType: string; DeveloperName: string; Name: string }>(
                    'ListView',
                    recordId
                );
                if (listView && listView.SObjectType) {
                    const filterName = listView.DeveloperName || recordId;
                    navigate(`/lightning/o/${listView.SObjectType}/list?filterName=${filterName}`);
                } else {
                    Logger.warn('[GlobalSearch] ListView record missing SObjectType', { listView });
                }
            } catch (err: unknown) {
                Logger.error('[GlobalSearch] Failed to navigate to ListView', { recordId }, err);
            }
            closeSearch();
            return;
        }

        // Guard: Non-navigable system metadata objects must not use the record detail route
        if (NON_NAVIGABLE_OBJECTS.has(objectApiName)) {
            Logger.warn('[GlobalSearch] Blocked navigation to non-navigable object', { objectApiName, recordId });
            return;
        }

        navigate(getSearchResultPath(objectApiName, recordId, currentFrontendAppId));
        closeSearch();
    };


    const handleKeyDown = (e: React.KeyboardEvent) => {
        // Handle Enter key - navigate to search results page or selected item
        if (e.key === 'Enter') {
            e.preventDefault();
            if (selectedIndex >= 0 && flatResults[selectedIndex]) {
                // Navigate to selected record
                const record = flatResults[selectedIndex];
                navigateToRecord(record._objectApiName as string, String(record[COMMON_FIELDS.ID]));
            } else if (searchTerm.length >= 2) {
                // Navigate to full search results page
                setIsOpen(false);
                navigate(`/search?q=${encodeURIComponent(searchTerm)}${scope !== 'all' ? `&scope=${scope}` : ''}`);
                inputRef.current?.blur();
            }
            return;
        }

        if (!isOpen) {
            if (e.key === 'ArrowDown') {
                setIsOpen(true);
            }
            return;
        }

        switch (e.key) {
            case 'ArrowDown':
                e.preventDefault();
                setSelectedIndex(prev => (prev < flatResults.length - 1 ? prev + 1 : prev));
                break;
            case 'ArrowUp':
                e.preventDefault();
                setSelectedIndex(prev => (prev > 0 ? prev - 1 : -1));
                break;
            case 'Escape':
                setIsOpen(false);
                inputRef.current?.blur();
                break;
        }
    };


    // Scroll selected item into view
    useEffect(() => {
        if (selectedIndex >= 0 && resultRefs.current[selectedIndex]) {
            resultRefs.current[selectedIndex]?.scrollIntoView({
                block: 'nearest',
            });
        }
    }, [selectedIndex]);

    const handleClear = () => {
        setSearchTerm('');
        setResults([]);
        setIsOpen(false);
        setShowUnsupportedQueryState(false);
        setSearchError(false);
        setSelectedIndex(-1);
        inputRef.current?.focus();
    };

    const getRecordSecondaryInfo = (record: SObject) => {
        const parts: string[] = [];
        if (record.Email) parts.push(String(record.Email));
        if (record.Phone) parts.push(String(record.Phone));
        if (record.Company) parts.push(String(record.Company));
        if (record.Title) parts.push(String(record.Title));
        return parts.join(' • ');
    };

    const placeholderText = scope === 'all' ? 'Search...' : `Search ${getSearchScopeLabel(scope)}...`;

    // Calculate global index for flattening
    let globalIndex = 0;

    return (
        <div className="relative w-full max-w-2xl group" ref={searchRef}>
            {/* Search Bar Container */}
            <div className={`relative flex items-center bg-white rounded-lg border shadow-sm transition-all duration-200 ${isOpen ? 'ring-2 ring-blue-500 border-transparent rounded-b-none z-50' : 'border-slate-300 focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-transparent'}`}>

                {/* Scope Selector */}
                <div className="flex-shrink-0 h-full relative">
                    <SearchScopeSelector value={scope} onChange={setScope} />
                </div>

                {/* Vertical Divider */}
                <div className="w-px h-6 bg-slate-200" />

                {/* Search Input */}
                <div className="relative flex-1">
                    <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                        {loading ? (
                            <Loader2 size={18} className="text-slate-400 animate-spin" />
                        ) : (
                            <Search size={18} className="text-slate-400" />
                        )}
                    </div>
                    <input
                        ref={inputRef}
                        type="text"
                        role="combobox"
                        aria-autocomplete="list"
                        aria-expanded={isOpen}
                        aria-controls="search-results-listbox"
                        aria-activedescendant={selectedIndex >= 0 ? `search-result-${selectedIndex}` : undefined}
                        value={searchTerm}
                        onChange={(e) => setSearchTerm(e.target.value)}
                        onFocus={() => searchTerm.length >= 2 && setIsOpen(true)}
                        onKeyDown={handleKeyDown}
                        placeholder={placeholderText}
                        className="block w-full pl-10 pr-10 py-2.5 bg-transparent border-0 text-slate-900 placeholder:text-slate-400 focus:ring-0 text-sm"
                        autoComplete="off"
                    />
                    {searchTerm && (
                        <button
                            onClick={handleClear}
                            aria-label="Clear search"
                            className="absolute inset-y-0 right-0 pr-3 flex items-center hover:text-slate-600 rounded-r-lg transition-colors cursor-pointer"
                        >
                            <X size={16} className="text-slate-400" />
                        </button>
                    )}
                </div>
            </div>

            {/* Results Dropdown */}
            {isOpen && (
                <div
                    id="search-results-listbox"
                    role="listbox"
                    className="absolute top-full left-0 right-0 bg-white border border-t-0 border-slate-200 rounded-b-lg shadow-xl z-[49] flex overflow-hidden"
                >

                    {/* Left Panel: Results */}
                    <div className="flex-1 max-h-[500px] overflow-y-auto">

                        {loading && results.length === 0 && (
                            <div className="px-4 py-8 text-center text-slate-500 text-sm">
                                Searching...
                            </div>
                        )}

                        {!loading && results.length === 0 && searchTerm.length >= 2 && (
                            <div className="px-8 py-12 text-center">
                                <Search size={48} className="mx-auto text-slate-200 mb-4" />
                                <h3 className="text-slate-900 font-medium mb-1">
                                    {searchError
                                        ? 'Search is temporarily unavailable'
                                        : showUnsupportedQueryState
                                            ? UNSUPPORTED_NL_SEARCH_TITLE
                                            : 'No results found'}
                                </h3>
                                <p className="text-sm text-slate-500">
                                    {searchError
                                        ? 'We couldn’t reach the server. Please try again in a moment.'
                                        : showUnsupportedQueryState
                                            ? UNSUPPORTED_NL_SEARCH_DESCRIPTION
                                            : `We couldn't find anything matching "${searchTerm}"`}
                                </p>
                            </div>
                        )}

                        {results.length > 0 && (
                            <>
                                {loading && (
                                    <div className="px-4 py-2 text-xs text-slate-500 border-b border-slate-100 bg-slate-50">
                                        Finding more matches...
                                    </div>
                                )}
                                <div className="divide-y divide-slate-100">
                                    {results.map((group) => {
                                        const Icon = getSearchResultIcon(group.ObjectApiName);

                                        return (
                                            <div key={group.ObjectApiName} role="group" aria-label={group.ObjectLabel}>
                                                <div className="px-4 py-2 bg-slate-50 border-b border-slate-100 sticky top-0 z-10 backdrop-blur-sm bg-slate-50/90">
                                                    <div className="flex items-center gap-2">
                                                        <Icon size={14} className="text-slate-500" />
                                                        <span className="text-xs font-bold text-slate-600 uppercase tracking-wider">{group.ObjectLabel}</span>
                                                        <span className="text-xs text-slate-400 font-normal">({group.Matches?.length})</span>
                                                    </div>
                                                </div>

                                                <div>
                                                    {group.Matches?.map((record) => {
                                                        const currentIndex = globalIndex++;
                                                        const isSelected = currentIndex === selectedIndex;

                                                        return (
                                                            <button
                                                                key={record[COMMON_FIELDS.ID] as string}
                                                                id={`search-result-${currentIndex}`}
                                                                role="option"
                                                                aria-selected={isSelected}
                                                                ref={(el) => resultRefs.current[currentIndex] = el}
                                                                onClick={() => navigateToRecord(group.ObjectApiName, record[COMMON_FIELDS.ID] as string)}
                                                                className={`w-full px-4 py-3 text-left transition-colors flex items-start gap-3 group/item border-l-4 
                                                                    ${isSelected
                                                                        ? 'bg-blue-50/80 border-blue-500'
                                                                        : 'bg-white hover:bg-slate-50 border-transparent hover:border-blue-300'
                                                                    }`}
                                                            >
                                                                <div className={`p-2 rounded-lg transition-transform duration-200 bg-slate-100 text-slate-600
                                                                    ${isSelected ? 'scale-110 shadow-sm' : 'group-hover/item:scale-110'}
                                                                `}>
                                                                    <Icon size={18} />
                                                                </div>
                                                                <div className="flex-1 min-w-0 pt-0.5">
                                                                    <p className={`text-sm truncate font-medium ${isSelected ? 'text-blue-700' : 'text-slate-900'}`}>
                                                                        <HighlightMatch text={String(getRecordDisplayName(record))} highlight={searchTerm} />
                                                                    </p>
                                                                    <p className="text-xs text-slate-500 mt-0.5 truncate flex items-center gap-1.5">
                                                                        {group.ObjectLabel}
                                                                        {getRecordSecondaryInfo(record) && (
                                                                            <>
                                                                                <span className="text-slate-300">•</span>
                                                                                <HighlightMatch text={getRecordSecondaryInfo(record)} highlight={searchTerm} />
                                                                            </>
                                                                        )}
                                                                    </p>
                                                                </div>
                                                            </button>
                                                        );
                                                    })}
                                                </div>
                                            </div>
                                        );
                                    })}
                                </div>

                                {/* Show More Footer */}
                                <div className="p-2 border-t border-slate-100 bg-slate-50">
                                    <button
                                        onClick={() => {
                                            setIsOpen(false);
                                            navigate(`/search?q=${encodeURIComponent(searchTerm)}${scope !== 'all' ? `&scope=${scope}` : ''}`);
                                        }}
                                        className="w-full py-2 flex items-center justify-center gap-2 text-sm text-blue-600 font-medium hover:bg-blue-50 rounded-lg transition-colors group"
                                    >
                                        Show more results for "{searchTerm}"
                                        <ArrowRight size={16} className="group-hover:translate-x-1 transition-transform" />
                                    </button>
                                </div>
                            </>
                        )}
                    </div>

                    {/* Right Panel: Insights */}
                    <SearchInsights
                        onRecentItemSelect={() => undefined}
                        recentItems={[]}
                        recentItemsLoading={false}
                    />
                </div>
            )}
        </div>
    );
};
