import { useCallback, useEffect, useState } from "react";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { DumpCard } from "../../components/DumpCard.tsx";
import { ErrorCard } from "../../components/ErrorCard.tsx";
import { API_URL, DEFAULT_PAGE_SIZE } from "../../config/api.ts";
import { useAuth } from "../../hooks/useAuth.ts";
import { useDumpListSync } from "../../hooks/useDumpListSync.ts";
import { useFeedCache } from "../../hooks/useFeedCache.ts";
import { useInfiniteScroll } from "../../hooks/useInfiniteScroll.ts";
import { useScrollSave } from "../../hooks/useScrollSave.ts";
import {
  deserializeDump,
  type Dump,
  hydrateDump,
  type PaginatedData,
  type RawDump,
  type User,
} from "../../model.ts";
import { friendlyFetchError } from "../../utils/apiError.ts";

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

type FollowedSection = "users" | "playlists";

interface FollowedFeedProps {
  voteCounts: Record<string, number>;
  myVotes: Set<string>;
  user: User;
  castVote: (id: string) => void;
  removeVote: (id: string) => void;
  deletedDumpIds: Set<string>;
}

// ── FollowedSubFeed ───────────────────────────────────────────────────────────

interface FollowedSubFeedProps {
  state: FeedState;
  voteCounts: Record<string, number>;
  myVotes: Set<string>;
  user: User;
  castVote: (id: string) => void;
  removeVote: (id: string) => void;
  deletedDumpIds: Set<string>;
  emptyMessage: string;
  onLoadMore: () => void;
}

function FollowedSubFeed({
  state,
  voteCounts,
  myVotes,
  user,
  castVote,
  removeVote,
  deletedDumpIds,
  emptyMessage,
  onLoadMore,
}: FollowedSubFeedProps) {
  const enabled = state.status === "loaded" && state.hasMore &&
    !state.loadingMore;
  const sentinelRef = useInfiniteScroll(onLoadMore, enabled);

  if (state.status === "loading") {
    return (
      <p className="index-status">
        <Trans>Loading…</Trans>
      </p>
    );
  }
  if (state.status === "error") {
    return <ErrorCard title={t`Failed to load`} message={state.error} />;
  }

  const visible = state.dumps.filter((d) => !deletedDumpIds.has(d.id));

  if (visible.length === 0) {
    return <p className="index-status">{emptyMessage}</p>;
  }

  return (
    <>
      <ul className="dump-feed">
        {visible.map((dump) => (
          <DumpCard
            key={dump.id}
            dump={dump}
            voteCount={voteCounts[dump.id] ?? dump.voteCount}
            voted={myVotes.has(dump.id)}
            canVote
            castVote={castVote}
            removeVote={removeVote}
            isOwner={user.id === dump.userId}
          />
        ))}
      </ul>
      <div ref={sentinelRef} />
      {state.loadingMore && (
        <p className="feed-loading-more">
          <Trans>Loading more…</Trans>
        </p>
      )}
    </>
  );
}

// ── FollowedFeed ──────────────────────────────────────────────────────────────

export function FollowedFeed({
  voteCounts,
  myVotes,
  user,
  castVote,
  removeVote,
  deletedDumpIds,
}: FollowedFeedProps) {
  const { token } = useAuth();

  const { cached: cachedUsers, saveState: saveUsers } = useFeedCache<Dump>(
    `feed:followed-users:${user.id}`,
    hydrateDump,
  );
  const { cached: cachedPlaylists, saveState: savePlaylists } = useFeedCache<
    Dump
  >(
    `feed:followed-playlists:${user.id}`,
    hydrateDump,
  );

  const [usersState, setUsersState] = useState<FeedState>(() =>
    cachedUsers
      ? {
        status: "loaded",
        dumps: cachedUsers.items,
        hasMore: cachedUsers.hasMore,
        page: cachedUsers.page,
        loadingMore: false,
      }
      : { status: "loading" }
  );

  const [playlistsState, setPlaylistsState] = useState<FeedState>(() =>
    cachedPlaylists
      ? {
        status: "loaded",
        dumps: cachedPlaylists.items,
        hasMore: cachedPlaylists.hasMore,
        page: cachedPlaylists.page,
        loadingMore: false,
      }
      : { status: "loading" }
  );

  const [section, setSection] = useState<FollowedSection>("users");

  // WS sync
  const setUsersItems = useCallback(
    (fn: (prev: Dump[]) => Dump[]) =>
      setUsersState((s) =>
        s.status !== "loaded" ? s : { ...s, dumps: fn(s.dumps) }
      ),
    [],
  );
  useDumpListSync(setUsersItems);

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

  // Fetch on mount if not cached
  useEffect(() => {
    if (!token) return;
    if (usersState.status === "loading") {
      fetch(
        `${API_URL}/api/follows/feed/users?page=1&limit=${DEFAULT_PAGE_SIZE}`,
        { headers: { Authorization: `Bearer ${token}` } },
      )
        .then((r) => r.json())
        .then((body) => {
          const { items, hasMore } = body.data as PaginatedData<RawDump>;
          setUsersState({
            status: "loaded",
            dumps: items.map(deserializeDump),
            hasMore,
            page: 1,
            loadingMore: false,
          });
        })
        .catch((err) =>
          setUsersState({ status: "error", error: friendlyFetchError(err) })
        );
    }
    if (playlistsState.status === "loading") {
      fetch(
        `${API_URL}/api/follows/feed/playlists?page=1&limit=${DEFAULT_PAGE_SIZE}`,
        { headers: { Authorization: `Bearer ${token}` } },
      )
        .then((r) => r.json())
        .then((body) => {
          const { items, hasMore } = body.data as PaginatedData<RawDump>;
          setPlaylistsState({
            status: "loaded",
            dumps: items.map(deserializeDump),
            hasMore,
            page: 1,
            loadingMore: false,
          });
        })
        .catch((err) =>
          setPlaylistsState({
            status: "error",
            error: friendlyFetchError(err),
          })
        );
    }
  }, [token, usersState.status, playlistsState.status]);

  // Scroll save
  useScrollSave(
    usersState.status === "loaded",
    useCallback((y) => {
      if (usersState.status !== "loaded") return;
      saveUsers(usersState.dumps, usersState.page, usersState.hasMore, y);
    }, [usersState, saveUsers]),
  );
  useScrollSave(
    playlistsState.status === "loaded",
    useCallback((y) => {
      if (playlistsState.status !== "loaded") return;
      savePlaylists(
        playlistsState.dumps,
        playlistsState.page,
        playlistsState.hasMore,
        y,
      );
    }, [playlistsState, savePlaylists]),
  );

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

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

  return (
    <div className="followed-feed">
      <div className="feed-sort followed-sub-nav">
        <button
          type="button"
          className={`feed-sort-btn${section === "users" ? " active" : ""}`}
          onClick={() => setSection("users")}
        >
          <Trans>From people</Trans>
        </button>
        <button
          type="button"
          className={`feed-sort-btn${section === "playlists" ? " active" : ""}`}
          onClick={() => setSection("playlists")}
        >
          <Trans>From playlists</Trans>
        </button>
      </div>

      {section === "users" && (
        <FollowedSubFeed
          state={usersState}
          voteCounts={voteCounts}
          myVotes={myVotes}
          user={user}
          castVote={castVote}
          removeVote={removeVote}
          deletedDumpIds={deletedDumpIds}
          emptyMessage={t`Follow some users to see their dumps here.`}
          onLoadMore={loadMoreUsers}
        />
      )}

      {section === "playlists" && (
        <FollowedSubFeed
          state={playlistsState}
          voteCounts={voteCounts}
          myVotes={myVotes}
          user={user}
          castVote={castVote}
          removeVote={removeVote}
          deletedDumpIds={deletedDumpIds}
          emptyMessage={t`Follow some public playlists to see their dumps here.`}
          onLoadMore={loadMorePlaylists}
        />
      )}
    </div>
  );
}
