/**
 * SearchBar Component
 *
 * Interactive search input component with query suggestions support.
 *
 * ## Usage modes
 *
 * 1. **Full bundle** — SearchBar inside SearchProvider, inline dropdown enabled (default):
 *    ```tsx
 *    <SearchProvider client={client}>
 *      <SearchBar />
 *    </SearchProvider>
 *    ```
 *
 * 2. **Standalone (controlled)** — no suggestions fetch, no internal state drives the input:
 *    ```tsx
 *    <SearchBar value={query} onChange={setQuery} inlineDropdown={false} />
 *    ```
 *
 * 3. **Standalone (navigate)** — input only, Enter navigates to configurable URL:
 *    ```tsx
 *    <SearchBar
 *      inlineDropdown={false}
 *      submitMode="navigate"
 *      searchResultsUrl="/search?q="
 *    />
 *    ```
 *
 * 4. **Truly minimal** — no Seekora dependencies whatsoever (see SearchInput):
 *    ```tsx
 *    <SearchInput value={query} onChange={setQuery} onSubmit={handleSubmit} />
 *    ```
 */
import React, { useState, useRef, useEffect, useCallback } from "react";
import { useSearchContext } from "./SearchProvider";
import { useSearchState } from "../hooks/useSearchState";
import { useQuerySuggestions } from "../hooks/useQuerySuggestions";
import { log } from "@seekora-ai/ui-sdk-core";
import { clsx } from "clsx";
import { ensureSeekoraStyles } from "../styles/inject";
const DefaultSearchIcon = ({ size = 18 }) => (React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
    React.createElement("circle", { cx: "11", cy: "11", r: "8" }),
    React.createElement("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })));
const DefaultClearIcon = ({ size = 14 }) => (React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
    React.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
    React.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })));
export const SearchBar = ({ placeholder = "Search products", showSuggestions = true, debounceMs = 300, minQueryLength = 2, maxSuggestions = 10, onSearch, onQueryChange, onSuggestionSelect, onSearchStateChange, searchOptions, className, style, theme: customTheme, renderSuggestion, renderSearchIcon, showClearButton = true, renderClearIcon, showSubmitButton = false, renderSubmitButton, size = "medium", searchAsYouType = false, inlineDropdown = true, externalDropdownRef, submitMode = "spa", value, onChange, searchResultsUrl = "/search?q=", }) => {
    useEffect(() => {
        ensureSeekoraStyles();
    }, []);
    const { client, theme, enableAnalytics, autoTrackSearch } = useSearchContext();
    const { query, setQuery, search: triggerSearch, results, loading: searchLoading, error: searchError, } = useSearchState();
    const [isFocused, setIsFocused] = useState(false);
    const [selectedIndex, setSelectedIndex] = useState(-1);
    const inputRef = useRef(null);
    const containerRef = useRef(null);
    const dropdownRef = useRef(null);
    const isSearchingRef = useRef(false); // Flag to prevent blur handler from interfering
    const searchAsYouTypeRef = useRef(null);
    // Defect #36 — explicit debounce ref for cancellation on clear / unmount
    const debounceRef = useRef(null);
    const activeTimersRef = useRef(new Set());
    // Cleanup timers on unmount
    useEffect(() => {
        return () => {
            if (searchAsYouTypeRef.current)
                clearTimeout(searchAsYouTypeRef.current);
            if (debounceRef.current)
                clearTimeout(debounceRef.current);
            activeTimersRef.current.forEach(clearTimeout);
            activeTimersRef.current.clear();
        };
    }, []);
    // Defect #26 — pointer-down-outside to dismiss dropdown instead of setTimeout blur
    useEffect(() => {
        if (!isFocused)
            return;
        const handlePointerDown = (e) => {
            const target = e.target;
            if (containerRef.current?.contains(target) ||
                dropdownRef.current?.contains(target)) {
                return;
            }
            setIsFocused(false);
            setSelectedIndex(-1);
        };
        document.addEventListener("pointerdown", handlePointerDown);
        document.addEventListener("mousedown", handlePointerDown);
        return () => {
            document.removeEventListener("pointerdown", handlePointerDown);
            document.removeEventListener("mousedown", handlePointerDown);
        };
    }, [isFocused]);
    // When a controlled value prop is provided, use it as the effective query for the
    // input's displayed text. The internal state manager query continues to drive
    // search-on-Enter; they are kept in sync via handleInputChange below.
    const effectiveQuery = value !== undefined ? value : query;
    // Defect 1 — when SearchBar is UNCONTROLLED (no value prop), the state manager
    // is the single source of truth. External mutations (browser back-button, sibling
    // setQuery calls) change `query` in the state manager but the DOM input may lag
    // if a concurrent local edit is in flight. This effect forces the DOM input value
    // to match state-manager truth whenever `query` changes while we are uncontrolled.
    // In controlled mode (value prop provided) the consumer is responsible for sync.
    useEffect(() => {
        if (value === undefined &&
            inputRef.current &&
            inputRef.current.value !== query) {
            inputRef.current.value = query;
        }
    }, [query, value]);
    // Defect 2 — clamp effectiveMaxSuggestions so the dropdown never opens with 0
    // items. A consumer who passes maxSuggestions={0} by mistake would otherwise get
    // hasSuggestions=true from the raw suggestions array but displayedSuggestions=[]
    // from the slice, showing an empty open dropdown.
    const effectiveMaxSuggestions = Math.max(1, maxSuggestions);
    const { suggestions, loading: suggestionsLoading } = useQuerySuggestions({
        client,
        query: effectiveQuery,
        // STANDALONE-LEAK-1: only fetch suggestions when the inline dropdown is visible
        enabled: inlineDropdown &&
            showSuggestions &&
            effectiveQuery.length >= minQueryLength,
        debounceMs,
        maxSuggestions: effectiveMaxSuggestions,
    });
    // Defect 2 — use effectiveMaxSuggestions (clamped >= 1) so hasSuggestions and
    // displayedSuggestions are always in agreement: if the slice is empty, the
    // dropdown gate will be false and we won't open an empty dropdown.
    const displayedSuggestions = suggestions.slice(0, effectiveMaxSuggestions);
    // Expose search state to parent
    useEffect(() => {
        if (onSearchStateChange) {
            onSearchStateChange({
                results,
                loading: searchLoading,
                error: searchError,
            });
        }
    }, [results, searchLoading, searchError, onSearchStateChange]);
    useEffect(() => {
        if (results && onSearch) {
            onSearch(query, results);
        }
    }, [results, query, onSearch]);
    useEffect(() => {
        if (onQueryChange) {
            onQueryChange(query);
        }
    }, [query, onQueryChange]);
    const handleSearch = useCallback(async (searchQuery) => {
        // Cancel any pending searchAsYouType debounce — user explicitly submitted
        if (searchAsYouTypeRef.current) {
            clearTimeout(searchAsYouTypeRef.current);
            searchAsYouTypeRef.current = null;
        }
        // Allow empty queries - use empty string for search
        const query = searchQuery.trim();
        log.verbose("SearchBar: Triggering search", {
            query,
            originalQuery: searchQuery,
            isEmpty: !query,
        });
        // Set flag to prevent blur handler from interfering
        isSearchingRef.current = true;
        // Close suggestions first
        setIsFocused(false);
        setSelectedIndex(-1);
        // Update query in state manager (don't trigger search yet - we'll do it explicitly)
        setQuery(query, false);
        // Explicitly trigger search immediately (bypass debounce for Enter key)
        log.verbose("SearchBar: Calling search() directly");
        await triggerSearch();
        // Blur after a small delay to ensure search is triggered.
        // Timer IDs are tracked in activeTimersRef so they can be cancelled on unmount.
        const outerTimerId = setTimeout(() => {
            activeTimersRef.current.delete(outerTimerId);
            inputRef.current?.blur();
            const innerTimerId = setTimeout(() => {
                activeTimersRef.current.delete(innerTimerId);
                isSearchingRef.current = false;
            }, 50);
            activeTimersRef.current.add(innerTimerId);
        }, 100);
        activeTimersRef.current.add(outerTimerId);
    }, [setQuery, triggerSearch]);
    const handleSuggestionSelect = useCallback((suggestion) => {
        log.verbose("SearchBar: Suggestion selected", { suggestion });
        setSelectedIndex(-1);
        setIsFocused(false);
        inputRef.current?.blur();
        if (onSuggestionSelect) {
            onSuggestionSelect(suggestion);
        }
        // Update query and trigger search
        setQuery(suggestion);
    }, [onSuggestionSelect, setQuery]);
    const handleInputChange = useCallback((e) => {
        const next = e.target.value;
        // Cancel any pending debounce before scheduling a new one (Defect #36)
        if (debounceRef.current) {
            clearTimeout(debounceRef.current);
            debounceRef.current = null;
        }
        // STANDALONE-LEAK-2: notify controlled consumer first
        onChange?.(next);
        // In uncontrolled mode update the internal state manager.
        // In controlled mode the consumer drives the value via the `value` prop;
        // we must NOT overwrite their state or the input will appear to lag.
        if (value === undefined) {
            setQuery(next, false); // false = don't trigger search immediately
        }
        setSelectedIndex(-1);
        if (searchAsYouType && value === undefined) {
            if (searchAsYouTypeRef.current)
                clearTimeout(searchAsYouTypeRef.current);
            debounceRef.current = setTimeout(() => {
                debounceRef.current = null;
                searchAsYouTypeRef.current = null;
                triggerSearch();
            }, debounceMs);
            searchAsYouTypeRef.current = debounceRef.current;
        }
    }, [value, onChange, setQuery, searchAsYouType, debounceMs, triggerSearch]);
    const handleKeyDown = useCallback((e) => {
        switch (e.key) {
            case "ArrowDown":
                if (showSuggestions && displayedSuggestions.length > 0) {
                    e.preventDefault();
                    setSelectedIndex((prev) => prev < displayedSuggestions.length - 1 ? prev + 1 : prev);
                }
                break;
            case "ArrowUp":
                if (showSuggestions && displayedSuggestions.length > 0) {
                    e.preventDefault();
                    setSelectedIndex((prev) => (prev > 0 ? prev - 1 : -1));
                }
                break;
            case "Enter":
                if (submitMode === "spa") {
                    e.preventDefault();
                    e.stopPropagation();
                }
                // Only select suggestion if one is explicitly selected (selectedIndex >= 0)
                // Otherwise, search whatever is in the text box
                if (showSuggestions &&
                    displayedSuggestions.length > 0 &&
                    selectedIndex >= 0 &&
                    selectedIndex < displayedSuggestions.length) {
                    // Select suggestion if one is highlighted — always intercept
                    e.preventDefault();
                    e.stopPropagation();
                    handleSuggestionSelect(displayedSuggestions[selectedIndex].query);
                }
                else {
                    // Search whatever is in the text box (even if suggestions are shown)
                    const currentValue = inputRef.current?.value || effectiveQuery;
                    if (submitMode === "spa") {
                        handleSearch(currentValue);
                    }
                    else {
                        // navigate mode: close dropdown + call onSearch but do NOT
                        // preventDefault so ancestor form submits naturally.
                        setIsFocused(false);
                        setSelectedIndex(-1);
                        onSearch?.(currentValue, results);
                        // If there is no ancestor form, do a fallback navigation.
                        const ancestorForm = inputRef.current?.closest("form");
                        if (!ancestorForm) {
                            window.location.href =
                                (searchResultsUrl ?? "/search?q=") +
                                    encodeURIComponent(currentValue.trim());
                        }
                    }
                }
                break;
            case "Escape":
                setIsFocused(false);
                inputRef.current?.blur();
                break;
        }
    }, [
        displayedSuggestions,
        selectedIndex,
        query,
        effectiveQuery,
        showSuggestions,
        handleSearch,
        handleSuggestionSelect,
        searchResultsUrl,
    ]);
    const handleFocus = useCallback(() => {
        setIsFocused(true);
    }, []);
    // Defect #26 — blur handler no longer uses a setTimeout to dismiss the dropdown.
    // Dropdown dismissal is handled by the pointer-down-outside effect above.
    // This handler only covers keyboard Tab-away: focus leaves the container entirely.
    const handleBlur = useCallback((e) => {
        if (isSearchingRef.current)
            return;
        // relatedTarget is the element receiving focus next (null when focus leaves DOM)
        const relatedTarget = e.relatedTarget;
        if (relatedTarget && containerRef.current?.contains(relatedTarget)) {
            // Focus stayed inside the container (e.g. moved to the clear button)
            return;
        }
        // Focus left the component via keyboard Tab or similar
        setIsFocused(false);
        setSelectedIndex(-1);
    }, []);
    const defaultRenderSuggestion = (suggestion, index) => (React.createElement("div", { key: index }, suggestion));
    const searchBarTheme = customTheme || {};
    const isLoading = suggestionsLoading || searchLoading;
    // Show list when we have suggestions (including previous while loading) or when loading and showLoadingState
    const hasSuggestions = displayedSuggestions.length > 0;
    const showSuggestionsList = isFocused &&
        showSuggestions &&
        effectiveQuery.length >= minQueryLength &&
        hasSuggestions;
    // Get processing time from results
    const res = results;
    const processingTime = res?.processingTimeMS ||
        res?.data?.processingTimeMS ||
        res?.data?.data?.processingTimeMS;
    const hasClearBtn = showClearButton && effectiveQuery.length > 0;
    const handleClear = useCallback(() => {
        // Defect #36 — cancel pending debounce so old query can't resurface
        if (debounceRef.current) {
            clearTimeout(debounceRef.current);
            debounceRef.current = null;
        }
        if (searchAsYouTypeRef.current) {
            clearTimeout(searchAsYouTypeRef.current);
            searchAsYouTypeRef.current = null;
        }
        // STANDALONE-LEAK-2: in controlled mode notify consumer; don't touch internal state
        if (value !== undefined) {
            onChange?.("");
        }
        else {
            setQuery("", false);
            if (searchAsYouType) {
                triggerSearch();
            }
        }
        setSelectedIndex(-1);
        // Defect #26 — refocus input after clear (no preventDefault needed any more)
        inputRef.current?.focus();
    }, [value, onChange, setQuery, searchAsYouType, triggerSearch]);
    const handleSubmit = useCallback(() => {
        const currentValue = inputRef.current?.value || effectiveQuery;
        if (submitMode === "spa") {
            handleSearch(currentValue);
        }
        else {
            setIsFocused(false);
            setSelectedIndex(-1);
            onSearch?.(currentValue, results);
            const ancestorForm = inputRef.current?.closest("form");
            if (!ancestorForm) {
                window.location.href =
                    (searchResultsUrl ?? "/search?q=") +
                        encodeURIComponent(currentValue.trim());
            }
        }
    }, [
        effectiveQuery,
        handleSearch,
        submitMode,
        onSearch,
        results,
        searchResultsUrl,
    ]);
    // Defect #35 — dev-only warning when consumer wires both inline and external dropdown
    useEffect(() => {
        if (process.env.NODE_ENV !== "production" &&
            inlineDropdown !== false &&
            externalDropdownRef?.current != null) {
            console.warn("[SearchBar] Both inlineDropdown (true) and externalDropdownRef are active. " +
                "This will render two suggestion dropdowns simultaneously. " +
                "Pass inlineDropdown={false} to suppress the built-in dropdown.");
        }
    }, [inlineDropdown, externalDropdownRef]);
    return (React.createElement("div", { ref: containerRef, className: clsx("seekora-searchbar", searchBarTheme.container, className), "data-size": size, "data-focused": isFocused || undefined, style: style },
        React.createElement("div", { className: "seekora-searchbar__input-wrap" },
            React.createElement("span", { className: clsx("seekora-searchbar__icon", searchBarTheme.searchIcon), "aria-hidden": "true" }, renderSearchIcon ? renderSearchIcon() : React.createElement(DefaultSearchIcon, null)),
            React.createElement("input", { ref: inputRef, type: "text", value: effectiveQuery, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, placeholder: placeholder, className: clsx("seekora-searchbar__input", searchBarTheme.input, isFocused && searchBarTheme.inputFocused), "data-has-clear": hasClearBtn || undefined }),
            hasClearBtn && (React.createElement("button", { type: "button", onClick: handleClear, className: clsx("seekora-searchbar__clear", searchBarTheme.clearButton), "aria-label": "Clear search" }, renderClearIcon ? renderClearIcon() : React.createElement(DefaultClearIcon, null)))),
        showSubmitButton &&
            (renderSubmitButton ? (React.createElement("div", { className: "seekora-searchbar__submit-wrap", onClick: handleSubmit }, renderSubmitButton())) : (React.createElement("button", { type: "button", onClick: handleSubmit, className: clsx("seekora-searchbar__submit", searchBarTheme.submitButton) }, "Search"))),
        processingTime !== undefined && (React.createElement("span", { className: "seekora-searchbar__timing" },
            processingTime,
            "ms")),
        inlineDropdown && showSuggestionsList && (React.createElement("div", { ref: dropdownRef, className: clsx("seekora-searchbar__suggestions", searchBarTheme.suggestionsContainer), role: "listbox", "aria-label": "Search suggestions" },
            displayedSuggestions.length > 0 && (React.createElement(React.Fragment, null, displayedSuggestions.map((suggestion, index) => {
                const isSelected = index === selectedIndex;
                const renderFn = renderSuggestion || defaultRenderSuggestion;
                return (React.createElement("div", { key: index, role: "option", "aria-selected": isSelected, tabIndex: 0, className: clsx("seekora-searchbar__suggestion-item", searchBarTheme.suggestionItem, isSelected &&
                        "seekora-searchbar__suggestion-item--active", isSelected && searchBarTheme.suggestionItemActive), onClick: () => handleSuggestionSelect(suggestion.query), onKeyDown: (e) => {
                        if (e.key === "Enter" || e.key === " ") {
                            e.preventDefault();
                            handleSuggestionSelect(suggestion.query);
                        }
                    }, onMouseEnter: () => setSelectedIndex(index) }, renderFn(suggestion.query, index)));
            }))),
            !isLoading &&
                displayedSuggestions.length === 0 &&
                query.length >= minQueryLength && (React.createElement("div", { className: "seekora-searchbar__suggestion-empty" }, "No suggestions found"))))));
};
