import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
import type { Video } from "../lib/api";
import VideoCard from "./VideoCard";

// Grid column sizing mirrors the CSS the feed used before virtualization
// (grid-cols-[repeat(auto-fill,minmax(260px,1fr))] with a gap-4 / 1rem gutter).
const GRID_MIN_COL = 260;
const GRID_GAP = 16;
const LIST_GAP = 4;
// Estimated row heights before measurement; real heights are measured per-row so
// these only affect the very first paint and the scrollbar's initial guess.
const GRID_ROW_EST = 340;
const LIST_ROW_EST = 96;
// Start fetching the next page this many rows before the end scrolls into view
// (keeps the old IntersectionObserver's generous prefetch feel).
const PREFETCH_ROWS = 4;

function chunk<T>(arr: T[], size: number): T[][] {
  if (size <= 1) return arr.map((x) => [x]);
  const out: T[][] = [];
  for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
  return out;
}

// The feed scrolls inside the app's <main overflow-y-auto>, not the window, so the
// virtualizer needs that element. Walk up to the nearest scrollable ancestor.
function getScrollParent(node: HTMLElement | null): HTMLElement {
  let el = node?.parentElement ?? null;
  while (el) {
    const oy = getComputedStyle(el).overflowY;
    if (oy === "auto" || oy === "scroll") return el;
    el = el.parentElement;
  }
  return document.documentElement;
}

export interface VirtualFeedProps {
  items: Video[];
  view: "grid" | "list";
  onState: (id: string, status: string) => void;
  onToggleSave: (id: string, saved: boolean) => void;
  onOpenChannel: (channelId: string, channelName: string) => void;
  onResetState: (id: string) => void;
  onOpen: (v: Video, startAt?: number | null) => void;
  hasNextPage: boolean;
  isFetchingNextPage: boolean;
  fetchNextPage: () => void;
}

export default function VirtualFeed({
  items,
  view,
  onState,
  onToggleSave,
  onOpenChannel,
  onResetState,
  onOpen,
  hasNextPage,
  isFetchingNextPage,
  fetchNextPage,
}: VirtualFeedProps) {
  const listRef = useRef<HTMLDivElement>(null);
  const [scrollEl, setScrollEl] = useState<HTMLElement | null>(null);
  const [colCount, setColCount] = useState(view === "list" ? 1 : 4);
  // Distance from the scroll element's content top to where this list starts (the
  // feed toolbar sits above it inside the same scroll area).
  const [scrollMargin, setScrollMargin] = useState(0);

  useLayoutEffect(() => {
    if (listRef.current) setScrollEl(getScrollParent(listRef.current));
  }, []);

  // Re-measure the column count and start offset on container/scroll-area resize.
  useLayoutEffect(() => {
    const node = listRef.current;
    if (!node) return;
    const measure = () => {
      if (scrollEl) {
        const m =
          node.getBoundingClientRect().top -
          scrollEl.getBoundingClientRect().top +
          scrollEl.scrollTop;
        setScrollMargin(m);
      }
      setColCount(
        view === "list"
          ? 1
          : Math.max(1, Math.floor((node.clientWidth + GRID_GAP) / (GRID_MIN_COL + GRID_GAP)))
      );
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(node);
    if (scrollEl) ro.observe(scrollEl);
    return () => ro.disconnect();
  }, [scrollEl, view]);

  const rows = useMemo(() => chunk(items, colCount), [items, colCount]);

  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => scrollEl,
    estimateSize: () => (view === "grid" ? GRID_ROW_EST : LIST_ROW_EST),
    overscan: 4,
    gap: view === "grid" ? GRID_GAP : LIST_GAP,
    scrollMargin,
  });

  const virtualRows = virtualizer.getVirtualItems();

  // Infinite-scroll trigger: once the last rendered row is within PREFETCH_ROWS of
  // the end, pull the next keyset page.
  useEffect(() => {
    const last = virtualRows[virtualRows.length - 1];
    if (!last) return;
    if (last.index >= rows.length - 1 - PREFETCH_ROWS && hasNextPage && !isFetchingNextPage) {
      fetchNextPage();
    }
  }, [virtualRows, rows.length, hasNextPage, isFetchingNextPage, fetchNextPage]);

  return (
    <div ref={listRef} style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
      {virtualRows.map((vr) => {
        const row = rows[vr.index];
        if (!row) return null;
        return (
          <div
            key={vr.key}
            data-index={vr.index}
            ref={virtualizer.measureElement}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              transform: `translateY(${vr.start - virtualizer.options.scrollMargin}px)`,
            }}
          >
            {view === "grid" ? (
              <div
                className="grid gap-4"
                style={{ gridTemplateColumns: `repeat(${colCount}, minmax(0, 1fr))` }}
              >
                {row.map((v) => (
                  <VideoCard
                    key={v.id}
                    video={v}
                    view="grid"
                    onState={onState}
                    onToggleSave={onToggleSave}
                    onOpenChannel={onOpenChannel}
                    onResetState={onResetState}
                    onOpen={onOpen}
                  />
                ))}
              </div>
            ) : (
              <div className="max-w-4xl mx-auto pb-1">
                <VideoCard
                  video={row[0]}
                  view="list"
                  onState={onState}
                  onToggleSave={onToggleSave}
                  onOpenChannel={onOpenChannel}
                  onResetState={onResetState}
                  onOpen={onOpen}
                />
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}
