"use client";

import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";
import Image from "next/image";
import { Loader2, Search, X } from "lucide-react";

import { apiFetch } from "@/lib/client/api";
import { buildSearchParams, cn, mediaHref, posterUrl } from "@/lib/utils";

interface Suggestion {
  id: number;
  name: string;
  poster: string | null;
  year: number | null;
  mediaType: "movie" | "tv";
}

/**
 * Instant search box.
 *
 * • debounced (280 ms) calls to the server-side TMDB proxy — the browser never
 *   sees the API token
 * • full keyboard support (↑ ↓ Enter Esc)
 * • filters are encoded in the URL (`/search?q=…&type=…`) so a result page is
 *   shareable and cacheable. Nothing is written to localStorage.
 */
export function SearchBox({
  className,
  autoFocus = false,
  onNavigate,
  size = "md",
}: {
  className?: string;
  autoFocus?: boolean;
  onNavigate?: () => void;
  size?: "sm" | "md" | "lg";
}) {
  const router = useRouter();
  const params = useSearchParams();
  const [value, setValue] = useState(params.get("q") ?? "");
  const [type, setType] = useState<"all" | "movie" | "tv">(
    (params.get("type") as "all" | "movie" | "tv") || "all",
  );
  const [results, setResults] = useState<Suggestion[]>([]);
  // The query that produced `results`. Comparing it with the live query derives
  // the spinner, so no effect ever has to synchronously flip a loading flag.
  const [settledQuery, setSettledQuery] = useState("");
  const [open, setOpen] = useState(false);
  const [highlight, setHighlight] = useState(-1);
  const rootRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const abortRef = useRef<AbortController | null>(null);

  /* debounce */
  useEffect(() => {
    const query = value.trim();
    if (query.length < 2) return; // results are derived as empty below

    const timer = window.setTimeout(async () => {
      abortRef.current?.abort();
      const controller = new AbortController();
      abortRef.current = controller;
      try {
        const data = await apiFetch<{ results: Suggestion[] }>(
          `/api/tmdb/search?q=${encodeURIComponent(query)}&type=${type}`,
          { signal: controller.signal },
        );
        setResults(data.results ?? []);
        setHighlight(-1);
      } catch {
        /* aborted or offline – keep the previous suggestions */
      } finally {
        // Settled even on failure, so the spinner always clears.
        if (abortRef.current === controller) setSettledQuery(query);
      }
    }, 280);

    return () => window.clearTimeout(timer);
  }, [type, value]);

  // A query shorter than two characters can never have results, so it is
  // derived at render time rather than cleared inside the effect.
  const trimmed = value.trim();
  const hasQuery = trimmed.length >= 2;
  const suggestions = hasQuery ? results : [];
  const loading = hasQuery && settledQuery !== trimmed;

  /* outside click */
  useEffect(() => {
    if (!open) return;
    const onDown = (event: MouseEvent) => {
      if (!rootRef.current?.contains(event.target as Node)) setOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, [open]);

  /* "/" focuses search, like every good media app */
  useEffect(() => {
    const onKey = (event: KeyboardEvent) => {
      const target = event.target as HTMLElement | null;
      const typing = target && /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName);
      if (event.key === "/" && !typing) {
        event.preventDefault();
        inputRef.current?.focus();
        setOpen(true);
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const submit = useCallback(
    (event?: React.FormEvent) => {
      event?.preventDefault();
      const query = value.trim();
      if (!query) return;
      setOpen(false);
      onNavigate?.();
      router.push(`/search${buildSearchParams({ q: query, type })}`);
    },
    [onNavigate, router, type, value],
  );

  const go = useCallback(
    (item: Suggestion) => {
      setOpen(false);
      onNavigate?.();
      router.push(mediaHref({ id: item.id, mediaType: item.mediaType }));
    },
    [onNavigate, router],
  );

  const onKeyDown = (event: React.KeyboardEvent) => {
    if (event.key === "Escape") {
      setOpen(false);
      inputRef.current?.blur();
      return;
    }
    if (!suggestions.length) return;
    if (event.key === "ArrowDown") {
      event.preventDefault();
      setHighlight((h) => (h + 1) % suggestions.length);
    } else if (event.key === "ArrowUp") {
      event.preventDefault();
      setHighlight((h) => (h - 1 + suggestions.length) % suggestions.length);
    } else if (event.key === "Enter" && highlight >= 0) {
      event.preventDefault();
      const item = suggestions[highlight];
      if (item) go(item);
    }
  };

  return (
    <div ref={rootRef} className={cn("relative", className)}>
      <form onSubmit={submit} role="search">
        <div
          className={cn(
            "flex items-center gap-2 rounded-pill border border-line bg-surface-2/70 backdrop-blur-md",
            "transition-colors duration-300 focus-within:border-white/30 focus-within:bg-surface-2",
            size === "lg" ? "h-12 px-4" : "h-10 px-3.5",
          )}
        >
          <Search className={cn("shrink-0 text-ink-muted", size === "lg" ? "h-5 w-5" : "h-4 w-4")} />
          <input
            ref={inputRef}
            type="search"
            value={value}
            autoFocus={autoFocus}
            onChange={(e) => {
              setValue(e.target.value);
              setOpen(true);
            }}
            onFocus={() => setOpen(true)}
            onKeyDown={onKeyDown}
            placeholder="Search movies, shows, anime…"
            aria-label="Search titles"
            autoComplete="off"
            spellCheck={false}
            className={cn(
              "w-full min-w-0 bg-transparent text-ink outline-none placeholder:text-ink-faint",
              size === "lg" ? "text-[15px]" : "text-[13px]",
            )}
          />
          {loading ? <Loader2 className="h-4 w-4 shrink-0 animate-spin text-ink-muted" /> : null}
          {value && !loading ? (
            <button
              type="button"
              onClick={() => {
                setValue("");
                inputRef.current?.focus();
              }}
              aria-label="Clear search"
              className="grid h-6 w-6 shrink-0 place-items-center rounded-full text-ink-muted transition hover:bg-white/10 hover:text-ink"
            >
              <X className="h-3.5 w-3.5" />
            </button>
          ) : null}
        </div>

        {/* type filter inside the search surface */}
        <div className="mt-2 flex items-center gap-1.5">
          {(["all", "movie", "tv"] as const).map((option) => (
            <button
              key={option}
              type="button"
              onClick={() => setType(option)}
              className={cn(
                "rounded-pill px-2.5 py-1 text-[11px] font-semibold transition-colors",
                type === option
                  ? "bg-white text-black"
                  : "bg-white/8 text-ink-muted hover:bg-white/14 hover:text-ink",
              )}
            >
              {option === "all" ? "All" : option === "movie" ? "Movies" : "TV Shows"}
            </button>
          ))}
        </div>
      </form>

      {open && hasQuery ? (
        <div className="animate-pop absolute left-0 right-0 top-[calc(100%+8px)] z-50 overflow-hidden rounded-md border border-line bg-surface/98 shadow-[0_28px_70px_-16px_rgba(0,0,0,0.9)] backdrop-blur-xl">
          {suggestions.length === 0 ? (
            <p className="px-4 py-5 text-center text-[13px] text-ink-muted">
              {loading ? "Searching…" : `No results for “${value.trim()}”`}
            </p>
          ) : (
            <>
              <ul className="max-h-[min(60vh,420px)] overflow-y-auto py-1.5">
                {suggestions.map((item, index) => {
                  const poster = posterUrl(item.poster, "w185");
                  return (
                    <li key={`${item.mediaType}-${item.id}`}>
                      <button
                        type="button"
                        onMouseEnter={() => setHighlight(index)}
                        onClick={() => go(item)}
                        className={cn(
                          "flex w-full items-center gap-3 px-3 py-2 text-left transition-colors",
                          highlight === index ? "bg-white/8" : "hover:bg-white/6",
                        )}
                      >
                        <span className="relative h-12 w-8 shrink-0 overflow-hidden rounded-xs bg-surface-3">
                          {poster ? (
                            <Image src={poster} alt="" fill sizes="32px" className="object-cover" />
                          ) : null}
                        </span>
                        <span className="min-w-0 flex-1">
                          <span className="block truncate text-[13px] font-semibold text-ink">
                            {item.name}
                          </span>
                          <span className="mt-0.5 block text-[11px] text-ink-muted">
                            {item.year ?? "—"} · {item.mediaType === "movie" ? "Movie" : "Series"}
                          </span>
                        </span>
                        <span className="hidden shrink-0 text-[11px] font-semibold uppercase tracking-wider text-ink-faint sm:block">
                          {item.mediaType === "movie" ? "Film" : "Series"}
                        </span>
                      </button>
                    </li>
                  );
                })}
              </ul>
              <button
                type="button"
                onClick={submit}
                className="flex w-full items-center justify-center gap-2 border-t border-line px-4 py-2.5 text-[12px] font-semibold text-ink-soft transition-colors hover:bg-white/6 hover:text-ink"
              >
                <Search className="h-3.5 w-3.5" />
                See all results for “{value.trim()}”
              </button>
            </>
          )}
        </div>
      ) : null}
    </div>
  );
}
