import {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { useLocation } from "react-router";

import { AppHeader } from "../components/AppHeader.tsx";
import { PresenceRow } from "../components/PresenceRow.tsx";
import { FeedTabBar } from "../components/FeedTabBar.tsx";
import { type FeedTab, FEED_TABS } from "../config/feedTabs.ts";
import { useTabParam } from "../hooks/useTabParam.ts";

import { API_URL, DEFAULT_PAGE_SIZE } from "../config/api.ts";

import {
  deserializeDump,
  type Dump,
  hydrateDump,
  type PaginatedData,
  type RawDump,
} from "../model.ts";

import { friendlyFetchError } from "../utils/apiError.ts";
import { useFeedCache } from "../hooks/useFeedCache.ts";
import { useScrollSave } from "../hooks/useScrollSave.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";

import { HotFeed } from "./index/HotFeed.tsx";
import { NewFeed } from "./index/NewFeed.tsx";
import { JournalFeed } from "./index/JournalFeed.tsx";
import { FollowedFeed } from "./index/FollowedFeed.tsx";
import type { MainFeedProps } from "./index/types.ts";

type DumpsState =
  | { status: "loading" }
  | { status: "error"; error: string }
  | {
    status: "loaded";
    dumps: Dump[];
    hasMore: boolean;
    page: number;
    loadingMore: boolean;
  };

export function Index() {
  const location = useLocation();
  const justDeletedId = (location.state as { deletedDumpId?: string } | null)
    ?.deletedDumpId;

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

  // ── Main feed state ──

  const { cached, saveState } = useFeedCache<Dump>(
    `feed:index:${user?.id ?? "guest"}`,
    hydrateDump,
  );
  const [dumpsState, setDumpsState] = useState<DumpsState>(() =>
    cached
      ? {
        status: "loaded",
        dumps: cached.items,
        hasMore: cached.hasMore,
        page: cached.page,
        loadingMore: false,
      }
      : { status: "loading" }
  );
  const mainFetchDone = useRef(false);

  const [tab] = useTabParam<FeedTab>(FEED_TABS, "hot");

  // Web Share Target: Android share sheet navigates to /?share_url=...
  const searchParams = new URLSearchParams(location.search);
  const shareUrl = searchParams.get("share_url") ??
    searchParams.get("share_text") ?? "";

  useEffect(() => {
    if (!shareUrl) return;
    // Clean share params from the URL so a refresh doesn't re-open the modal.
    // The active tab already lives in the pathname (e.g. /~/new), so just drop
    // the query string.
    globalThis.history.replaceState({}, "", location.pathname);
  }, [shareUrl, location.pathname]);

  // ── Main feed fetch ──

  useEffect(() => {
    if (mainFetchDone.current || cached) return;
    mainFetchDone.current = true;
    const controller = new AbortController();
    (async () => {
      try {
        const res = await fetch(
          `${API_URL}/api/dumps/?page=1&limit=${DEFAULT_PAGE_SIZE}`,
          {
            signal: controller.signal,
            headers: token ? { Authorization: `Bearer ${token}` } : {},
          },
        );
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const body = await res.json();
        const { items, hasMore } = body.data as PaginatedData<RawDump>;
        setDumpsState({
          status: "loaded",
          dumps: items.map(deserializeDump),
          hasMore,
          page: 1,
          loadingMore: false,
        });
      } catch (err) {
        if ((err as Error).name === "AbortError") return;
        setDumpsState({ status: "error", error: friendlyFetchError(err) });
      }
    })();
    return () => {
      mainFetchDone.current = false;
      controller.abort();
    };
  }, [cached, token]);

  // ── WS sync for main feed ──

  const setDumpsItems = useCallback(
    (fn: (prev: Dump[]) => Dump[]) =>
      setDumpsState((s) =>
        s.status !== "loaded" ? s : { ...s, dumps: fn(s.dumps) }
      ),
    [],
  );
  useDumpListSync(setDumpsItems);

  // ── Load more ──

  const loadMore = useCallback(() => {
    if (
      dumpsState.status !== "loaded" || !dumpsState.hasMore ||
      dumpsState.loadingMore
    ) return;
    const nextPage = dumpsState.page + 1;
    setDumpsState((s) =>
      s.status === "loaded" ? { ...s, loadingMore: true } : s
    );
    fetch(`${API_URL}/api/dumps/?page=${nextPage}&limit=${DEFAULT_PAGE_SIZE}`, {
      headers: token ? { Authorization: `Bearer ${token}` } : {},
    })
      .then((r) => r.json())
      .then((body) => {
        const { items, hasMore } = body.data as PaginatedData<RawDump>;
        setDumpsState((s) =>
          s.status === "loaded"
            ? {
              ...s,
              dumps: [...s.dumps, ...items.map(deserializeDump)],
              hasMore,
              page: nextPage,
              loadingMore: false,
            }
            : s
        );
      })
      .catch(() =>
        setDumpsState((s) =>
          s.status === "loaded" ? { ...s, loadingMore: false } : s
        )
      );
  }, [dumpsState, token]);

  // ── Scroll save / restore ──

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

  useScrollSave(
    dumpsState.status === "loaded",
    useCallback((y) => {
      if (dumpsState.status !== "loaded") return;
      saveState(dumpsState.dumps, dumpsState.page, dumpsState.hasMore, y);
    }, [dumpsState, saveState]),
  );

  const scrollRestored = useRef(false);
  useLayoutEffect(() => {
    if (cached?.scrollY == null || scrollRestored.current) return;
    if (dumpsState.status === "loaded") {
      globalThis.scrollTo(0, cached.scrollY);
      scrollRestored.current = true;
    }
  }, [dumpsState.status, cached]);

  // ── Derived values ──

  const loading = dumpsState.status === "loading";
  const error = dumpsState.status === "error" ? dumpsState.error : null;
  const dumps = useMemo(
    () => dumpsState.status === "loaded" ? dumpsState.dumps : [],
    [dumpsState],
  );
  const loadingMore = dumpsState.status === "loaded" && dumpsState.loadingMore;
  const hasMore = dumpsState.status === "loaded" ? dumpsState.hasMore : false;

  const restIds = useMemo(() => new Set(dumps.map((d) => d.id)), [dumps]);
  const combined = useMemo(
    () =>
      [...recentDumps.filter((d) => !restIds.has(d.id)), ...dumps].filter(
        (d) => !deletedDumpIds.has(d.id) && d.id !== justDeletedId,
      ),
    [recentDumps, restIds, dumps, deletedDumpIds, justDeletedId],
  );

  const mainFeedProps: MainFeedProps = {
    dumps: combined,
    loading,
    error,
    hasMore,
    loadingMore,
    sentinelRef,
    voteCounts,
    myVotes,
    user,
    castVote,
    removeVote,
  };

  // ── Render ──

  return (
    <div className="index-page">
      <AppHeader
        centerSlot={
          <div className="header-center-slot">
            <PresenceRow />
            <FeedTabBar />
          </div>
        }
        disableNew={dumpsState.status === "error"}
        initialDumpUrl={shareUrl || undefined}
      />

      <div className="index-below-header">
        <FeedTabBar />
        <PresenceRow />
      </div>

      {tab === "hot" && <HotFeed {...mainFeedProps} />}
      {tab === "new" && <NewFeed {...mainFeedProps} />}
      {tab === "journal" && <JournalFeed {...mainFeedProps} />}
      {tab === "followed" && user && (
        <FollowedFeed
          voteCounts={voteCounts}
          myVotes={myVotes}
          user={user}
          castVote={castVote}
          removeVote={removeVote}
          deletedDumpIds={deletedDumpIds}
        />
      )}
    </div>
  );
}
