/* global React, ReactDOM, MainMenu, ContactForm, Lenis */
/* ============================================================
   Videos / Films page.

   Data comes from /api/videos (editable in Admin → Films). Each film plays from
   EITHER a self-hosted file (streamed with Range support by the backend) or a
   YouTube / Vimeo embed. Third-party players are only injected on click, so no
   external frames, scripts or cookies load until the visitor asks to watch.
   ============================================================ */
const { useState, useEffect, useRef, useCallback } = React;

/* Poster URL helper — mirrors HIMG on the home page (absolute URLs pass through
   so a poster can also be an external still). */
const VIMG = (filename, size = "web") =>
  /^https?:\/\//.test(filename) ? filename : `/api/images/${size}/${encodeURIComponent(filename)}`;

/* One poster, two widths: the backend already generates a 600px thumb and a
   2400px web version, so let the browser pick per card size instead of shipping
   a 2400px file into a 430px slot. */
function posterProps(poster, sizes) {
  if (!poster || /^https?:\/\//.test(poster || "")) return { src: VIMG(poster) };
  return {
    src: VIMG(poster, "web"),
    srcSet: `${VIMG(poster, "thumb")} 600w, ${VIMG(poster, "web")} 2400w`,
    sizes,
  };
}

/* Build a privacy-friendly embed URL. Returns null for anything unrecognised —
   the admin panel validates on save, so this is the second line of defence. */
function embedUrl(url) {
  if (!url) return null;
  const yt = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]{6,})/i);
  if (yt) return `https://www.youtube-nocookie.com/embed/${yt[1]}?autoplay=1&rel=0&modestbranding=1&playsinline=1`;
  const vm = url.match(/vimeo\.com\/(?:video\/)?(\d+)/i);
  if (vm) return `https://player.vimeo.com/video/${vm[1]}?autoplay=1`;
  return null;
}

const PlayGlyph = () => (
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3l15 9-15 9V3z" /></svg>
);

/* How many films show before "VIEW MORE FILMS" is offered. */
const PAGE_SIZE = 6;

/* ============================================================
   Scroll reveal — one IntersectionObserver for the whole page.
   Elements opt in with className="v-reveal" and are settled by adding `.in`
   once, in DOM order, with a small stagger. Honours reduced-motion by
   revealing everything immediately.
   ============================================================ */
function useScrollReveal(deps) {
  useEffect(() => {
    const nodes = Array.from(document.querySelectorAll(".v-reveal:not(.in)"));
    if (!nodes.length) return;
    const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduced || !("IntersectionObserver" in window)) {
      nodes.forEach((n) => n.classList.add("in"));
      return;
    }
    const io = new IntersectionObserver((entries) => {
      // Stagger only within a single batch, so a group entering together
      // cascades while a lone element later on appears immediately.
      const shown = entries.filter((e) => e.isIntersecting);
      shown.forEach((e, i) => {
        e.target.style.setProperty("--v-delay", `${Math.min(i, 8) * 70}ms`);
        e.target.classList.add("in");
        io.unobserve(e.target);
      });
    }, { rootMargin: "0px 0px -10% 0px", threshold: 0.08 });
    nodes.forEach((n) => io.observe(n));
    return () => io.disconnect();
  }, deps);
}

/* ============================================================
   Lenis smooth scroll. Loaded from a CDN like React/Babel elsewhere on the
   site; if it fails to load the page simply scrolls natively.
   ============================================================ */
function useSmoothScroll(paused) {
  const lenisRef = useRef(null);
  useEffect(() => {
    if (typeof Lenis === "undefined") return;            // CDN blocked → native scroll
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    let lenis;
    // Smooth scroll is an enhancement: never let it take the page down with it.
    try { lenis = new Lenis({ duration: 1.15, smoothWheel: true, touchMultiplier: 1.6 }); }
    catch (e) { return; }
    lenisRef.current = lenis;
    let raf = 0;
    const loop = (t) => { lenis.raf(t); raf = requestAnimationFrame(loop); };
    raf = requestAnimationFrame(loop);
    return () => { cancelAnimationFrame(raf); lenis.destroy(); lenisRef.current = null; };
  }, []);

  /* Hand scrolling back to the browser while a full-screen overlay is open.
     Lenis listens for wheel/touch on the window, so without this it would
     swallow the gesture and try to scroll the (locked) page instead of the
     CONTACT form, which is long and scrolls inside itself. */
  useEffect(() => {
    const l = lenisRef.current;
    if (!l) return;
    try { if (paused) l.stop(); else l.start(); } catch (e) { /* non-critical */ }
  }, [paused]);
}

/* ============================================================
   Fit-to-width headline.

   The featured title is the page's signature moment, and it only reads that way
   as ONE line spanning the whole frame. Film names vary in length, so instead of
   a fixed size that happens to fit one title, measure the text and scale it to
   the container: every film gets a headline that lands edge to edge.
   ============================================================ */
function useFitText(text) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el || !el.parentElement) return;
    const BASE = 100; // measure at a known size, then scale by the ratio
    const fit = () => {
      /* Phones opt out: squeezing a long title onto one 360px line lands around
         23px, which throws away the whole point of the headline. Below this width
         the stylesheet lets it wrap at a big size instead. */
      if (window.matchMedia("(max-width: 520px)").matches) {
        el.style.removeProperty("font-size");
        return;
      }
      const avail = el.parentElement.clientWidth;
      if (!avail) return;
      el.style.fontSize = `${BASE}px`;
      /* Measure with `width: max-content` — the element then takes exactly the
         width of its single nowrap line. scrollWidth is NOT usable here: it
         clamps to the element's own width whenever the text does not overflow,
         which would let the title shrink to fit but never grow to fill. */
      el.style.width = "max-content";
      const w = el.getBoundingClientRect().width;
      el.style.removeProperty("width");
      if (!w) return;
      // Clamped so a one-word title can't balloon and a very long one stays legible.
      el.style.fontSize = `${Math.max(20, Math.min(240, (avail / w) * BASE))}px`;
    };
    fit();
    // Re-fit on resize. Observing the PARENT is safe: its width comes from the
    // page margins, so changing the child's font-size can't feed back into it.
    let ro = null;
    if ("ResizeObserver" in window) {
      ro = new ResizeObserver(fit);
      ro.observe(el.parentElement);
    } else {
      window.addEventListener("resize", fit);
    }
    // Inter arrives from a CDN — remeasure once the real face is in, or the fit
    // is computed against the fallback's metrics.
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(fit).catch(() => {});
    return () => {
      if (ro) ro.disconnect();
      else window.removeEventListener("resize", fit);
    };
  }, [text]);
  return ref;
}

/* ============================================================
   Player — the element that actually plays one film, used by both the hero
   (inline) and the lightbox.
   ============================================================ */
function Player({ film, autoPlay = true }) {
  const embed = embedUrl(film.url);
  if (film.src) {
    return (
      <video
        src={film.src}
        controls
        autoPlay={autoPlay}
        playsInline
        preload="metadata"
        poster={film.poster ? VIMG(film.poster) : undefined}
      />
    );
  }
  if (embed) {
    return (
      <iframe
        src={embed}
        title={film.title}
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowFullScreen
        referrerPolicy="strict-origin-when-cross-origin"
      />
    );
  }
  return <div className="nosrc">ФИЛМЪТ СКОРО</div>;
}

/* ============================================================
   Lightbox — a native <dialog>, so Escape, the backdrop and focus return are
   handled by the platform rather than re-implemented.
   ============================================================ */
function Lightbox({ film, onClose }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (!el.open) el.showModal();
    const onCancel = (e) => { e.preventDefault(); onClose(); };
    el.addEventListener("cancel", onCancel);
    return () => el.removeEventListener("cancel", onCancel);
  }, [onClose]);

  return (
    <dialog className="v-lightbox" ref={ref} aria-label={`${film.title} — film`}>
      <button className="lb-close" onClick={onClose}>CLOSE</button>
      <div className="v-lightbox-inner" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
        <div className="stage">
          <div className="stage-frame"><Player film={film} /></div>
          <div className="stage-cap">
            <div className="name">{film.title}</div>
            {film.subtitle && <div className="sub">{film.subtitle}</div>}
          </div>
        </div>
      </div>
    </dialog>
  );
}

/* ============================================================
   Featured film — the hero. Shows the poster until pressed, then swaps the
   still for the player in place (no navigation, no layout shift).
   ============================================================ */
function Featured({ film }) {
  const [playing, setPlaying] = useState(false);
  const headline = `FEATURED FILM: ${(film.subtitle || film.title).toUpperCase()}`;
  const titleRef = useFitText(headline);
  return (
    <section className="v-featured">
      <div className="v-frame v-reveal">
        {playing ? (
          <Player film={film} />
        ) : (
          <React.Fragment>
            {film.poster
              ? <img {...posterProps(film.poster, "100vw")} alt={`${film.title}${film.subtitle ? ` — ${film.subtitle}` : ""}`} />
              : <div className="v-skeleton" style={{ width: "100%", height: "100%" }} />}
            <button className="v-play" onClick={() => setPlaying(true)}
              aria-label={`Play ${film.title}`}>
              <span className="disc"><PlayGlyph /></span>
            </button>
          </React.Fragment>
        )}
      </div>
      <h2 className="v-featured-title v-reveal" ref={titleRef}>{headline}</h2>
      <div className="v-featured-meta v-reveal">
        <span className="sub">{film.title}</span>
        {film.tag && <span className="tag">{film.tag.toUpperCase()}</span>}
        {film.duration && <span className="tag">{film.duration}</span>}
      </div>
    </section>
  );
}

/* One film card in the grid. The whole card is the button — a 44px+ target
   everywhere, with the small play badge as the visual affordance. */
function FilmCard({ film, onOpen }) {
  return (
    <button className="v-card v-reveal" onClick={() => onOpen(film)}
      aria-label={`Play ${film.title}${film.subtitle ? ` — ${film.subtitle}` : ""}`}>
      <div className="shot">
        {film.poster
          ? <img {...posterProps(film.poster, "(max-width: 520px) 100vw, (max-width: 820px) 50vw, 33vw")}
              alt={`${film.title}${film.subtitle ? ` — ${film.subtitle}` : ""}`} loading="lazy" />
          : <div className="noposter">NO POSTER YET</div>}
        <span className="v-badge"><PlayGlyph /></span>
        {film.duration && <span className="v-duration">{film.duration}</span>}
      </div>
      <div className="label">
        <span className="name">{film.title}</span>
        {film.subtitle && <span className="sub">{film.subtitle}</span>}
      </div>
    </button>
  );
}

/* Loading state: skeletons shaped like the real thing, so nothing jumps when
   the data lands. */
function Skeletons() {
  return (
    <React.Fragment>
      <section className="v-featured">
        <div className="v-frame v-skeleton" />
      </section>
      <div className="v-grid">
        {Array.from({ length: 6 }).map((_, i) => (
          <div key={i}>
            <div className="shot v-skeleton" style={{ aspectRatio: "16 / 9" }} />
          </div>
        ))}
      </div>
    </React.Fragment>
  );
}

/* ============================================================
   PAGE
   ============================================================ */
function VideosPage() {
  const [data, setData] = useState(null);       // null = loading
  const [error, setError] = useState("");
  const [overlay, setOverlay] = useState(null); // null | "menu" | "contact"
  const [open, setOpen] = useState(null);       // film shown in the lightbox
  const [shown, setShown] = useState(PAGE_SIZE);

  useEffect(() => {
    let alive = true;
    fetch("/api/videos")
      .then((r) => { if (!r.ok) throw new Error("Request failed"); return r.json(); })
      .then((d) => { if (alive) setData({ featured: d.featured || null, videos: Array.isArray(d.videos) ? d.videos : [] }); })
      .catch(() => { if (alive) { setError("Филмите не се заредиха."); setData({ featured: null, videos: [] }); } });
    return () => { alive = false; };
  }, []);

  // Overlays and the lightbox sit above the page and own the gesture themselves.
  useSmoothScroll(overlay !== null || open !== null);
  useScrollReveal([data, shown]);

  // Lock the page behind a full overlay, matching the home page's behaviour.
  useEffect(() => {
    document.body.style.overflow = overlay ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [overlay]);

  const nav = useCallback((where) => {
    if (where === "portfolio") window.location.href = "../galleries_test/Galleries_Test.html";
    if (where === "about") window.location.href = "../about_test/About_Test.html";
    if (where === "videos") setOverlay(null); // already here
  }, []);
  const openContact = () => setOverlay("contact");

  const films = (data && data.videos) || [];
  const visible = films.slice(0, shown);
  const hasMore = films.length > shown;

  return (
    <React.Fragment>
      {/* Fixed chrome — same components/classes as every other page */}
      <div className="h-logo" onClick={() => { window.location.href = "../home_test/Home_Test.html"; }}>
        <div className="lname">NICKO TRENCHEV</div>
        <div className="lsub">STUDIO</div>
      </div>
      <button className="h-chrome-label h-menu-btn" onClick={() => setOverlay("menu")}>MENU</button>
      <button className="h-chrome-label h-contact-btn" onClick={openContact}>CONTACT</button>

      <main>
        <div className="v-head">
          <h1 className="v-title">VIDEOS</h1>
        </div>

        {data === null ? (
          <Skeletons />
        ) : !data.featured && films.length === 0 ? (
          <div className="v-empty">
            <h2>{error ? "Нещо се обърка" : "Скоро."}</h2>
            <p>{error
              ? "Опреснете страницата — ако проблемът продължи, филмите ще се появят скоро."
              : "Филмите ни се подготвят. Междувременно разгледайте фотографията или ни пишете за видеозаснемане."}</p>
          </div>
        ) : (
          <React.Fragment>
            {data.featured && <Featured film={data.featured} />}

            {visible.length > 0 && (
              <div className="v-grid">
                {visible.map((f) => <FilmCard key={f.id} film={f} onOpen={setOpen} />)}
              </div>
            )}

            <div className="v-foot">
              {hasMore && (
                <button className="v-more v-reveal" onClick={() => setShown((n) => n + PAGE_SIZE)}>
                  VIEW MORE FILMS
                  <svg viewBox="0 0 44 10" aria-hidden="true"><path d="M0 5h40M35 1l5 4-5 4" /></svg>
                </button>
              )}
            </div>
          </React.Fragment>
        )}
        <div className="v-page-foot" />
      </main>

      {open && <Lightbox film={open} onClose={() => setOpen(null)} />}

      {overlay === "menu" && (
        <MainMenu onClose={() => setOverlay(null)} onContact={openContact} onNav={nav} />
      )}
      {overlay === "contact" && <ContactForm onClose={() => setOverlay(null)} />}
    </React.Fragment>
  );
}

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