// ===========================================================
// TodayCard — a reason to open the site today, and the streak that rewards it.
//
// Album of the Day has run as an email since July with ZERO in-app surface: the
// only true daily cadence in the product was invisible to anyone who came to the
// site. And every ladder here is keyed to collection size (tiers, nurture cohorts,
// badges), so nothing rewarded a visit — New Spinner to Enthusiast is 40 records,
// a months-long loop, against DAU:MAU of 7%.
//
// The pick MUST match that morning's email or the two contradict each other, so it
// replicates api/collection-digest.js exactly:
//   dayIndex() = floor(UTC-midnight ms / 86400000)
//   index      = (dayIndex * 31) % 100      // gcd(31,100)=1, a full permutation
// window.BBR_ALBUMS is rank-ordered and identical to lib/canon.js (verified), so
// the same index yields the same record. Confirmed against the live email.
//
// The streak comes from store.visit (profiles.streak_days via touch_visit()).
// Guests and pre-migration users have visit == null, and then no streak is shown
// rather than a zero — a "0-day streak" is worse than none.
// ===========================================================
const { useState: useTdState, useEffect: useTdEffect } = React;

function bbrDayIndex() {
  const n = new Date();
  return Math.floor(Date.UTC(n.getUTCFullYear(), n.getUTCMonth(), n.getUTCDate()) / 86400000);
}

// Exported so the album page and any future surface can agree on the same pick.
function bbrAlbumOfDay(offset) {
  const albums = window.BBR_ALBUMS || [];
  if (!albums.length) return null;
  const i = (((bbrDayIndex() + (Number(offset) || 0)) * 31) % albums.length + albums.length) % albums.length;
  return albums[i];
}

// ── TodayAdd ────────────────────────────────────────────────────────────────
// One tap to put today's record on your shelf, shared by the homepage card and
// /today so the two cannot drift.
//
// This is the ask that belongs on this surface. A guest can add up to 25 records
// before an account is needed and they migrate on signup (store.addGuestCollectionItem),
// so "add this" costs nothing and is the try-before-signup gesture the list page
// already uses. An email field here would have been a third ask on a homepage whose
// existing popup converted 0 of 59 — and a signed-in collector is already on the
// mailing list, so it would only ever have shown to the very people for whom adding
// a record is worth more.
//
// One tap means no condition picker, so the record lands with media_condition NM and
// est_value null. That is not a hole: the repricer sweeps every ten minutes and values
// slug-bearing rows, and 0 of the 4,053 items currently in the table have a slug and a
// null value. So the copy promises a price "shortly", never a number we do not have.
function TodayAdd({ album, onRequireAuth, compact }) {
  const store = window.BBR_store;
  // No "added" state on purpose. Holding one meant the card went on saying "on your
  // shelf" after the record was removed somewhere else, because local state outlived
  // the fact it was describing. The store publishes on every write and this subscribes,
  // so ownedSlugV2() is the single source of truth for what is displayed; local state
  // only tracks the in-flight request.
  const [state, setState] = useTdState("idle");   // idle | saving | error
  const [err, setErr] = useTdState("");
  const [, force] = useTdState(0);
  useTdEffect(() => (store ? store.subscribe(() => force((n) => n + 1)) : undefined), []);

  if (!store || !album) return null;
  const owned = store.ownedSlugV2 ? store.ownedSlugV2(album.slug) : false;
  const guest = store.isGuest && store.isGuest();

  if (owned) {
    return (
      <div className={"tda tda--done" + (compact ? " tda--compact" : "")}>
        <span className="tda-tick" aria-hidden="true">✓</span>
        <span>
          On your shelf.{" "}
          {guest
            ? <span className="tda-note">Saved to this device — create an account and it comes with you.</span>
            : <span className="tda-note">We&rsquo;ll price it within a few minutes.</span>}
        </span>
      </div>
    );
  }

  async function add() {
    setErr(""); setState("saving");
    try {
      // The guest cap is the ask, not a failure: hand them to the signup gate.
      if (guest && store.guestFull && store.guestFull()) {
        setState("idle");
        if (onRequireAuth) onRequireAuth("collection");
        return;
      }
      await store.addAlbumToCollectionV2({ slug: album.slug }, window.BBR_ALBUMS || []);
      setState("idle");   // the store's own notification flips this to the owned view
    } catch (e) {
      if (e && e.guestLimit) {
        setState("idle");
        if (onRequireAuth) onRequireAuth("collection");
        return;
      }
      setState("error");
      // The store throws a distinct "session expired" for a stale login; passing it
      // through verbatim tells a member to sign in again instead of "try again".
      setErr((e && e.message) || "Couldn’t save that. Try again.");
    }
  }

  return (
    <div className={"tda" + (compact ? " tda--compact" : "")}>
      <button className="tda-btn" onClick={add} disabled={state === "saving"}>
        {state === "saving" ? "Adding…" : "Add №" + album.rank + " to my shelf"}
      </button>
      {state === "error"
        ? <span className="tda-err">{err}</span>
        : <span className="tda-note">
            {guest ? "No account needed — it moves with you when you sign up."
                   : "Straight onto your shelf, priced automatically."}
          </span>}
    </div>
  );
}

function TodayCard({ onAlbum, onRequireAuth, user, authReady }) {
  const store = window.BBR_store;
  const [, force] = useTdState(0);
  useTdEffect(() => (store ? store.subscribe(() => force((n) => n + 1)) : undefined), []);

  const album = bbrAlbumOfDay(0);
  if (!album) return null;

  const visit = store && store.visit;
  // Was `streakDays > 1`, which hid the mechanic from every signed-in visitor on
  // the day they'd most benefit from learning it exists — day one, and the day
  // after any break. touch_visit() sets streak_days = 1 on a fresh visit, so 1 is
  // a real streak, not the zero the original note was rightly avoiding: a guest
  // (visit == null) still gets nothing rather than "0 days".
  const streak = visit && visit.streakDays >= 1 ? visit.streakDays : 0;
  const best = (visit && visit.longestStreak) || 0;
  const essay = (window.ESSAYS_FULL && window.ESSAYS_FULL[album.slug]) || {};
  // The list blurb is the hook the email uses too; fall back to the dek.
  // window.BLURBS (data/blurbs.js), keyed by rank — the same hook the list page and
  // the email use. NOT BBR_BLURBS, which does not exist.
  const hook = (window.BLURBS && window.BLURBS[album.rank]) ||
    (essay.essay && essay.essay.dek) || "";

  return (
    <section className="td" aria-labelledby="td-h">
      <div className="td-inner">
        <div className="td-media">
          <img src={"/covers/" + album.slug + ".jpg"} alt={album.title + " — " + album.artist}
               width="128" height="128" loading="lazy" />
        </div>
        <div className="td-body">
          <div className="td-kicker">Today from the canon</div>
          <h2 className="td-h" id="td-h">
            <span className="td-rank">№{album.rank}</span> {album.title}
          </h2>
          <div className="td-artist">{album.artist} · {album.year}</div>
          {hook ? <p className="td-hook">{hook}</p> : null}
          <button className="td-cta" onClick={() => onAlbum && onAlbum(album.slug)}>
            Read the case for №{album.rank} →
          </button>
          <TodayAdd album={album} onRequireAuth={onRequireAuth} compact />
          <div className="td-fine">
            A different record every day, the same one as the morning email.
          </div>
        </div>

        {/* The streak, as its own object in the strip rather than four mono words
            trailing the kicker — where it was the smallest text on the card and the
            only thing in the product that rewards a visit instead of a purchase.
            Own grid column on desktop, own full-width row on mobile, so it reads at
            a glance without taking the headline's place.
            Three states, and the third is deliberately NOT a streak: a signed-out
            reader has nothing to count, so it invites rather than displays. It gets
            no numeral and a muted border instead of the accent one, so an earned
            streak still looks like the reward and this looks like an offer.
            A signed-in reader whose visit row hasn't loaded (pre-migration account,
            or touch_visit() failed) falls through to nothing, as before — better
            silence than either a fake zero or a sign-in prompt they don't need. */}
        {streak ? (
          <div className="td-streak" title="Consecutive days you have visited">
            <div className="td-streak-n">{streak}</div>
            <div className="td-streak-l">{streak === 1 ? "day" : "days"} in a row</div>
            <div className="td-streak-nudge">
              {streak === 1
                ? "Come back tomorrow to make it two."
                : streak >= 3 && streak === best
                  ? "Your longest run yet — keep it going tomorrow."
                  : "Come back tomorrow to keep it."}
            </div>
          </div>
        ) : authReady && !user ? (
          <div className="td-streak td-streak--invite">
            <div className="td-streak-l">Daily streak</div>
            <div className="td-streak-nudge">Every visit counts, with a free account.</div>
            {/* Kept short on purpose: a longer label ("Start one — it's free") pushed
                this column to 209px against the earned badge's 150px, so the offer
                took more of the card than the reward it is offering. */}
            <button className="td-streak-cta" onClick={() => onRequireAuth && onRequireAuth("streak")}>
              Start a streak
            </button>
          </div>
        ) : null}
      </div>
    </section>
  );
}

Object.assign(window, { TodayCard, TodayAdd, bbrAlbumOfDay, bbrDayIndex });
