import { useCallback, useEffect, useRef, useState } from "react";
import { useInfiniteQuery, useQuery, useQueryClient } from "@tanstack/react-query";
import { api, type FeedFilters, type Video } from "../lib/api";
import { notify } from "../lib/notifications";
import VideoCard from "./VideoCard";
import PlayerModal from "./PlayerModal";

const PAGE = 60;

function matchesView(status: string, show: string): boolean {
  switch (show) {
    case "hidden":
      return status === "hidden";
    case "watched":
      return status === "watched";
    case "saved":
      return status === "saved";
    case "unwatched":
      return status !== "watched" && status !== "hidden";
    default:
      return status !== "hidden"; // all
  }
}

export default function Feed({
  filters,
  setFilters,
  view,
  canRead,
  onOpenWizard,
}: {
  filters: FeedFilters;
  setFilters: (f: FeedFilters) => void;
  view: "grid" | "list";
  canRead: boolean;
  onOpenWizard: () => void;
}) {
  const [overrides, setOverrides] = useState<Record<string, string>>({});
  const [activeVideo, setActiveVideo] = useState<Video | null>(null);
  const qc = useQueryClient();

  const query = useInfiniteQuery({
    queryKey: ["feed", filters],
    queryFn: ({ pageParam }) => api.feed(filters, pageParam as number, PAGE),
    initialPageParam: 0,
    getNextPageParam: (last) => (last.has_more ? last.offset + last.limit : undefined),
  });

  // Drop optimistic status overrides when filters change OR fresh server data
  // arrives — after a refetch the server is authoritative, so a stale override
  // (e.g. a video reverted to "new" from the notification center) won't keep it
  // filtered out of the current view.
  useEffect(() => setOverrides({}), [filters]);
  useEffect(() => setOverrides({}), [query.dataUpdatedAt]);

  const countQuery = useQuery({
    queryKey: ["feed-count", filters],
    queryFn: () => api.feedCount(filters),
    staleTime: 30_000,
  });

  const sentinel = useRef<HTMLDivElement | null>(null);
  const { hasNextPage, isFetchingNextPage, fetchNextPage } = query;
  useEffect(() => {
    const el = sentinel.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage();
        }
      },
      { rootMargin: "1500px" } // prefetch the next page well before the bottom is in view
    );
    io.observe(el);
    return () => io.disconnect();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  // Keep the loaded videos in a ref so onState can stay referentially stable
  // (it needs the list only to look up a title for the notification). A stable
  // onState lets memo(VideoCard) skip re-rendering existing cards on page append.
  const loadedRef = useRef<Video[]>([]);

  const onState = useCallback(
    (id: string, status: string) => {
      setOverrides((o) => ({ ...o, [id]: status }));
      // Refetch once the server has the change so other views (e.g. Hidden) are in sync.
      api
        .setState(id, status)
        .then(() => qc.invalidateQueries({ queryKey: ["feed"] }))
        .catch(() => {});
      if (status === "hidden") {
        const v = loadedRef.current.find((x) => x.id === id);
        notify({
          message: v?.title ? `Hidden “${v.title}”` : "Video hidden",
          action: { label: "Undo", onClick: () => onState(id, "new") },
          meta: {
            kind: "video-hidden",
            videoId: id,
            title: v?.title ?? "this video",
            channelId: v?.channel_id ?? "",
            channelName: v?.channel_title ?? "",
          },
        });
      } else if (status === "watched") {
        const v = loadedRef.current.find((x) => x.id === id);
        notify({
          message: v?.title ? `Marked watched “${v.title}”` : "Marked watched",
          action: { label: "Unwatch", onClick: () => onState(id, "new") },
          meta: { kind: "video-watched", videoId: id, title: v?.title ?? "this video" },
        });
      }
    },
    [qc]
  );

  const onChannelFilter = useCallback(
    (channelId: string, channelName: string) => {
      setFilters({ ...filters, channelId, channelName });
    },
    [filters, setFilters]
  );

  const loaded: Video[] = (query.data?.pages ?? []).flatMap((p) => p.items);
  loadedRef.current = loaded;
  const items: Video[] = loaded
    .map((v) => (overrides[v.id] ? { ...v, status: overrides[v.id] } : v))
    .filter((v) => matchesView(v.status, filters.show));

  if (query.isLoading) return <div className="p-8 text-muted">Loading feed…</div>;
  if (query.isError) return <div className="p-8 text-muted">Couldn't load the feed.</div>;
  if (items.length === 0) {
    if (!canRead)
      return (
        <div className="p-8 grid place-items-center text-center">
          <div className="max-w-sm">
            <h2 className="text-lg font-semibold">Your feed is empty</h2>
            <p className="text-sm text-muted mt-2 mb-4">
              Connect your YouTube account to import your subscriptions and build your feed.
            </p>
            <button
              onClick={onOpenWizard}
              className="inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-xl font-semibold bg-accent text-accent-fg hover:opacity-90 transition"
            >
              Set up my feed
            </button>
          </div>
        </div>
      );
    return <div className="p-8 text-muted">No videos match these filters.</div>;
  }

  return (
    <div className="p-4">
      <div className="pb-3 text-sm text-muted">
        {countQuery.data
          ? `${countQuery.data.count.toLocaleString()} video${countQuery.data.count === 1 ? "" : "s"}`
          : " "}
      </div>
      {view === "grid" ? (
        <div className="grid gap-4 grid-cols-[repeat(auto-fill,minmax(260px,1fr))]">
          {items.map((v) => (
            <VideoCard
              key={v.id}
              video={v}
              view="grid"
              onState={onState}
              onChannelFilter={onChannelFilter}
              onOpen={setActiveVideo}
            />
          ))}
        </div>
      ) : (
        <div className="max-w-4xl mx-auto flex flex-col gap-1">
          {items.map((v) => (
            <VideoCard
              key={v.id}
              video={v}
              view="list"
              onState={onState}
              onChannelFilter={onChannelFilter}
              onOpen={setActiveVideo}
            />
          ))}
        </div>
      )}
      {activeVideo && (
        <PlayerModal
          video={activeVideo}
          onClose={() => setActiveVideo(null)}
          onState={onState}
        />
      )}
      <div ref={sentinel} className="h-10" />
      {isFetchingNextPage && (
        <div className="text-center text-muted py-4">Loading more…</div>
      )}
    </div>
  );
}
