import React, {
  useState, useEffect, useRef, useCallback, memo,
} from 'react';
import ArtworkCard from './ArtworkCard';
import './MasonryGallery.css';

// ── Masonry helpers ────────────────────────────────────────────────────────
const ROW_SIZE = 8;
const ROW_GAP  = 16;

function applyMasonry(grid) {
  if (!grid) return;
  Array.from(grid.querySelectorAll('.nova-card')).forEach((card) => {
    const media = card.querySelector('.nova-card-media') || card;
    let height  = media.getBoundingClientRect().height || 200;

    // Clamp to the computed max-height so the span never over-reserves rows
    // when CSS max-height kicks in (e.g. portrait images capped to 2×16:9).
    const cssMaxH = parseFloat(getComputedStyle(media).maxHeight);
    if (!isNaN(cssMaxH) && cssMaxH > 0 && cssMaxH < height) {
      height = cssMaxH;
    }

    const span = Math.max(1, Math.ceil((height + ROW_GAP) / (ROW_SIZE + ROW_GAP)));
    card.style.gridRowEnd = `span ${span}`;
  });
}

function waitForImages(el) {
  return Promise.all(
    Array.from(el.querySelectorAll('img')).map((img) =>
      img.decode ? img.decode().catch(() => null) : Promise.resolve(),
    ),
  );
}

// ── Page fetch helpers ─────────────────────────────────────────────────────
/**
 * Fetch the next page of data.
 *
 * The response is either:
 *  - JSON  { artworks: [...], next_cursor: '...' }  when X-Requested-With is
 *    sent and the controller returns JSON (future enhancement)
 *  - HTML  page – we parse [data-react-masonry-gallery] from it and read its
 *    data-artworks / data-next-cursor / data-next-page-url attributes.
 */
async function fetchPageData(url) {
  const res = await fetch(url, {
    credentials: 'same-origin',
    headers: { 'X-Requested-With': 'XMLHttpRequest' },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);

  const ct = res.headers.get('content-type') || '';

  // JSON fast-path (if controller ever returns JSON)
  if (ct.includes('application/json')) {
    const json = await res.json();
    return {
      artworks:    json.artworks    ?? [],
      nextCursor:  json.next_cursor ?? null,
      nextPageUrl: json.next_page_url ?? null,
    };
  }

  // HTML: parse and extract mount-container data attributes
  const html = await res.text();
  const doc  = new DOMParser().parseFromString(html, 'text/html');
  const el   = doc.querySelector('[data-react-masonry-gallery]');
  if (!el) return { artworks: [], nextCursor: null, nextPageUrl: null };

  let artworks = [];
  try { artworks = JSON.parse(el.dataset.artworks || '[]'); } catch { /* empty */ }

  return {
    artworks,
    nextCursor:  el.dataset.nextCursor  || null,
    nextPageUrl: el.dataset.nextPageUrl || null,
  };
}

// ── Skeleton row ──────────────────────────────────────────────────────────
function SkeletonCard() {
  return <div className="nova-skeleton-card" aria-hidden="true" />;
}

const SKELETON_COUNT = 10;

// ── Main component ────────────────────────────────────────────────────────
/**
 * MasonryGallery
 *
 * Props (all optional – set via data attributes in entry-masonry-gallery.jsx):
 *   artworks           []        Initial artwork objects
 *   galleryType        string    Maps to data-gallery-type (e.g. 'trending')
 *   cursorEndpoint     string|null  Route for cursor-based feeds (e.g. For You)
 *   initialNextCursor  string|null  First cursor token
 *   initialNextPageUrl string|null  First "next page" URL (page-based feeds)
 *   limit              number    Items per page (default 40)
 */
function MasonryGallery({
  artworks: initialArtworks  = [],
  galleryType                = 'discover',
  cursorEndpoint             = null,
  initialNextCursor          = null,
  initialNextPageUrl         = null,
  limit                      = 40,
}) {
  const [artworks,    setArtworks]    = useState(initialArtworks);
  const [nextCursor,  setNextCursor]  = useState(initialNextCursor);
  const [nextPageUrl, setNextPageUrl] = useState(initialNextPageUrl);
  const [loading,     setLoading]     = useState(false);
  const [done,        setDone]        = useState(!initialNextCursor && !initialNextPageUrl);

  const gridRef    = useRef(null);
  const triggerRef = useRef(null);

  // ── Masonry re-layout ──────────────────────────────────────────────────
  const relayout = useCallback(() => {
    const g = gridRef.current;
    if (!g) return;
    applyMasonry(g);
    waitForImages(g).then(() => applyMasonry(g));
  }, []);

  // Re-layout whenever artworks list changes.
  // Defer by one requestAnimationFrame so the browser has resolved
  // aspect-ratio heights before we measure with getBoundingClientRect().
  useEffect(() => {
    const raf = requestAnimationFrame(() => relayout());
    return () => cancelAnimationFrame(raf);
  }, [artworks, relayout]);

  // Re-layout on container resize (column width changes)
  useEffect(() => {
    const g = gridRef.current;
    if (!g || !('ResizeObserver' in window)) return;
    const ro = new ResizeObserver(relayout);
    ro.observe(g);
    return () => ro.disconnect();
  }, [relayout]);

  // ── Load more ──────────────────────────────────────────────────────────
  const fetchNext = useCallback(async () => {
    if (loading || done) return;

    // Build the URL to fetch
    let fetchUrl = null;
    if (cursorEndpoint && nextCursor) {
      const u = new URL(cursorEndpoint, window.location.href);
      u.searchParams.set('cursor', nextCursor);
      u.searchParams.set('limit', String(limit));
      fetchUrl = u.toString();
    } else if (nextPageUrl) {
      fetchUrl = nextPageUrl;
    }

    if (!fetchUrl) { setDone(true); return; }

    setLoading(true);
    try {
      const { artworks: newItems, nextCursor: nc, nextPageUrl: np } =
        await fetchPageData(fetchUrl);

      if (!newItems.length) {
        setDone(true);
      } else {
        setArtworks((prev) => [...prev, ...newItems]);
        if (cursorEndpoint) {
          setNextCursor(nc);
          if (!nc) setDone(true);
        } else {
          setNextPageUrl(np);
          if (!np) setDone(true);
        }
      }
    } catch {
      setDone(true);
    } finally {
      setLoading(false);
    }
  }, [loading, done, cursorEndpoint, nextCursor, nextPageUrl, limit]);

  // ── Intersection observer for infinite scroll ──────────────────────────
  useEffect(() => {
    if (done) return;
    const trigger = triggerRef.current;
    if (!trigger || !('IntersectionObserver' in window)) return;

    const io = new IntersectionObserver(
      (entries) => { if (entries[0].isIntersecting) fetchNext(); },
      { rootMargin: '900px', threshold: 0 },
    );
    io.observe(trigger);
    return () => io.disconnect();
  }, [done, fetchNext]);

  // ── Render ─────────────────────────────────────────────────────────────
  return (
    <section
      className="px-6 pb-10 pt-2 md:px-10 is-enhanced"
      data-nova-gallery
      data-gallery-type={galleryType}
      data-react-masonry-gallery
      data-artworks={JSON.stringify(artworks)}
      data-next-cursor={nextCursor   ?? undefined}
      data-next-page-url={nextPageUrl ?? undefined}
    >
      {artworks.length > 0 ? (
        <>
          <div
            ref={gridRef}
            className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 force-5"
            data-gallery-grid
          >
            {artworks.map((art, idx) => (
              <ArtworkCard
                key={`${art.id}-${idx}`}
                art={art}
                loading={idx < 8 ? 'eager' : 'lazy'}
                fetchpriority={idx === 0 ? 'high' : null}
              />
            ))}
          </div>

          {/* Infinite scroll sentinel – placed after the grid */}
          {!done && (
            <div
              ref={triggerRef}
              className="h-px w-full"
              aria-hidden="true"
            />
          )}

          {/* Loading indicator */}
          {loading && (
            <div className="flex justify-center items-center gap-2 mt-8 py-4 text-white/30 text-sm">
              <svg
                className="animate-spin h-4 w-4 shrink-0"
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                aria-hidden="true"
              >
                <circle
                  className="opacity-25"
                  cx="12" cy="12" r="10"
                  stroke="currentColor"
                  strokeWidth="4"
                />
                <path
                  className="opacity-75"
                  fill="currentColor"
                  d="M4 12a8 8 0 018-8v8H4z"
                />
              </svg>
              Loading more…
            </div>
          )}

          {done && artworks.length > 0 && (
            <p className="text-center text-xs text-white/20 mt-8 py-2">
              All caught up
            </p>
          )}
        </>
      ) : (
        /* Empty state – gallery-type-specific messaging handled by caller */
        <div className="rounded-xl border border-white/[0.06] bg-white/[0.02] px-8 py-12 text-center">
          <p className="text-white/40 text-sm">No artworks found for this section yet.</p>
        </div>
      )}
    </section>
  );
}

export default memo(MasonryGallery);
