import { useCallback, useEffect, useRef, useState } from "react";
import { Link, useSearchParams } from "react-router";
import { AppHeader } from "../components/AppHeader.tsx";
import { SearchBar } from "../components/SearchBar.tsx";
import { DumpCard } from "../components/DumpCard.tsx";
import { PlaylistCard } from "../components/PlaylistCard.tsx";
import { ErrorCard } from "../components/ErrorCard.tsx";
import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
import {
  deserializeDump,
  deserializePlaylist,
  deserializePublicUser,
  type Dump,
  type Playlist,
  type PublicUser,
  type RawDump,
  type RawPlaylist,
  type RawPublicUser,
} from "../model.ts";
import { DEFAULT_PAGE_SIZE, SEARCH_URL } from "../config/api.ts";

type Tab = "dumps" | "users" | "playlists";

type SearchState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "error"; error: string }
  | {
    status: "loaded";
    q: string;
    dumps: {
      items: Dump[];
      total: number;
      hasMore: boolean;
      page: number;
      loadingMore: boolean;
    };
    users: PublicUser[];
    playlists: Playlist[];
  };

export function Search() {
  const [searchParams, setSearchParams] = useSearchParams();
  const q = searchParams.get("q") ?? "";
  const tab = (searchParams.get("tab") ?? "dumps") as Tab;

  const { token, user } = useAuth();
  const { voteCounts, myVotes, castVote, removeVote } = useWS();

  const [state, setState] = useState<SearchState>({ status: "idle" });
  const abortRef = useRef<AbortController | null>(null);

  const fetchSearch = useCallback(async (query: string, page: number) => {
    if (!query.trim()) {
      setState({ status: "idle" });
      return;
    }

    if (page === 1) {
      setState({ status: "loading" });
    }

    abortRef.current?.abort();
    const controller = new AbortController();
    abortRef.current = controller;

    try {
      const url = new URL(SEARCH_URL);
      url.searchParams.set("q", query);
      url.searchParams.set("page", String(page));
      url.searchParams.set("limit", String(DEFAULT_PAGE_SIZE));

      const res = await fetch(url.toString(), {
        signal: controller.signal,
        headers: token ? { Authorization: `Bearer ${token}` } : {},
      });

      if (!res.ok) throw new Error(`Search failed (${res.status})`);

      const body = await res.json() as {
        success: true;
        data: {
          dumps: { items: RawDump[]; total: number; hasMore: boolean };
          users: RawPublicUser[];
          playlists: RawPlaylist[];
        };
      };

      const { data } = body;

      if (page === 1) {
        setState({
          status: "loaded",
          q: query,
          dumps: {
            items: data.dumps.items.map(deserializeDump),
            total: data.dumps.total,
            hasMore: data.dumps.hasMore,
            page: 1,
            loadingMore: false,
          },
          users: data.users.map(deserializePublicUser),
          playlists: data.playlists.map(deserializePlaylist),
        });
      } else {
        setState((prev) => {
          if (prev.status !== "loaded") return prev;
          return {
            ...prev,
            dumps: {
              ...prev.dumps,
              items: [...prev.dumps.items, ...data.dumps.items.map(deserializeDump)],
              hasMore: data.dumps.hasMore,
              page,
              loadingMore: false,
            },
          };
        });
      }
    } catch (err) {
      if ((err as Error).name === "AbortError") return;
      setState({ status: "error", error: (err as Error).message });
    }
  }, [token]);

  useEffect(() => {
    fetchSearch(q, 1);
    return () => abortRef.current?.abort();
  }, [q, fetchSearch]);

  const loadMore = useCallback(() => {
    if (state.status !== "loaded" || !state.dumps.hasMore || state.dumps.loadingMore) return;
    setState((prev) => {
      if (prev.status !== "loaded") return prev;
      return { ...prev, dumps: { ...prev.dumps, loadingMore: true } };
    });
    fetchSearch(q, state.dumps.page + 1);
  }, [state, q, fetchSearch]);

  const sentinelRef = useInfiniteScroll(
    loadMore,
    state.status === "loaded" && tab === "dumps" && state.dumps.hasMore && !state.dumps.loadingMore,
  );

  function setTab(t: Tab) {
    setSearchParams((prev) => {
      const next = new URLSearchParams(prev);
      next.set("tab", t);
      return next;
    }, { replace: true });
  }

  const dumpCount = state.status === "loaded" ? state.dumps.total : null;
  const userCount = state.status === "loaded" ? state.users.length : null;
  const playlistCount = state.status === "loaded" ? state.playlists.length : null;

  function tabLabel(t: Tab, count: number | null) {
    const label = t === "dumps" ? "Dumps" : t === "users" ? "Users" : "Playlists";
    return count !== null ? `${label} (${count})` : label;
  }

  return (
    <div className="page-shell">
      <AppHeader centerSlot={<SearchBar />} />
      <main className="search-page">
        {q && (
          <div className="search-tabs">
            {(["dumps", "users", "playlists"] as Tab[]).map((t) => (
              <button
                key={t}
                type="button"
                className={`feed-sort-btn${tab === t ? " active" : ""}`}
                onClick={() => setTab(t)}
              >
                {tabLabel(t, t === "dumps" ? dumpCount : t === "users" ? userCount : playlistCount)}
              </button>
            ))}
          </div>
        )}

        {state.status === "idle" && (
          <p className="search-page-empty">Enter a query to search.</p>
        )}

        {state.status === "loading" && (
          <p className="search-page-empty">Searching…</p>
        )}

        {state.status === "error" && (
          <ErrorCard title="Search failed" message={state.error} />
        )}

        {state.status === "loaded" && tab === "dumps" && (
          <>
            {state.dumps.items.length === 0
              ? <p className="search-page-empty">No dumps match "{q}".</p>
              : (
                <ul className="dump-feed">
                  {state.dumps.items.map((dump) => (
                    <DumpCard
                      key={dump.id}
                      dump={dump}
                      voteCount={voteCounts[dump.id] ?? dump.voteCount}
                      voted={myVotes.has(dump.id)}
                      canVote={!!user}
                      castVote={castVote}
                      removeVote={removeVote}
                      isOwner={user?.id === dump.userId}
                    />
                  ))}
                </ul>
              )}
            <div ref={sentinelRef} />
            {state.dumps.loadingMore && <p className="feed-loading-more">Loading more…</p>}
            {!state.dumps.hasMore && state.dumps.items.length > 0 && (
              <p className="feed-end">You've reached the end.</p>
            )}
          </>
        )}

        {state.status === "loaded" && tab === "users" && (
          state.users.length === 0
            ? <p className="search-page-empty">No users match "{q}".</p>
            : (
              <ul className="user-results">
                {state.users.map((u) => (
                  <li key={u.id}>
                    <Link to={`/users/${u.username}`} className="user-result-item">
                      <span className="user-result-name">@{u.username}</span>
                      {u.description && (
                        <span className="user-result-description">{u.description}</span>
                      )}
                    </Link>
                  </li>
                ))}
              </ul>
            )
        )}

        {state.status === "loaded" && tab === "playlists" && (
          state.playlists.length === 0
            ? <p className="search-page-empty">No playlists match "{q}".</p>
            : (
              <ul className="dump-feed">
                {state.playlists.map((p) => (
                  <PlaylistCard key={p.id} playlist={p} />
                ))}
              </ul>
            )
        )}
      </main>
    </div>
  );
}
