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

import { Avatar } from "../components/Avatar.tsx";
import { DumpCard } from "../components/DumpCard.tsx";
import { AppHeader } from "../components/AppHeader.tsx";

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

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

import { useFeedCache } from "../hooks/useFeedCache.ts";

import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";

const PAGE_SIZE = 20;

// After JSON roundtrip, createdAt is a string — re-parse it
const hydrateDump = (raw: Dump): Dump =>
  deserializeDump(raw as unknown as RawDump);

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

type SortMode = "new" | "hot";

function hotScore(dump: Dump): number {
  const ageHours = (Date.now() - dump.createdAt.getTime()) / 3_600_000;
  return (dump.voteCount + 1) / Math.pow(ageHours + 2, 1.5);
}

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

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

  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 [sort, setSort] = useState<SortMode>("hot");

  useEffect(() => {
    if (cached) return; // restored from cache, skip fetch
    (async () => {
      try {
        const res = await fetch(`${API_URL}/api/dumps/?page=1&limit=${PAGE_SIZE}`, {
          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) {
        setDumpsState({
          status: "error",
          error: err instanceof Error ? err.message : "Failed to load",
        });
      }
    })();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  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=${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]);

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

  // Save scroll position + loaded state to sessionStorage on scroll
  useEffect(() => {
    if (dumpsState.status !== "loaded") return;
    let timer: ReturnType<typeof setTimeout>;
    const onScroll = () => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        if (dumpsState.status === "loaded") {
          saveState(dumpsState.dumps, dumpsState.page, dumpsState.hasMore, window.scrollY);
        }
      }, 100);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => { window.removeEventListener("scroll", onScroll); clearTimeout(timer); };
  }, [dumpsState, saveState]);

  // Restore scroll position after cache restoration
  const scrollRestored = useRef(false);
  useLayoutEffect(() => {
    if (cached?.scrollY == null || scrollRestored.current) return;
    if (dumpsState.status === "loaded") {
      window.scrollTo(0, cached.scrollY);
      scrollRestored.current = true;
    }
  // cached is stable (read once), safe to omit
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [dumpsState.status]);

  const loading = dumpsState.status === "loading";
  const error = dumpsState.status === "error" ? dumpsState.error : null;
  const dumps = dumpsState.status === "loaded" ? dumpsState.dumps : [];
  const loadingMore = dumpsState.status === "loaded" && dumpsState.loadingMore;

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

  const sortedDumps = [...combined].sort(
    sort === "hot"
      ? (a, b) => hotScore(b) - hotScore(a)
      : (a, b) => b.createdAt.getTime() - a.createdAt.getTime(),
  );

  const presenceRow = (
    <div className="index-presence">
      {onlineUsers.map((u) => (
        <Link
          key={u.userId}
          to={`/users/${u.username}`}
          title={u.username}
          className="index-presence-avatar"
        >
          <Avatar
            userId={u.userId}
            username={u.username}
            hasAvatar={u.hasAvatar}
            size={32}
          />
        </Link>
      ))}
    </div>
  );

  const sortButtons = !loading && !error && combined.length > 0 && (
    <div className="feed-sort">
      <button
        type="button"
        className={`feed-sort-btn${sort === "hot" ? " active" : ""}`}
        onClick={() => setSort("hot")}
      >
        Hot
      </button>
      <button
        type="button"
        className={`feed-sort-btn${sort === "new" ? " active" : ""}`}
        onClick={() => setSort("new")}
      >
        New
      </button>
    </div>
  );

  return (
    <div className="index-page">
      <AppHeader
        centerSlot={
          <div className="header-center-slot">
            {presenceRow}
            {sortButtons}
          </div>
        }
      />

      {/* Shown only on narrow viewports */}
      <div className="index-below-header">
        {sortButtons}
        {presenceRow}
      </div>

      {loading && <p className="index-status">Loading…</p>}
      {error && <p className="index-status index-status--error">{error}</p>}

      {!loading && !error && combined.length === 0 && (
        <p className="index-status">No dumps yet. Be the first!</p>
      )}

      {!loading && !error && combined.length > 0 && (
        <ul className="dump-feed">
          {sortedDumps.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} />
      {loadingMore && <p className="feed-loading-more">Loading more…</p>}
    </div>
  );
}
