/* global React, ReactDOM, lucide, TypesMenu, MainMenu, ContactForm */
const { useState, useEffect, useRef, useCallback } = React;

/* Image URL helper — backend image route (falls back to Unsplash for seeds).
   Absolute URLs pass through untouched so static previews can stub data. */
const IMG = (filename, size = "web") => /^https?:\/\//.test(filename) ? filename : `/api/images/${size}/${encodeURIComponent(filename)}`;
const VID = (filename) => /^https?:\/\//.test(filename) ? filename : `/api/images/video/${encodeURIComponent(filename)}`;

/* Track a max-width media query so layouts rebuild on rotation/resize. */
function useIsPhone(maxW = 700) {
  const [is, setIs] = useState(() => window.matchMedia(`(max-width: ${maxW}px)`).matches);
  useEffect(() => {
    const mq = window.matchMedia(`(max-width: ${maxW}px)`);
    const fn = () => setIs(mq.matches);
    if (mq.addEventListener) mq.addEventListener("change", fn); else mq.addListener(fn);
    return () => { if (mq.removeEventListener) mq.removeEventListener("change", fn); else mq.removeListener(fn); };
  }, [maxW]);
  return is;
}

/* Chunk an item count into justified rows of varying width.
   Phones get a clean 2-up mosaic instead of 3-tile strips. */
function buildRows(count, phone) {
  const pattern = phone ? [2] : [3, 2, 3, 3, 2, 3];
  const rows = []; let i = 0, p = 0;
  while (i < count) {
    const size = Math.min(pattern[p % pattern.length], count - i);
    rows.push(Array.from({ length: size }, (_, k) => i + k));
    i += size; p++;
  }
  return rows;
}

/* Build the horizontal story as album spreads. Every photo keeps its real
   aspect ratio (no cropping): solo pages alternate with facing pairs when
   two neighbours are slim enough to share a spread. Videos always get a
   full page of their own. */
function buildStory(images, coverFile, phone) {
  const story = [{ type: "title" }];
  // Pool of media, skipping the cover once (it stars in the title spread).
  const pool = [];
  let coverSkipped = false;
  for (const m of images || []) {
    if (!coverSkipped && coverFile && m.filename === coverFile) { coverSkipped = true; continue; }
    const ar = m.width && m.height
      ? Math.min(2.6, Math.max(0.5, m.width / m.height))
      : (m.media_type === "video" ? 16 / 9 : 1.5);
    pool.push({ file: m.filename, video: m.media_type === "video", ar });
  }
  let idx = 0, sincePair = 1;
  while (idx < pool.length) {
    const cur = pool[idx];
    const nxt = pool[idx + 1];
    // Phones never pair — side-by-side photos turn into cramped slivers there;
    // every photo gets its own full-width page instead.
    const pairable = !phone && nxt && !cur.video && !nxt.video && cur.ar + nxt.ar <= 2.75 && sincePair >= 1;
    if (pairable) { story.push({ type: "pair", media: [cur, nxt] }); idx += 2; sincePair = 0; }
    else { story.push({ type: "solo", media: cur }); idx += 1; sincePair += 1; }
  }
  story.push({ type: "next" });
  return story;
}

/* Name with optional italic ampersand */
function NameLine({ s }) {
  return s.name_b
    ? <React.Fragment>{s.name_a} <span style={{fontStyle:"italic"}}>&amp;</span> {s.name_b}</React.Fragment>
    : <React.Fragment>{s.name_a}</React.Fragment>;
}

const IDLE_MS = 7000;
const AUTO_SPEED = 2.1;
const WHEEL_SPEED = 2.4; // vertical wheel → horizontal scroll multiplier

/* ============================================================
   Chrome — matches home_test (logo TL, MENU TR, CONTACT BR, "+" BL)
   ============================================================ */
function HomeChrome({ onLogo, onMenu, onContact, onPlus, plusOpen }) {
  return (
    <React.Fragment>
      <div className="h-logo" onClick={onLogo}>
        <div className="lname">NICKO TRENCHEV</div>
        <div className="lsub">STUDIO</div>
      </div>
      <button className="h-chrome-label h-menu-btn" onClick={onMenu}>MENU</button>
      <button className="h-chrome-label h-contact-btn" onClick={onContact}>CONTACT</button>
      <button className={`h-plus ${plusOpen ? "open" : ""}`} onClick={onPlus} aria-label="Galleries">
        <svg viewBox="0 0 64 64">
          <line x1="32" y1="14" x2="32" y2="50" />
          <line x1="14" y1="32" x2="50" y2="32" />
        </svg>
      </button>
    </React.Fragment>
  );
}

/* Category menu ("+") is the shared <TypesMenu> from Overlays.jsx, so the
   home page and this page always show the same admin-editable category list. */

/* ============================================================
   Grid view
   ============================================================ */
function GridView({ gallery, onOpen, dim }) {
  const items = (gallery && gallery.items) || [];
  const phone = useIsPhone(520);
  const rows = buildRows(items.length, phone);
  return (
    <div className={`gt-grid-view ${dim ? "dim" : ""}`}>
      <div className="gt-grid-inner">
        <div className="gt-rows">
          {rows.map((row, ri) => (
            <div className="gt-row" key={ri}>
              {row.map((ci) => {
                const s = items[ci];
                return (
                  <div className="gt-tile" key={s.id} onClick={() => onOpen(s)}>
                    <div className="protected-image">
                      <img src={IMG(s.cover)} alt={s.name_a} onContextMenu={(e) => e.preventDefault()} draggable="false" />
                      <div className="img-guard"></div>
                    </div>
                    <div className="gt-tile-cap">
                      <div className="names"><NameLine s={s} /></div>
                      <div className="place">{s.place}</div>
                      <div className="open">View gallery <i data-lucide="arrow-right" style={{width:13,height:13}}></i></div>
                    </div>
                  </div>
                );
              })}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   Loading view
   ============================================================ */
function LoadingView({ subject, onDone }) {
  const [pct, setPct] = useState(0);
  useEffect(() => {
    let v = 0;
    const t = setInterval(() => {
      v += Math.random() * 9 + 3;
      if (v >= 100) { v = 100; clearInterval(t); setTimeout(onDone, 480); }
      setPct(Math.floor(v));
    }, 70);
    return () => clearInterval(t);
  }, []);
  return (
    <div className="gt-loading">
      <div className="lname"><NameLine s={subject} /></div>
      <div className="lplace">{subject.place}</div>
      <div className="pct">{String(pct).padStart(2,"0")}<span style={{fontSize:"0.34em", verticalAlign:"super"}}>%</span></div>
      <div className="bar"><i style={{width: pct + "%"}}></i></div>
    </div>
  );
}

/* ============================================================
   Story panel renderer — editorial cards, auto-peeking widths
   ============================================================ */
function ProtectedImg(props) {
  return (
    <div className="protected-image">
      <img {...props} onContextMenu={(e) => e.preventDefault()} draggable="false" />
      <div className="img-guard"></div>
    </div>
  );
}

/* One media element — photo or auto-playing ambient video. */
function MediaEl({ m }) {
  if (!m) return null;
  if (m.video) {
    return (
      <div className="protected-image">
        <video
          src={VID(m.file)}
          autoPlay muted loop playsInline preload="metadata"
          onContextMenu={(e) => e.preventDefault()}
          style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
        />
        <div className="img-guard"></div>
      </div>
    );
  }
  return <ProtectedImg src={IMG(m.file)} alt="" />;
}

function Panel({ p, subject, nextSubject, onNext }) {
  switch (p.type) {
    case "title":
      // Opening spread — text on white left, full-bleed cover right.
      return (
        <div className="gt-panel p-title">
          <div className="titleblock">
            <div className="eyebrow">{subject.place}</div>
            <h2>
              {subject.name_a}{subject.name_b && <span className="amp"> &amp;</span>}
              {subject.name_b && <React.Fragment><br/><em>{subject.name_b}</em></React.Fragment>}
            </h2>
            <div className="sub">Selected frames — 2026</div>
          </div>
          <div className="cover">
            <ProtectedImg src={IMG(subject.cover)} alt="" />
          </div>
        </div>
      );
    case "solo":
      return (
        <div className="gt-panel p-solo">
          <div className={`gt-card ${p.media.ar >= 1.05 ? "land" : "port"}`} style={{ "--ar": p.media.ar }}>
            <MediaEl m={p.media} />
          </div>
        </div>
      );
    case "pair":
      return (
        <div className="gt-panel p-pair">
          {p.media.map((m, i) => (
            <div className="gt-card" key={i} style={{ "--ar": m.ar }}><MediaEl m={m} /></div>
          ))}
        </div>
      );
    case "next":
      return (
        <div className="gt-panel p-next" onClick={onNext}>
          <div className="next-text">
            <div className="next-eyebrow">— Continue</div>
            <h3>Next<br/>Project</h3>
            <div className="next-line"></div>
            <div className="next-name"><NameLine s={nextSubject} /></div>
            <div className="next-place">{nextSubject.place}</div>
          </div>
          <div className="gt-card portrait">
            <ProtectedImg src={IMG(nextSubject.cover)} alt="" />
            <div className="next-go"><i data-lucide="arrow-right"></i></div>
          </div>
        </div>
      );
    default:
      return null;
  }
}

/* ============================================================
   Gallery view — horizontal scroll + idle auto-scroll
   ============================================================ */
function GalleryView({ story, subject, nextSubject, onBack, onPage, onNext }) {
  const scrollerRef = useRef(null);
  const lastInteract = useRef(Date.now());
  const [auto, setAuto] = useState(false);
  const phone = useIsPhone(700);

  useEffect(() => { window.__gtBack = onBack; return () => { window.__gtBack = null; }; }, [onBack]);

  useEffect(() => {
    const el = scrollerRef.current;
    if (!el) return;
    const onWheel = (e) => {
      if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
        el.scrollLeft += e.deltaY * WHEEL_SPEED;
        e.preventDefault();
      }
      bump();
    };
    el.addEventListener("wheel", onWheel, { passive: false });
    return () => el.removeEventListener("wheel", onWheel);
  }, []);

  const bump = useCallback(() => { lastInteract.current = Date.now(); setAuto(false); }, []);

  useEffect(() => {
    const el = scrollerRef.current;
    const onScroll = () => {
      const panels = el.querySelectorAll(".gt-panel");
      const center = el.scrollLeft + el.clientWidth / 2;
      let idx = 0;
      panels.forEach((pnl, i) => { if (pnl.offsetLeft <= center) idx = i; });
      onPage(idx + 1);
    };
    el.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    const handlers = ["mousedown","touchstart","keydown","mousemove"];
    handlers.forEach(h => window.addEventListener(h, bump, { passive: true }));
    return () => {
      el.removeEventListener("scroll", onScroll);
      handlers.forEach(h => window.removeEventListener(h, bump));
    };
  }, [bump]);

  useEffect(() => {
    let raf;
    const tick = () => {
      const el = scrollerRef.current;
      // Phones use scroll-snap paging; a creeping scrollLeft would fight the
      // snap points and jitter, so idle auto-play is desktop-only.
      if (el && !window.matchMedia("(max-width: 700px)").matches) {
        const idle = Date.now() - lastInteract.current;
        if (idle > IDLE_MS) {
          if (!auto) setAuto(true);
          const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 2;
          if (atEnd) { lastInteract.current = Date.now(); setAuto(false); }
          else { el.scrollLeft += AUTO_SPEED; }
        }
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [auto]);

  useEffect(() => {
    const onKey = (e) => {
      const el = scrollerRef.current;
      if (!el) return;
      if (e.key === "ArrowRight") el.scrollLeft += window.innerWidth * 0.7;
      if (e.key === "ArrowLeft") el.scrollLeft -= window.innerWidth * 0.7;
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  useEffect(() => { if (window.lucide) window.lucide.createIcons(); });

  return (
    <React.Fragment>
      <div className="gt-gallery" ref={scrollerRef}>
        <div className="gt-track">
          {story.map((p, i) => <Panel key={i} p={p} subject={subject} nextSubject={nextSubject} onNext={onNext} />)}
        </div>
      </div>
      <div className={`gt-hint ${auto ? "auto" : ""}`}>
        <span className="dot"></span>
        {auto ? "Auto-playing — move to take control" : (phone ? "Swipe to explore →" : "Scroll to explore →")}
      </div>
    </React.Fragment>
  );
}

/* ============================================================
   Gallery-view chrome
   ============================================================ */
function GalleryChrome({ onBack, onMenu, page, total }) {
  return (
    <React.Fragment>
      <button className="gv-corner gv-back" onClick={onBack}>BACK</button>
      <div className="gv-wordmark" onClick={onBack}>
        <div className="name">NICKO TRENCHEV</div>
        <div className="sub">STUDIO</div>
      </div>
      <button className="gv-corner gv-menu" onClick={onMenu}>MENU</button>
      <div className="gv-counter">
        {String(page).padStart(2,"0")}<span> / {String(total).padStart(2,"0")}</span>
      </div>
      <div className="gv-credits">CREDITS</div>
    </React.Fragment>
  );
}

/* ============================================================
   Root
   ============================================================ */
function GalleriesTest() {
  const [galleries, setGalleries] = useState(null);   // list (null = loading)
  const [activeKey, setActiveKey] = useState(null);
  const [detail, setDetail] = useState(null);         // active gallery with items
  const [view, setView] = useState("grid");           // grid | loading | gallery
  const [subject, setSubject] = useState(null);       // full item with images
  const [overlay, setOverlay] = useState(null);
  const [typesOpen, setTypesOpen] = useState(false);
  const [page, setPage] = useState(1);

  // Fetch gallery list once. Honor a #category or #category/itemId hash
  // (deep-link from the home "+", or straight into one album's story).
  const pendingItem = useRef(null);
  useEffect(() => {
    fetch("/api/galleries").then((r) => r.json()).then((list) => {
      const arr = Array.isArray(list) ? list : [];
      setGalleries(arr);
      if (arr.length) {
        const hash = decodeURIComponent((window.location.hash || "").replace(/^#/, ""));
        const [hashKey, hashItem] = hash.split("/");
        const match = arr.find((g) => g.key === hashKey);
        if (match && hashItem) pendingItem.current = Number(hashItem);
        setActiveKey(match ? match.key : arr[0].key);
      }
    }).catch(() => setGalleries([]));
  }, []);

  // Fetch the active gallery's items whenever the key changes.
  useEffect(() => {
    if (!activeKey) return;
    fetch(`/api/galleries/${activeKey}`).then((r) => r.json()).then((d) => {
      setDetail(d);
      // Deep link straight into one album's story view.
      if (pendingItem.current && d && Array.isArray(d.items)) {
        const target = d.items.find((it) => it.id === pendingItem.current);
        pendingItem.current = null;
        if (target) open(target);
      }
    }).catch(() => setDetail(null));
  }, [activeKey]);

  const items = (detail && detail.items) || [];

  const nextSubject = (() => {
    if (!subject || !items.length) return items[0] || subject;
    const idx = items.findIndex((it) => it.id === subject.id);
    return items[(idx + 1) % items.length];
  })();

  const isPhone = useIsPhone(700);
  const story = subject ? buildStory(subject.images, subject.cover, isPhone) : [];

  const open = (s) => {
    // Fetch full item (with all images) before showing the story.
    fetch(`/api/galleries/${activeKey}/${s.id}`).then((r) => r.json()).then((full) => {
      setSubject(full); setPage(1); setView("loading"); setTypesOpen(false); setOverlay(null);
    }).catch(() => {});
  };
  const back = () => { setView("grid"); setSubject(null); };
  const selectCat = (key) => {
    setActiveKey(key); setTypesOpen(false); setView("grid"); setSubject(null);
    window.history.replaceState(null, "", "#" + encodeURIComponent(key));
  };
  const onLogo = () => {
    setTypesOpen(false); setOverlay(null);
    if (view !== "grid") back();
    else window.location.href = "../home_test/Home_Test.html";
  };
  const nav = (where) => {
    setOverlay(null);
    if (where === "about") window.location.href = "../about_test/About_Test.html";
  };

  useEffect(() => {
    document.body.style.overflow = overlay === "contact" ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [overlay]);

  useEffect(() => { if (window.lucide) window.lucide.createIcons(); }, [view, activeKey, typesOpen, overlay, page, detail]);

  return (
    <React.Fragment>
      {view === "grid" && <GridView gallery={detail} onOpen={open} dim={typesOpen} />}
      {view === "loading" && subject && <LoadingView subject={subject} onDone={() => setView("gallery")} />}
      {view === "gallery" && subject && (
        <GalleryView
          story={story}
          subject={subject}
          nextSubject={nextSubject}
          onBack={back}
          onPage={setPage}
          onNext={() => open(nextSubject)}
        />
      )}

      {view === "grid" && (
        <HomeChrome
          onLogo={onLogo}
          onMenu={() => { setOverlay("menu"); setTypesOpen(false); }}
          onContact={() => { setOverlay("contact"); setTypesOpen(false); }}
          onPlus={() => setTypesOpen(o => !o)}
          plusOpen={typesOpen}
        />
      )}

      {view === "gallery" && (
        <GalleryChrome
          onBack={back}
          onMenu={() => setOverlay("menu")}
          page={page}
          total={story.length}
        />
      )}

      {typesOpen && view === "grid" && galleries && <TypesMenu galleries={galleries} activeKey={activeKey} onPick={selectCat} />}
      {overlay === "menu" && <MainMenu onClose={() => setOverlay(null)} onContact={() => setOverlay("contact")} onNav={nav} />}
      {overlay === "contact" && <ContactForm onClose={() => setOverlay(null)} />}
    </React.Fragment>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<GalleriesTest />);
