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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "vermillion",
  "grain": "on"
}/*EDITMODE-END*/;

const ACCENTS = {
  vermillion: "oklch(0.62 0.19 28)",
  ochre: "oklch(0.72 0.15 85)",
  ink: "#17130f",
  forest: "oklch(0.50 0.12 155)",
  cobalt: "oklch(0.48 0.18 250)",
  plum: "oklch(0.45 0.18 330)",
};

// Clean-path routing. Each page has a real URL (so it's individually
// crawlable / prerendered); the Vercel SPA rewrite serves index.html for any
// path without a static file, and this reads the path back into a route.
const SEG_TO_ROUTE = {
  list: "list", leaderboards: "leaderboards", legal: "legal",
  "pressing-guide": "pressing-guide", account: "account", collection: "collection",
};
// Read the current URL path into a route descriptor (null => home).
function bbrParsePath() {
  const segs = (location.pathname || "/").split("/").filter(Boolean);
  if (!segs.length) return bbrParseLegacyHash();   // "/" — but honour old hash links
  if (segs[0] === "album" && segs[1]) return { route: "album", slug: decodeURIComponent(segs[1]) };
  if (segs[0] === "collector" && segs[1]) return { route: "public-collection", publicId: decodeURIComponent(segs[1]) };
  if (SEG_TO_ROUTE[segs[0]]) return { route: SEG_TO_ROUTE[segs[0]] };
  return null; // unknown path -> home (SPA), keeps a friendly 404-free fallback
}
// Back-compat: resolve the old hash deep links (#album=, #collection=, #list…)
// so previously-shared URLs still land on the right page.
function bbrParseLegacyHash() {
  const h = (location.hash || "").replace(/^#/, "");
  if (!h) return null;
  if (h.startsWith("collection=")) return { route: "public-collection", publicId: decodeURIComponent(h.slice(11)) };
  if (h.startsWith("album=")) return { route: "album", slug: decodeURIComponent(h.slice(6)) };
  const legacy = { mycollection: "collection", list: "list", leaderboards: "leaderboards", legal: "legal", "pressing-guide": "pressing-guide", account: "account" };
  if (legacy[h]) return { route: legacy[h] };
  return null;
}
// Build the clean path for the current route ("/" = home).
function bbrRouteToPath(route, albumSlug, publicUser) {
  if (route === "home") return "/";
  if (route === "album" && albumSlug) return "/album/" + encodeURIComponent(albumSlug);
  if (route === "collection") return "/collection";
  if (route === "public-collection" && publicUser) return "/collector/" + encodeURIComponent(publicUser.id);
  return "/" + route; // list | leaderboards | legal | pressing-guide | account
}

// Per-route <title> + meta description, kept in sync as the SPA navigates.
const BBR_SITE_DESC = "The Lead-In is a long-form music publication: one definitive, ever-evolving ranking of the 100 greatest albums ever recorded, every placement defended in an essay with pressing guides and scoring data. Track and value your vinyl collection and climb the collector leaderboard.";
function bbrSetMetaTag(selector, attr, value) {
  const el = document.head.querySelector(selector);
  if (el) el.setAttribute(attr, value);
}
function bbrApplyRouteMeta(route, album, publicUser) {
  let title, desc;
  if (route === "album" && album) {
    const dek = (window.ESSAYS_FULL && window.ESSAYS_FULL[album.slug] && window.ESSAYS_FULL[album.slug].essay && window.ESSAYS_FULL[album.slug].essay.dek) || "";
    title = album.title + " — " + album.artist + " (№ " + album.rank + ") | The Lead-In";
    desc = dek || (album.title + " by " + album.artist + " (" + album.year + "), ranked № " + album.rank + " in The Lead-In's hundred greatest albums — a full essay with pressing guide and scoring data.");
  } else if (route === "list") {
    title = "The 100 Greatest Albums, Ranked 100→1 | The Lead-In";
    desc = "The Lead-In's definitive ranking of the hundred greatest albums ever recorded, counted down from 100 to 1 — every placement defended in writing.";
  } else if (route === "leaderboards") {
    title = "Collector Leaderboards | The Lead-In";
    desc = "See how collectors rank by collection value, genre and how much of the canon they own. Every collection is public.";
  } else if (route === "collection") {
    title = "My Collection — Track & Value Your Vinyl | The Lead-In";
    desc = "Log the vinyl you own, get a live market valuation, track your collection's value over time and climb the collector leaderboard.";
  } else if (route === "pressing-guide") {
    title = "Pressing Guides | The Lead-In";
    desc = "Which pressing to buy and which to avoid — runout etchings, reissues and mastering notes for the canon.";
  } else if (route === "public-collection") {
    title = ((publicUser && publicUser.name) ? publicUser.name + "'s collection" : "A collector's collection") + " | The Lead-In";
    desc = "A public vinyl collection on The Lead-In — records, conditions and estimated values, open for the community to judge.";
  } else if (route === "account") {
    title = "Account settings | The Lead-In";
    desc = BBR_SITE_DESC;
  } else if (route === "legal") {
    title = "Legal | The Lead-In";
    desc = BBR_SITE_DESC;
  } else {
    title = "The Lead-In — The 100 Greatest Albums, Defended in Writing";
    desc = BBR_SITE_DESC;
  }
  document.title = title;
  bbrSetMetaTag('meta[name="description"]', "content", desc);
  bbrSetMetaTag('meta[property="og:title"]', "content", title);
  bbrSetMetaTag('meta[property="og:description"]', "content", desc);
  bbrSetMetaTag('meta[name="twitter:title"]', "content", title);
  bbrSetMetaTag('meta[name="twitter:description"]', "content", desc);
  // Canonical + og:url track the real path now that pages have their own URLs.
  const url = "https://www.theleadin.com" + bbrRouteToPath(route, album && album.slug, publicUser);
  bbrSetMetaTag('link[rel="canonical"]', "href", url);
  bbrSetMetaTag('meta[property="og:url"]', "content", url);
}

function App() {
  const [route, setRoute] = useState("home"); // 'home' | 'list' | 'album' | 'collection'
  const [albumSlug, setAlbumSlug] = useState(null);
  const [tweakOn, setTweakOn] = useState(false);
  const [tweaks, setTweaks] = useState(TWEAK_DEFAULTS);

  // ---- My Collection: auth + demo state ----
  const [user, setUser] = useState(null);                     // null = logged out (real Supabase session)
  const [authReady, setAuthReady] = useState(false);          // false until the initial session check resolves
  const [dataMode, setDataMode] = useState("populated");        // 'populated' | 'empty'
  const [authView, setAuthView] = useState(null);              // null|'login'|'signup'|'forgot'
  const [gate, setGate] = useState(null);                       // null|'collection'|'wishlist'
  const [publicUser, setPublicUser] = useState(null);           // {id, name} for the public collection view

  const albums = window.ALBUMS;

  // Restore from localStorage
  useEffect(() => {
    // Routing is driven by the URL path so every page is its own crawlable URL
    // and a refresh keeps you put. A bare "/" (no descriptor) falls through to
    // the default 'home'. Old #hash deep links are honoured for back-compat.
    const parsed = bbrParsePath();
    if (parsed) {
      if (parsed.route === "public-collection") { setPublicUser({ id: parsed.publicId, name: "" }); setRoute("public-collection"); }
      else if (parsed.route === "album") { setAlbumSlug(parsed.slug); setRoute("album"); }
      else setRoute(parsed.route);
    }
    const saved = {};
    ["accent", "grain"].forEach(k => {
      const v = localStorage.getItem("bbr:" + k);
      if (v) saved[k] = v;
    });
    if (Object.keys(saved).length) setTweaks(t => ({ ...t, ...saved }));
    // (auth is restored from the real Supabase session, see the effect below)
    const dm = localStorage.getItem("bbr:mc:dm");
    if (dm === "empty" || dm === "populated") setDataMode(dm);
  }, []);

  // ---- real Supabase auth: hydrate session on mount + subscribe to changes ----
  useEffect(() => {
    if (!window.BBR_auth) { setAuthReady(true); return; }
    let unsub = null;
    const sync = (u) => {
      setUser(u);
      if (window.BBR_store) {
        if (u && u.id) window.BBR_store.onLogin(u.id);
        else window.BBR_store.onLogout();
      }
    };
    window.BBR_auth.current().then((u) => { if (u) sync(u); }).finally(() => setAuthReady(true));
    unsub = window.BBR_auth.onChange((u) => { sync(u); setAuthReady(true); });
    return () => { if (unsub) unsub(); };
  }, []);
  // Keep the URL hash in sync with the current page so a refresh restores
  // where you are AND browser back/forward step through in-app pages. Each
  // in-app navigation pushes a history entry; when back/forward fires popstate
  // the browser has already updated the hash, so target === location.hash here
  // and we skip the push (no loop). Skip the first pass: on mount the hash IS
  // the source of truth (the restore effect reads it).
  const hashSyncReady = React.useRef(false);
  useEffect(() => {
    if (!hashSyncReady.current) { hashSyncReady.current = true; return; }
    const target = bbrRouteToPath(route, albumSlug, publicUser);
    if (location.pathname !== target) {
      // strip any legacy hash when we push a clean path
      history.pushState(null, "", target + location.search);
    } else if (location.hash) {
      history.replaceState(null, "", target + location.search);
    }
  }, [route, albumSlug, publicUser]);
  // Back/forward: apply the route encoded in the (browser-updated) path.
  useEffect(() => {
    const onPop = () => {
      const parsed = bbrParsePath();
      if (!parsed) { setRoute("home"); setAlbumSlug(null); return; }
      if (parsed.route === "public-collection") { setPublicUser({ id: parsed.publicId, name: "" }); setRoute("public-collection"); }
      else if (parsed.route === "album") { setAlbumSlug(parsed.slug); setRoute("album"); }
      else { setAlbumSlug(null); setRoute(parsed.route); }
    };
    window.addEventListener("popstate", onPop);
    return () => window.removeEventListener("popstate", onPop);
  }, []);
  // 'account' is the only strictly logged-in route — if a refresh lands there
  // without a session, send the visitor home rather than show a blank page.
  useEffect(() => {
    if (authReady && !user && route === "account") { setRoute("home"); setAlbumSlug(null); }
  }, [authReady, user, route]);
  useEffect(() => {
    Object.entries(tweaks).forEach(([k, v]) => localStorage.setItem("bbr:" + k, v));
  }, [tweaks]);
  useEffect(() => { localStorage.setItem("bbr:mc:dm", dataMode); }, [dataMode]);
  // setMode only affects guests now; logged-in data is driven by onLogin/onLogout.
  useEffect(() => { if (window.BBR_store && !user) window.BBR_store.setMode(); }, [dataMode, user]);

  // Apply accent + grain to document
  useEffect(() => {
    document.documentElement.style.setProperty("--accent", ACCENTS[tweaks.accent] || ACCENTS.vermillion);
    document.body.classList.toggle("grain", tweaks.grain !== "off");
  }, [tweaks.accent, tweaks.grain]);

  // Tweak mode
  useEffect(() => {
    const onMsg = (e) => {
      const d = e.data || {};
      if (d.type === "__activate_edit_mode") setTweakOn(true);
      if (d.type === "__deactivate_edit_mode") setTweakOn(false);
    };
    window.addEventListener("message", onMsg);
    window.parent.postMessage({ type: "__edit_mode_available" }, "*");
    return () => window.removeEventListener("message", onMsg);
  }, []);

  const setKey = (k, v) => {
    setTweaks(t => ({ ...t, [k]: v }));
    window.parent.postMessage({ type: "__edit_mode_set_keys", edits: { [k]: v } }, "*");
  };

  // Remember where the user was on the list, so returning from a deep dive
  // lands them back at the same scroll position instead of jumping to the top.
  const listScrollRef = useRef(0);
  const restoreListScrollRef = useRef(false);

  const goHome = () => { setRoute("home"); setAlbumSlug(null); window.scrollTo(0, 0); };
  const goList = () => {
    // Coming back from an album → restore the saved list scroll position.
    restoreListScrollRef.current = (route === "album");
    setRoute("list"); setAlbumSlug(null);
    if (!restoreListScrollRef.current) window.scrollTo(0, 0);
  };
  const goAlbum = (slugOrAlbum) => {
    // Capture the list scroll position before leaving so we can return to it.
    if (route === "list") listScrollRef.current = window.scrollY;
    const slug = typeof slugOrAlbum === "string" ? slugOrAlbum : slugOrAlbum.slug;
    setAlbumSlug(slug); setRoute("album"); window.scrollTo(0, 0);
  };

  // After the list re-mounts, restore the saved scroll position if we arrived
  // here via "back" from a deep dive. A couple of rAF passes lets row heights
  // settle (cover art, etc.) before we snap to the target offset.
  useLayoutEffect(() => {
    if (route !== "list" || !restoreListScrollRef.current) return;
    restoreListScrollRef.current = false;
    const target = listScrollRef.current;
    window.scrollTo(0, target);
    let n = 0;
    const settle = () => {
      window.scrollTo(0, target);
      if (++n < 3) requestAnimationFrame(settle);
    };
    requestAnimationFrame(settle);
  }, [route]);
  const goCollection = () => { setRoute("collection"); setAlbumSlug(null); window.scrollTo(0, 0); };
  const goLegal = () => { setRoute("legal"); setAlbumSlug(null); window.scrollTo(0, 0); };
  const goPressingGuide = () => { setRoute("pressing-guide"); setAlbumSlug(null); window.scrollTo(0, 0); };
  const goLeaderboards = () => { setRoute("leaderboards"); setAlbumSlug(null); window.scrollTo(0, 0); };
  const goPublicCollection = (uid, name) => { setPublicUser({ id: uid, name: name || "" }); setRoute("public-collection"); window.scrollTo(0, 0); };
  const goAccount = () => { setRoute("account"); setAlbumSlug(null); window.scrollTo(0, 0); };
  window.__nav = { goHome, goList, goAlbum, goCollection, goLegal, goPressingGuide, goLeaderboards, goPublicCollection, goAccount };

  // ---- auth helpers ----
  const openAuth = (mode) => setAuthView(mode);
  const onAuthed = (u) => { setUser(u || null); setAuthView(null); setGate(null); goCollection(); };
  const signOut = async () => { try { if (window.BBR_auth) await window.BBR_auth.signOut(); } catch (e) {} setUser(null); goCollection(); };
  const requireAuth = (intent) => { if (user) return true; setGate(intent || "collection"); return false; };
  window.__bbrRequireAuth = requireAuth; // available to essay pages etc.

  const currentAlbum = albumSlug ? albums.find(a => a.slug === albumSlug) : albums[0];

  // Per-route document title + meta description (for JS-rendering crawlers and
  // for the browser tab). The static <head> + #root fallback cover no-JS bots.
  useEffect(() => { bbrApplyRouteMeta(route, route === "album" ? currentAlbum : null, publicUser); },
    [route, albumSlug, currentAlbum, publicUser]);

  return (
    <>
      <TopNav
        route={route} onHome={goHome} onList={goList} onCollection={goCollection}
        onLeaderboards={goLeaderboards}
        currentRank={currentAlbum?.rank}
        user={user} onSignIn={() => openAuth("login")} onSignOut={signOut} onSettings={goAccount}
      />

      {route === "home" && (
        <Home albums={albums} onAlbum={goAlbum} onList={goList} user={user} authReady={authReady} onCollection={goCollection} onLeaderboards={goLeaderboards} onAuthOpen={openAuth} />
      )}
      {route === "list" && (
        <ListPage albums={albums} onAlbum={goAlbum} onRequireAuth={requireAuth} />
      )}
      {route === "album" && currentAlbum && (
        <AlbumPage album={currentAlbum} onHome={goHome} onList={goList} onAlbum={goAlbum} all={albums} />
      )}
      {route === "collection" && (
        <MyCollection
          albums={albums} user={user} dataMode={dataMode}
          onAuthOpen={openAuth} onGate={(intent) => setGate(intent || "collection")}
        />
      )}
      {route === "legal" && window.Legal && (
        <Legal onHome={goHome} />
      )}
      {route === "pressing-guide" && window.PressingGuide && (
        <PressingGuide onHome={goHome} onCollection={goCollection} />
      )}
      {route === "leaderboards" && window.Leaderboards_v2 && (
        <Leaderboards_v2 albums={albums} currentUserId={user && user.id} onViewCollection={goPublicCollection} />
      )}
      {route === "public-collection" && window.PublicCollectionV2 && publicUser && (
        <PublicCollectionV2 userId={publicUser.id} displayName={publicUser.name} albums={albums} onBack={goLeaderboards} />
      )}
      {route === "account" && user && window.AccountSettings && (
        <AccountSettings user={user} onBack={goCollection} onSignOut={signOut} onViewPublic={goPublicCollection} />
      )}

      {window.Footer && (
        <Footer
          onLegal={goLegal}
          onPressingGuide={goPressingGuide}
          onList={goList}
          onCollection={goCollection}
        />
      )}

      {/* Auth screens */}
      {authView && (
        <AuthScreen mode={authView} onMode={setAuthView} onAuthed={onAuthed} onClose={() => setAuthView(null)} />
      )}

      {/* Signup gate */}
      {gate && (
        <SignupGate
          intent={gate}
          onSignup={() => { setGate(null); openAuth("signup"); }}
          onLogin={() => { setGate(null); openAuth("login"); }}
          onClose={() => setGate(null)}
        />
      )}

      {tweakOn && (
        <div className="tweaks" onClick={(e) => e.stopPropagation()}>
          <h4>Tweaks</h4>
          <div className="t-row">
            <label>Accent color</label>
            <div className="t-opts" style={{ flexWrap: "wrap" }}>
              {Object.keys(ACCENTS).map(k => (
                <button
                  key={k}
                  className={tweaks.accent === k ? "active" : ""}
                  onClick={() => setKey("accent", k)}
                  title={k}
                  style={{ minWidth: 0, padding: "6px 8px" }}
                >
                  <span style={{ display: "inline-block", width: 10, height: 10, background: ACCENTS[k], marginRight: 6, verticalAlign: "middle" }} />
                  {k}
                </button>
              ))}
            </div>
          </div>
          <div className="t-row">
            <label>Paper grain</label>
            <div className="t-opts">
              <button className={tweaks.grain !== "off" ? "active" : ""} onClick={() => setKey("grain", "on")}>On</button>
              <button className={tweaks.grain === "off" ? "active" : ""} onClick={() => setKey("grain", "off")}>Off</button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

function AccountMenu({ user, onCollection, onSettings, onSignOut }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);
  return (
    <div className="nav-acct" ref={ref}>
      <div className="nav-avatar" onClick={() => setOpen(o => !o)}>
        <span className="av">{user.initials}</span>
        <span className="nm">{user.name.split(" ")[0]}</span>
      </div>
      {open && (
        <div className="nav-menu">
          <div className="nav-menu-head">
            <div className="nm">{user.name}</div>
            <div className="em">{user.email}</div>
          </div>
          <a onClick={() => { setOpen(false); onCollection(); }}>
            <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4"><circle cx="8" cy="8" r="6" /><circle cx="8" cy="8" r="1.6" /></svg>
            My Collection
          </a>
          <a onClick={() => { setOpen(false); onSettings(); }}>
            <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4"><circle cx="8" cy="8" r="2.2" /><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3 3l1.4 1.4M11.6 11.6L13 13M13 3l-1.4 1.4M4.4 11.6L3 13" /></svg>
            Account settings
          </a>
          <a className="sep signout" onClick={() => { setOpen(false); onSignOut(); }}>
            <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M6 2H3v12h3M10 5l3 3-3 3M13 8H6" /></svg>
            Sign out
          </a>
        </div>
      )}
    </div>
  );
}

function TopNav({ route, onHome, onList, onCollection, onLeaderboards, currentRank, user, onSignIn, onSignOut, onSettings }) {
  const [menuOpen, setMenuOpen] = React.useState(false);
  // Wrap each nav action so tapping a link also closes the mobile menu.
  const go = (fn) => () => { setMenuOpen(false); if (fn) fn(); };
  return (
    <nav className={"topnav" + (menuOpen ? " topnav--open" : "")}>
      <div className="brand" onClick={go(onHome)} style={{ cursor: "pointer" }} aria-label="The Lead-In, home">
        <span className="brand-dot" />
        <span className="brand-name"><em className="brand-art">The</em>Lead-In</span>
      </div>

      <button
        className="nav-burger"
        aria-label={menuOpen ? "Close menu" : "Open menu"}
        aria-expanded={menuOpen}
        onClick={() => setMenuOpen((v) => !v)}
      >
        <span /><span /><span />
      </button>

      <div className={"links" + (menuOpen ? " links--open" : "")} style={{ alignItems: "center" }}>
        <a className={route === "home" ? "active" : ""} onClick={go(onHome)}>Home</a>
        <a className={route === "list" ? "active" : ""} onClick={go(onList)}>The List 100→1</a>
        <a className={route === "collection" ? "active" : ""} onClick={go(onCollection)}>My Collection</a>
        <a className={route === "leaderboards" ? "active" : ""} onClick={go(onLeaderboards)}>Leaderboards</a>
        {user
          ? <AccountMenu user={user} onCollection={go(onCollection)} onSettings={go(onSettings)} onSignOut={go(onSignOut)} />
          : <button className="nav-signin" onClick={go(onSignIn)}>Sign in</button>}
      </div>
    </nav>
  );
}

// Error boundary: a crash in any subtree shows a fallback instead of
// blanking the entire site. Logs the error so the console still shows it.
class BBRErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { error: null }; }
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, info) { console.error("[BBR] caught render error:", error, info); }
  render() {
    if (this.state.error) {
      return (
        <div style={{ maxWidth: 560, margin: "12vh auto", padding: "0 24px", fontFamily: "var(--sans, system-ui)", color: "var(--ink, #17130f)" }}>
          <h1 style={{ fontFamily: "var(--serif, Georgia)", fontSize: 28, marginBottom: 12 }}>Something went wrong on this page.</h1>
          <p style={{ lineHeight: 1.6, opacity: 0.8 }}>The rest of the site is fine, try reloading, or head back to the homepage. If it keeps happening, the details are in the browser console.</p>
          <button onClick={() => { location.hash = ""; location.reload(); }}
            style={{ marginTop: 16, padding: "10px 18px", border: "1px solid currentColor", background: "transparent", color: "inherit", borderRadius: 6, cursor: "pointer" }}>
            Reload the site
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

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