/* Books List — React app */

const { useState, useEffect, useRef, useMemo, useCallback } = React;
const DATA = window.BOOKS_DATA;

// ─── Small deterministic hash for cover styling variation ────
function hash(str) {
  let h = 0;
  for (let i = 0; i < str.length; i++) h = (h * 31 + str.charCodeAt(i)) | 0;
  return Math.abs(h);
}

// ─── Cover-art fetch (Open Library) ──────────────────────────
const coverCache = {};
const inFlight = {};

function normalizeAuthor(a) {
  // Take just the first listed author for matching
  return a.split(/[&,]/)[0].trim();
}

async function fetchCover(title, author, override) {
  // Explicit cover_i override: skip search entirely
  if (override) return `https://covers.openlibrary.org/b/id/${override}-L.jpg`;

  const key = `${title}|${author}`;
  if (key in coverCache) return coverCache[key];
  if (key in inFlight) return inFlight[key];

  const url = `https://openlibrary.org/search.json?title=${encodeURIComponent(title)}&author=${encodeURIComponent(normalizeAuthor(author))}&limit=5`;

  const promise = fetch(url)
    .then((r) => r.ok ? r.json() : null)
    .then((data) => {
      if (!data || !data.docs) return null;
      const doc = data.docs.find((d) => d.cover_i);
      if (!doc) return null;
      return `https://covers.openlibrary.org/b/id/${doc.cover_i}-L.jpg`;
    })
    .catch(() => null)
    .then((v) => { coverCache[key] = v; return v; });

  inFlight[key] = promise;
  return promise;
}

// ─── Cover ────────────────────────────────────────────────
function BookCover({ book, code, large, idx }) {
  const h = hash(book.title + book.author);
  const variant = h % 4;
  const accentCorner = h % 4;
  const titleStyle = h % 3;
  const bgClass = ["c-void", "c-carbon", "c-steel", "c-ink"][variant];

  const tlen = book.title.length;
  const titleSize = tlen > 38 ? "var(--cv-title-xs)"
                  : tlen > 26 ? "var(--cv-title-sm)"
                  : tlen > 16 ? "var(--cv-title-md)"
                  : "var(--cv-title-lg)";
  const titleClass = ["", "cv-chrome", "cv-glow"][titleStyle];

  const [imgUrl, setImgUrl] = useState(null);
  const [imgLoaded, setImgLoaded] = useState(false);

  useEffect(() => {
    let alive = true;
    setImgLoaded(false);
    setImgUrl(null);
    fetchCover(book.title, book.author, book.cover).then((url) => {
      if (!alive) return;
      if (url) setImgUrl(url);
    });
    return () => { alive = false; };
  }, [book.title, book.author, book.cover]);

  return (
    <div className={`cover ${bgClass} ${imgLoaded ? "has-art" : ""}`}>
      <div className="cv-grid" />

      {/* Typographic layer (always present; fades back when art loads) */}
      <div className="cv-typo">
        <div className="cv-top">
          <span className="cv-code">{code} / {String(idx + 1).padStart(2, "0")}</span>
          <span className="cv-plus" />
        </div>

        <div className="cv-mid">
          <div
            className={`cv-title ${titleClass}`}
            data-text={book.title}
            style={{ fontSize: titleSize }}
          >
            {book.title}
          </div>
        </div>

        <div className="cv-bot">
          <div className="cv-rule" />
          <div className="cv-author">— {book.author}</div>
        </div>

        {accentCorner === 0 && <span className="cv-marker tl" />}
        {accentCorner === 1 && <span className="cv-marker tr" />}
        {accentCorner === 2 && <span className="cv-marker bl" />}
        {accentCorner === 3 && <span className="cv-marker br" />}
      </div>

      {/* Cover art layer */}
      {imgUrl && (
        <img
          className={`cv-art ${imgLoaded ? "loaded" : ""}`}
          src={imgUrl}
          alt={book.title}
          onLoad={(e) => {
            // Open Library returns a tiny placeholder when no cover exists; treat ultra-small as miss
            if (e.target.naturalWidth < 80) return;
            setImgLoaded(true);
          }}
          onError={() => setImgLoaded(false)}
        />
      )}

      {/* When art loaded, overlay a thin gradient + corner labels */}
      {imgLoaded && (
        <div className="cv-art-overlay">
          <div className="cv-top">
            <span className="cv-code">{code} / {String(idx + 1).padStart(2, "0")}</span>
            <span className="cv-plus" />
          </div>
        </div>
      )}

      {large && (
        <>
          <span className="vf-c tl" />
          <span className="vf-c tr" />
          <span className="vf-c bl" />
          <span className="vf-c br" />
        </>
      )}
    </div>
  );
}

// ─── Slot transform helper ───────────────────────────────
function slotStyle(off, isMobile) {
  const abs = Math.abs(off);
  if (abs > 2) {
    return {
      transform: `translateX(0) translateZ(-500px) rotateY(0deg)`,
      opacity: 0,
      pointerEvents: "none",
      zIndex: 0,
    };
  }
  if (off === 0) {
    return {
      transform: "translateX(0) translateZ(40px) rotateY(0deg)",
      opacity: 1,
      zIndex: 5,
      cursor: "default",
    };
  }
  const dir = off > 0 ? 1 : -1;
  const xLayer1 = isMobile ? 130 : 200;
  const xLayer2 = isMobile ? 230 : 360;
  const x = abs === 1 ? xLayer1 * dir : xLayer2 * dir;
  const rot = abs === 1 ? -34 * dir : -46 * dir;
  const z = abs === 1 ? -80 : -200;
  return {
    transform: `translateX(${x}px) translateZ(${z}px) rotateY(${rot}deg)`,
    opacity: abs === 1 ? 0.85 : 0.45,
    filter: abs === 1 ? "brightness(0.9)" : "brightness(0.55)",
    zIndex: 5 - abs,
    cursor: "pointer",
  };
}

// ─── Rolodex ──────────────────────────────────────────────
function Rolodex({ category, onBack }) {
  const cat = DATA.categories[category];
  const books = cat.books;
  const [idx, setIdx] = useState(0);
  const [isMobile, setIsMobile] = useState(false);
  const [touchStart, setTouchStart] = useState(null);

  useEffect(() => {
    const mq = window.matchMedia("(max-width: 900px)");
    const onCh = () => setIsMobile(mq.matches);
    onCh();
    mq.addEventListener("change", onCh);
    return () => mq.removeEventListener("change", onCh);
  }, []);

  // Wrap idx around
  const go = useCallback((dir) => {
    setIdx((i) => (i + dir + books.length) % books.length);
  }, [books.length]);

  // Compute shortest signed offset for circular wrap
  const offsetOf = useCallback((i) => {
    const n = books.length;
    let raw = i - idx;
    if (raw > n / 2) raw -= n;
    if (raw < -n / 2) raw += n;
    return raw;
  }, [idx, books.length]);

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "ArrowRight") go(1);
      else if (e.key === "ArrowLeft") go(-1);
      else if (e.key === "Escape") onBack();
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [go, onBack]);

  // Reset to first when category changes
  useEffect(() => { setIdx(0); }, [category]);

  const current = books[idx];

  // Click-anywhere advance: robust against 3D-rotated card hit-test quirks
  const onStageClick = useCallback((e) => {
    // Arrows handle themselves
    if (e.target.closest(".rolo-arrow")) return;
    // Direct hit on a slot card — jump to that book
    const slotEl = e.target.closest(".rolo-slot");
    if (slotEl && slotEl.dataset.idx != null) {
      const i = parseInt(slotEl.dataset.idx, 10);
      if (i !== idx) setIdx(i);
      return;
    }
    // Otherwise (rotated card missed hit-test, or click in side gutter):
    // use click X relative to stage center
    const rect = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - rect.left;
    if (x < rect.width / 2) go(-1);
    else go(1);
  }, [go, idx]);

  const onTouchStart = (e) => setTouchStart(e.touches[0].clientX);
  const onTouchEnd = (e) => {
    if (touchStart == null) return;
    const dx = e.changedTouches[0].clientX - touchStart;
    if (dx > 50) go(-1);
    else if (dx < -50) go(1);
    setTouchStart(null);
  };

  return (
    <div className="rolo-shell">
      <div className="rolo-head">
        <button className="back-btn" onClick={onBack}>
          <span className="arr">←</span>
          <span>BACK TO CATEGORIES</span>
        </button>
        <div className="rolo-meta">
          <span className="rolo-code">DIR / {cat.code}</span>
          <span className="rolo-name">{cat.name}</span>
          <span className="rolo-count">
            {String(idx + 1).padStart(2, "0")} / {String(books.length).padStart(2, "0")}
          </span>
        </div>
      </div>

      <div
        className="rolo-stage"
        onClick={onStageClick}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
      >
        <button className="rolo-arrow left" aria-label="Previous" onClick={(e) => { e.stopPropagation(); go(-1); }}>
          <span>←</span>
        </button>
        <button className="rolo-arrow right" aria-label="Next" onClick={(e) => { e.stopPropagation(); go(1); }}>
          <span>→</span>
        </button>

        <div className="rolo-track">
          {books.map((book, i) => {
            const off = offsetOf(i);
            const style = slotStyle(off, isMobile);
            return (
              <div
                key={i} /* STABLE key so transforms animate */
                className="rolo-slot"
                data-idx={i}
                style={style}
              >
                <BookCover
                  book={book}
                  code={cat.code}
                  large={off === 0}
                  idx={i}
                />
              </div>
            );
          })}
        </div>
      </div>

      {/* Synopsis */}
      <div className="rolo-synopsis-wrap">
        <div className="syn-rec">
          <span className="rec-dot" />
          <span className="rec-text">SYNOPSIS</span>
        </div>
        <div className="rolo-synopsis" key={category + "-" + idx}>
          <div className="syn-title-row">
            <span className="syn-num">/{String(idx + 1).padStart(2, "0")}</span>
            <h3 className="syn-title">{current.title}</h3>
          </div>
          <div className="syn-author">— {current.author}</div>
          <p className="syn-body">{current.synopsis}</p>
        </div>
      </div>

      <div className="thumb-row" aria-hidden="false">
        {books.map((b, i) => (
          <button
            key={i}
            className={`thumb ${i === idx ? "active" : ""}`}
            onClick={() => setIdx(i)}
            aria-label={b.title}
            title={b.title}
          />
        ))}
      </div>
    </div>
  );
}

// ─── Category Grid ────────────────────────────────────────
function CategoryGrid({ onSelect }) {
  return (
    <div className="cat-wrap">
      <div className="cat-hero">
        <div className="cat-prompt-meta fade-up">
          <span className="meta">SYS / READER</span>
          <span className="meta">{DATA.order.length.toString().padStart(2, "0")} CHANNELS</span>
        </div>
        <h2 className="cat-prompt fade-up d1">
          <span className="glow" data-text="Choose a category.">Choose a category.</span>
        </h2>
        <p className="cat-sub fade-up d2">
          Each channel is a different frequency. Click in.
        </p>
      </div>

      <div className="cat-list">
        {DATA.order.map((key, i) => {
          const cat = DATA.categories[key];
          return (
            <button
              key={key}
              className="cat-row fade-up"
              data-key={key}
              style={{ animationDelay: `${0.6 + i * 0.18}s` }}
              onClick={() => onSelect(key)}
            >
              <span className="cat-num">/{String(i + 1).padStart(2, "0")}</span>
              <span className="cat-code">{cat.code}</span>
              <span className="cat-name">{cat.name}</span>
              <span className="cat-kicker">{cat.kicker}</span>
              <span className="cat-count">{String(cat.books.length).padStart(2, "0")} TITLES</span>
              <span className="cat-arrow">→</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ─── Top-level App ────────────────────────────────────────
function App() {
  const initial = (() => {
    const m = new URLSearchParams(location.search).get("cat");
    return DATA.order.includes(m) ? m : null;
  })();
  const [selected, setSelected] = useState(initial);

  useEffect(() => {
    const url = new URL(location.href);
    if (selected) url.searchParams.set("cat", selected);
    else url.searchParams.delete("cat");
    history.replaceState(null, "", url.toString());
    window.scrollTo({ top: 0, behavior: "smooth" });
  }, [selected]);

  return (
    <>
      {selected
        ? <Rolodex category={selected} onBack={() => setSelected(null)} />
        : <CategoryGrid onSelect={setSelected} />
      }
    </>
  );
}

ReactDOM.createRoot(document.getElementById("books-root")).render(<App />);
