import type { BrowsableSourceItemView } from "@batch-cooking/shared";
import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import "../recipes.scss";

/**
 * List of one source's browsable items (`RecipeSourcesPanel`) — same
 * "photo + name, click/Enter to select" row shape as `RecipeTable`, plus
 * an "already imported" badge in place of allergen/regime columns (a
 * source item has neither, it's not resolved against our catalogs until
 * previewed).
 *
 * Infinite scroll, not a "voir plus" button: a zero-content sentinel row
 * (`source-item-table__sentinel`) sits right after `items`, watched by an
 * `IntersectionObserver` scoped to this table's own scrolling container
 * (`.recipe-table-wrap`, not the page) — scrolling it into view calls
 * `onLoadMore`, the same callback a button's `onClick` would have. Kept
 * entirely inside this component rather than exposed as a prop callback
 * signature change on every call site: `RecipeSourcesPanel` doesn't need to
 * know *how* "load more" gets triggered, only that it does.
 */
export function SourceItemTable({
  items,
  selectedExternalId,
  onSelect,
  placeholderCount = 0,
  hasMore = false,
  onLoadMore,
}: {
  items: BrowsableSourceItemView[];
  selectedExternalId: string | null;
  onSelect: (item: BrowsableSourceItemView) => void;
  /**
   * Extra pulsing skeleton rows appended after `items` — `RecipeSourcesPanel`
   * sets this while a "load more" fetch is in flight, so the list fills in
   * right away instead of looking like nothing happened. Purely decorative:
   * never clickable/focusable, unlike a real row.
   */
  placeholderCount?: number;
  /** Whether a further page exists — renders the sentinel row (and therefore observes it) only when true; the observer would otherwise have nothing meaningful to trigger once the catalog is exhausted. */
  hasMore?: boolean;
  /** Called once when the sentinel row scrolls into view — `RecipeSourcesPanel`'s own re-entrancy guard (not this component) is what keeps a still-visible sentinel from firing this repeatedly while a fetch is already in flight. */
  onLoadMore?: () => void;
}) {
  const { t } = useTranslation();
  const scrollContainerRef = useRef<HTMLDivElement | null>(null);
  const sentinelRef = useRef<HTMLTableRowElement | null>(null);

  // biome-ignore lint/correctness/useExhaustiveDependencies: `onLoadMore` is deliberately excluded — RecipeSourcesPanel passes a fresh function identity on every render, and re-subscribing the observer on every single render (rather than only when hasMore actually flips) would be wasteful busywork for no behavioral difference.
  useEffect(() => {
    const root = scrollContainerRef.current;
    const sentinel = sentinelRef.current;
    if (!hasMore || !onLoadMore || !root || !sentinel) return;

    // `rootMargin` starts loading the next page slightly before the
    // sentinel actually reaches the visible edge — combined with
    // RecipeSourcesPanel's own prefetch-ahead-of-time, the goal is that a
    // page finishes arriving before anyone actually scrolls far enough to
    // need it, not just "as soon as" they do.
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries.some((entry) => entry.isIntersecting)) onLoadMore();
      },
      { root, rootMargin: "200px 0px" },
    );
    observer.observe(sentinel);
    return () => observer.disconnect();
  }, [hasMore]);

  return (
    <div className="recipe-table-wrap" ref={scrollContainerRef}>
      <table className="recipe-table">
        <thead>
          <tr>
            <th />
            <th>{t("recipes.table.name")}</th>
            <th />
          </tr>
        </thead>
        <tbody>
          {items.map((item) => (
            <tr
              key={item.externalId}
              className={item.externalId === selectedExternalId ? "selected" : undefined}
              onClick={() => onSelect(item)}
              onKeyDown={(e) => {
                if (e.key === "Enter" || e.key === " ") {
                  e.preventDefault();
                  onSelect(item);
                }
              }}
              tabIndex={0}
              aria-current={item.externalId === selectedExternalId ? "true" : undefined}
            >
              <td>
                <span className="recipe-table__photo" aria-hidden="true">
                  {item.picture ? <img src={item.picture} alt="" /> : "🍽️"}
                </span>
              </td>
              <td className="recipe-table__name">{item.title}</td>
              <td>
                {item.alreadyImported && (
                  <span className="source-item-table__imported-badge">
                    {t("recipes.sources.alreadyImported")}
                  </span>
                )}
              </td>
            </tr>
          ))}
          {hasMore && (
            <tr ref={sentinelRef} className="source-item-table__sentinel">
              <td colSpan={3} />
            </tr>
          )}
          {Array.from({ length: placeholderCount }, (_, index) => (
            // biome-ignore lint/suspicious/noArrayIndexKey: a fixed-length run of interchangeable, content-less placeholders — there's no stable identity to key on, and the count never reorders.
            <tr key={`skeleton-${index}`} className="source-item-table__row--skeleton">
              <td>
                <span className="recipe-table__photo source-item-table__photo--skeleton" />
              </td>
              <td>
                <span className="source-item-table__skeleton-bar" />
              </td>
              <td />
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
