"use client";

import { useState, useEffect, useRef, useCallback, useMemo } from "react";
import { useQuery } from "convex/react";
import { useRouter } from "next/navigation";
import { api } from "../../../../../convex/_generated/api";
import { MIN_SEARCH_LENGTH, DEBOUNCE_MS, MAX_TYPEAHEAD_RESULTS } from "./constants";
import type { SearchResultItem } from "./types";
import { getProductUrl } from "../shop/productMapper";

function useDebouncedValue<T>(value: T, delayMs: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delayMs);
    return () => clearTimeout(timer);
  }, [value, delayMs]);
  return debounced;
}

export function useProductSearch() {
  const router = useRouter();
  const [query, setQueryState] = useState("");
  const [categorySlug, setCategorySlug] = useState<string | undefined>(undefined);
  const [isOpen, setIsOpen] = useState(false);
  const [selectedIndex, setSelectedIndex] = useState(-1);

  const debouncedQuery = useDebouncedValue(query, DEBOUNCE_MS);

  const inputRef = useRef<HTMLInputElement>(null);
  const panelRef = useRef<HTMLDivElement>(null);

  const shouldSearch = debouncedQuery.trim().length >= MIN_SEARCH_LENGTH;

  const rawResults = useQuery(
    api.products.searchTypeahead,
    shouldSearch
      ? {
          query: debouncedQuery.trim(),
          parentCategorySlug: categorySlug,
          limit: MAX_TYPEAHEAD_RESULTS,
        }
      : "skip",
  );

  const isLoading = shouldSearch && rawResults === undefined;
  const results: SearchResultItem[] = useMemo(() => rawResults ?? [], [rawResults]);
  const isEmpty = shouldSearch && rawResults !== undefined && rawResults.length === 0;
  const showResults = isOpen && shouldSearch;
  const showMinCharsHint = isOpen && query.length > 0 && query.length < MIN_SEARCH_LENGTH;

  // 6.1.6 — surface a stalled state after 5s of continuous loading
  const [isStalled, setIsStalled] = useState(false);
  useEffect(() => {
    if (!isLoading) {
      setIsStalled(false);
      return;
    }
    const timer = setTimeout(() => setIsStalled(true), 5000);
    return () => clearTimeout(timer);
  }, [isLoading]);

  const setQuery = useCallback((q: string) => {
    const capped = q.slice(0, 100);
    setQueryState(capped);
    if (capped.length > 0) {
      setIsOpen(true);
    } else {
      setIsOpen(false);
      setSelectedIndex(-1);
    }
  }, []);

  useEffect(() => {
    setSelectedIndex(-1);
  }, [results]);

  const open = useCallback(() => setIsOpen(true), []);

  const close = useCallback(() => {
    setIsOpen(false);
    setSelectedIndex(-1);
  }, []);

  // 6.2.2 — Tab from input closes the panel; delay lets mousedown on a result fire first
  const handleBlur = useCallback(() => {
    setTimeout(() => close(), 150);
  }, [close]);

  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      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") {
        if (selectedIndex >= 0 && results[selectedIndex]) {
          router.push(getProductUrl(results[selectedIndex]));
          close();
        }
      } else if (e.key === "Escape") {
        close();
        inputRef.current?.blur();
      }
    },
    [results, selectedIndex, router, close],
  );

  return {
    query,
    setQuery,
    categorySlug,
    setCategorySlug,
    results,
    isLoading,
    isStalled,
    isEmpty,
    isOpen,
    open,
    close,
    selectedIndex,
    setSelectedIndex,
    handleKeyDown,
    handleBlur,
    inputRef,
    panelRef,
    showResults,
    showMinCharsHint,
  };
}
