const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============ Manifest loader ============
// All content (photos, categories, copy, hero image) is driven by photos.json,
// which is regenerated by scripts/build.sh whenever you add photos to uploads/.

function useManifest() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  useEffect(() => {
    fetch('photos.json', { cache: 'no-cache' }).
    then((r) => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json();
    }).
    then(setData).
    catch((err) => {
      console.error('Failed to load photos.json', err);
      setError(err.message);
    });
  }, []);
  return { data, error };
}

// ============ Loader / Error ============
function Loader() {
  return (
    <div className="loader">
      <div className="loader-orb" />
      <p>Loading the album&hellip;</p>
    </div>);

}

function LoadError({ message }) {
  return (
    <div className="loader">
      <p>Could not load the album.</p>
      <p className="loader-err">{message}</p>
      <p className="loader-hint">Make sure <code>photos.json</code> sits next to this page.</p>
    </div>);

}

// ============ Hero ============
function Hero({ manifest, totalCount }) {
  return (
    <header className="hero">
      <div className="hero-bg">
        <img src={manifest.hero} alt="" />
        <div className="hero-veil" />
      </div>
      <div className="hero-confetti" aria-hidden="true">
        {Array.from({ length: 32 }).map((_, i) =>
        <span key={i} className={`dot d${i % 6}`} style={{
          left: `${i * 37 % 100}%`,
          top: `${i * 53 % 100}%`,
          animationDelay: `${i * 0.4 % 6}s`
        }} />
        )}
      </div>
      <div className="hero-content">
        <div className="hero-eyebrow">
          <span className="line"></span>
          <span>{manifest.eyebrow}</span>
          <span className="line"></span>
        </div>
        {manifest.kicker && <p className="hero-kicker">{manifest.kicker}</p>}
        <h1 className="hero-name">{manifest.title}</h1>
        <p className="hero-welcome">{manifest.welcome}</p>
        <div className="hero-actions">
          <a href="#gallery" className="hero-btn primary" style={{ color: "rgb(55, 46, 55)" }}>
            <span>Open the album</span>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 5v14"></path><path d="m6 13 6 6 6-6"></path>
            </svg>
          </a>
          <span className="hero-count">{totalCount} photographs</span>
        </div>
      </div>
    </header>);

}

// ============ Filter bar ============
function FilterBar({ categories, active, onPick, counts, onDownload, zipSize }) {
  return (
    <div className="filters" id="gallery">
      <div className="filters-inner">
        <div className="filters-row">
          <div className="filter-chips" role="tablist">
            {categories.map((c) =>
            <button
              key={c.id}
              role="tab"
              aria-selected={active === c.id}
              className={`chip ${active === c.id ? 'on' : ''}`}
              onClick={() => onPick(c.id)}>

                <span className="chip-label">{c.label}</span>
                <span className="chip-count">{counts[c.id]}</span>
              </button>
            )}
          </div>
          <div className="filter-select-wrap">
            <select
              className="filter-select"
              value={active}
              onChange={(e) => onPick(e.target.value)}
              aria-label="Filter by category">
              {categories.map((c) =>
              <option key={c.id} value={c.id}>{c.label} ({counts[c.id]})</option>
              )}
            </select>
            <svg className="filter-select-caret" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <path d="m6 9 6 6 6-6"></path>
            </svg>
          </div>
          <button className="download-btn" onClick={onDownload}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 3v13"></path>
              <path d="m7 11 5 5 5-5"></path>
              <path d="M5 21h14"></path>
            </svg>
            <span>Download all{zipSize ? ` (${zipSize})` : ''}</span>
          </button>
        </div>
      </div>
    </div>);

}

// ============ Gallery ============
function Gallery({ photos, onOpen }) {
  return (
    <section className="gallery">
      <div className="masonry">
        {photos.map((p, i) =>
        <button
          key={p.id}
          className={`tile orient-${p.orient}`}
          onClick={() => onOpen(i)}
          style={{ animationDelay: `${i % 10 * 45}ms` }}>
          
            <img src={p.src} alt={p.alt || ''} loading="lazy" />
            <span className="tile-glow"></span>
            <span className="tile-zoom" aria-hidden="true">
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                <circle cx="11" cy="11" r="7"></circle>
                <path d="m20 20-3.5-3.5"></path>
                <path d="M11 8v6M8 11h6"></path>
              </svg>
            </span>
          </button>
        )}
      </div>
      {photos.length === 0 &&
      <p className="empty">{'No photos here yet \u2014 try another moment.'}</p>
      }
    </section>);

}

// ============ Lightbox ============
function Lightbox({ photos, index, onClose, onNav }) {
  const touchStart = useRef(null);

  useEffect(() => {
    if (index === null) return;
    const key = (e) => {
      if (e.key === 'Escape') onClose();
      if (e.key === 'ArrowRight') onNav(1);
      if (e.key === 'ArrowLeft') onNav(-1);
    };
    window.addEventListener('keydown', key);
    document.body.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', key);
      document.body.style.overflow = '';
    };
  }, [index, onClose, onNav]);

  if (index === null) return null;
  const p = photos[index];

  const onTouchStart = (e) => {touchStart.current = e.touches[0].clientX;};
  const onTouchEnd = (e) => {
    if (touchStart.current == null) return;
    const dx = e.changedTouches[0].clientX - touchStart.current;
    if (Math.abs(dx) > 50) onNav(dx < 0 ? 1 : -1);
    touchStart.current = null;
  };

  const fileName = `pearl-bday-${p.id}.jpg`;

  return (
    <div className="lightbox" onClick={onClose}>
      <div className="lb-backdrop"></div>
      <button className="lb-close" onClick={onClose} aria-label="Close">
        <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="m18 6-12 12M6 6l12 12"></path></svg>
      </button>
      <button className="lb-arrow lb-prev" onClick={(e) => {e.stopPropagation();onNav(-1);}} aria-label="Previous">
        <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"></path></svg>
      </button>
      <button className="lb-arrow lb-next" onClick={(e) => {e.stopPropagation();onNav(1);}} aria-label="Next">
        <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6"></path></svg>
      </button>
      <figure
        className="lb-stage"
        onClick={(e) => e.stopPropagation()}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}>
        
        <img key={p.id} src={p.src} alt={p.alt || ''} />
        <figcaption>
          <div className="lb-caption">{p.alt}</div>
          <div className="lb-count">{index + 1} <span>/</span> {photos.length}</div>
        </figcaption>
      </figure>
      <a
        className="lb-download"
        href={p.src}
        download={fileName}
        onClick={(e) => e.stopPropagation()}>
        
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M12 3v13"></path><path d="m7 11 5 5 5-5"></path><path d="M5 21h14"></path>
        </svg>
        Save this photo
      </a>
    </div>);

}

// ============ Back-to-top ============
function BackToTop() {
  const [show, setShow] = useState(false);
  useEffect(() => {
    const onScroll = () => setShow(window.scrollY > 800);
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  if (!show) return null;
  return (
    <button className="back-top" onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} aria-label="Back to top">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
        <path d="m6 15 6-6 6 6"></path>
      </svg>
    </button>);

}

// ============ App ============
function App() {
  const { data, error } = useManifest();
  const [filter, setFilter] = useState('all');
  const [lbIndex, setLbIndex] = useState(null);
  const zipSize = data?.downloadZipSize || '';
  const fullZipUrl = data?.downloadZipFull || '';
  const fullZipSize = data?.downloadZipFullSize || '';

  const photos = data ? data.photos : [];
  const categories = useMemo(() => {
    if (!data) return [];
    return [{ id: 'all', label: 'All Moments' }, ...data.categories];
  }, [data]);

  const filtered = useMemo(
    () => filter === 'all' ? photos : photos.filter((p) => p.cat === filter),
    [filter, photos]
  );

  const counts = useMemo(() => {
    const out = { all: photos.length };
    for (const c of categories) {
      if (c.id === 'all') continue;
      out[c.id] = photos.filter((p) => p.cat === c.id).length;
    }
    return out;
  }, [photos, categories]);

  const onNav = useCallback((dir) => {
    setLbIndex((i) => {
      if (i === null) return i;
      return (i + dir + filtered.length) % filtered.length;
    });
  }, [filtered.length]);

  const downloadAll = useCallback(() => {
    if (!data) return;
    const a = document.createElement('a');
    a.href = data.downloadZip || 'photos.zip';
    a.download = `pearl-bday-photos.zip`;
    document.body.appendChild(a);
    a.click();
    a.remove();
  }, [data]);

  const downloadFull = useCallback(() => {
    if (!fullZipUrl) return;
    const a = document.createElement('a');
    a.href = fullZipUrl;
    a.download = `pearl-bday-photos-full.zip`;
    document.body.appendChild(a);
    a.click();
    a.remove();
  }, [fullZipUrl]);

  if (error) return <LoadError message={error} />;
  if (!data) return <Loader />;

  return (
    <>
      <Hero manifest={data} totalCount={photos.length} />
      <FilterBar
        categories={categories}
        active={filter}
        onPick={setFilter}
        counts={counts}
        onDownload={downloadAll}
        zipSize={zipSize} />
      
      <Gallery photos={filtered} onOpen={setLbIndex} />

      <section className="closing">
        <div className="closing-rule">
          <span></span>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 14 9l7 1-5.5 4.5L17 22l-5-3.5L7 22l1.5-7.5L3 10l7-1z"></path></svg>
          <span></span>
        </div>
        <h2>Keep them all, with love.</h2>
        <p>{'Tap below to save every photograph in a single zip. Unzip it on your phone or computer and the moments are yours forever.'}</p>
        <button className="closing-btn" onClick={downloadAll}>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M12 3v13"></path><path d="m7 11 5 5 5-5"></path><path d="M5 21h14"></path>
          </svg>
          <span>Download all {photos.length} photos{zipSize ? ` \u00b7 ${zipSize}` : ''}</span>
        </button>
        {fullZipUrl &&
        <button className="closing-btn closing-btn-secondary" onClick={downloadFull}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 3v13"></path><path d="m7 11 5 5 5-5"></path><path d="M5 21h14"></path>
            </svg>
            <span>Full quality{fullZipSize ? ` \u00b7 ${fullZipSize}` : ''}</span>
          </button>
        }
        <p className="closing-note">{fullZipUrl ? 'Slow connection? The first download is resized for quick saving. The second has the originals.' : 'Tip: tap-and-hold any single photo above to save it on its own.'}</p>
      </section>

      <footer className="page-footer">
        <p>{data.footer}</p>
        <p className="pf-small">{data.footerSmall}</p>
      </footer>

      <BackToTop />
      <Lightbox photos={filtered} index={lbIndex} onClose={() => setLbIndex(null)} onNav={onNav} />
    </>);

}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);