// VELOUR marketing — Pricing page. Monthly/Annual toggle; Premium highlighted.
const { Badge } = window.VelourDesignSystem_380ed4;

// Free and Premium list the same rows in the same order, so the difference between
// them is read down a column rather than pieced together from two different lists.
// They mirror PREMIUM_FEATURES in UpgradePrompt.jsx, which is what a member meets
// at each gate. api/_entitlements.js decides what is actually allowed — if a limit
// changes, it changes there first and these follow. (The pointer here used to say
// api/me.js, which stopped defining them when the sync layer landed.)
//
// THE LONG VIEW ROW IS A PRESENTATION DIFFERENCE, NOT A RESTRICTION, and the
// wording carries that. Both plans say what they SHOW; neither says a member
// cannot reach their own history, because they can — the log is in their own
// browser and "Save a backup" hands all of it over on every plan. Write "see",
// never "unlock" or "access". The insights row is the cautionary tale: it
// advertised a restriction the product never enforced.
const PLANS = [
  {
    id: "free", name: "Free", priceM: "$0", priceA: "$0", noteM: "forever", noteA: "forever",
    features: [
      { t: "Up to 3 rituals", on: true },
      { t: "Wellness and wardrobe tracking", on: true },
      { t: "The last few weeks in view", on: true },
      { t: "The full Education library", on: true },
      { t: "Stylist & budget advisor", on: false },
      { t: "Jennifer's monthly letter", on: false },
      { t: "Insights & weekly reports", on: true },
    ],
    cta: "Get started free",
  },
  {
    id: "premium", name: "Premium", priceM: "$10", priceA: "$7", noteM: "per month", noteA: "per month, billed annually", popular: true, dark: true,
    features: [
      { t: "Unlimited rituals", on: true },
      { t: "Wellness and wardrobe tracking", on: true },
      { t: "The long view — your whole history", on: true },
      { t: "The full Education library", on: true },
      { t: "Stylist & budget advisor", on: true },
      { t: "Jennifer's monthly letter", on: true },
      { t: "Insights & weekly reports", on: true },
    ],
    cta: "Choose Premium",
  },
  // FAMILY WAS REMOVED 4 Sep 2026, BECAUSE IT COULD NOT BE BOUGHT.
  // It listed a price, five features and a "Get family plan" button, and had no
  // Stripe price and no tier in api/_entitlements.js — so the button was wired to
  // nothing on purpose, and the comment below the plans array said so. A priced
  // card that cannot take money is a promise the product cannot keep, and it was
  // about to be shown to eight research participants. Bring it back when there is
  // a price and a tier behind it, not before.
];

function Segment({ annual, onChange }) {
  const opt = (label, val) => {
    const active = annual === val;
    return (
      <button onClick={() => onChange(val)} style={{ border: "none", cursor: "pointer", padding: "9px 22px", borderRadius: 999, fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 500, letterSpacing: "0.01em", color: active ? "var(--parchment)" : "var(--text-secondary)", background: active ? "var(--ink)" : "transparent", transition: "all var(--dur-base) var(--ease-out)" }}>{label}</button>
    );
  };
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
      <div style={{ display: "inline-flex", background: "var(--sand)", borderRadius: 999, padding: 4 }}>
        {opt("Monthly", false)}
        {opt("Annual", true)}
      </div>
      <Badge tone="rituals" style={{ whiteSpace: "nowrap" }}>Save 35%</Badge>
    </div>
  );
}

function PlanCard({ p, annual, onCta, ctaLabel, disabled, error }) {
  const dark = p.dark;
  const ink = dark ? "var(--parchment)" : "var(--text-primary)";
  const muted = dark ? "var(--sand)" : "var(--text-tertiary)";
  // Gentle hover lift, preserving the premium card's already-raised offset.
  const baseT = dark ? "translateY(-12px)" : "none";
  const hoverT = dark ? "translateY(-16px)" : "translateY(-4px)";
  const baseShadow = dark ? "var(--shadow-xl)" : "var(--shadow-sm)";
  const hoverShadow = dark ? "var(--shadow-xl)" : "var(--shadow-md)";
  return (
    <div
      onMouseEnter={(e) => { e.currentTarget.style.transform = hoverT; e.currentTarget.style.boxShadow = hoverShadow; }}
      onMouseLeave={(e) => { e.currentTarget.style.transform = baseT; e.currentTarget.style.boxShadow = baseShadow; }}
      style={{ position: "relative", background: dark ? "var(--ink)" : "var(--surface-card)", border: dark ? "none" : "var(--border-width) solid var(--border)", borderRadius: 20, padding: "34px 32px 32px", boxShadow: baseShadow, transform: baseT, transition: "transform .28s cubic-bezier(.16,1,.3,1), box-shadow .28s cubic-bezier(.16,1,.3,1)", display: "flex", flexDirection: "column" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 18 }}>
        <span style={{ fontSize: 17, fontWeight: 500, color: ink, letterSpacing: "0.01em" }}>{p.name}</span>
        {p.popular && <span style={{ fontSize: 12, fontWeight: 500, color: "var(--parchment)", background: "var(--sage)", borderRadius: 999, padding: "5px 13px", whiteSpace: "nowrap" }}>Most popular</span>}
      </div>
      <div style={{ display: "flex", alignItems: "baseline", gap: 4 }}>
        <span style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 52, lineHeight: 1, letterSpacing: "-0.02em", color: ink }}>{annual ? p.priceA : p.priceM}</span>
      </div>
      <div style={{ fontSize: 14, color: muted, marginTop: 10, minHeight: 20 }}>{annual ? p.noteA : p.noteM}</div>
      <div style={{ height: 1, background: dark ? "rgba(247,244,239,0.16)" : "var(--divider)", margin: "24px 0" }}></div>
      <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 15, flex: 1 }}>
        {p.features.map((f) => (
          <li key={f.t} style={{ display: "flex", alignItems: "center", gap: 12, fontSize: 15 }}>
            <i className={`ph ph-${f.on ? "check" : "x"}`} style={{ fontSize: 17, color: f.on ? "var(--sage)" : muted, opacity: f.on ? 1 : 0.7 }}></i>
            <span style={{ color: f.on ? ink : muted, opacity: f.on ? 1 : 0.85 }}>{f.t}</span>
          </li>
        ))}
      </ul>
      <button onClick={onCta} disabled={disabled}
        style={{ marginTop: 28, width: "100%", padding: "14px 0", borderRadius: 999, cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.65 : 1, fontFamily: "var(--font-sans)", fontSize: 15, fontWeight: 500, letterSpacing: "0.01em", transition: "all var(--dur-base) var(--ease-out)", border: dark ? "none" : "var(--border-width) solid var(--border-strong)", background: dark ? "var(--parchment)" : "transparent", color: dark ? "var(--ink)" : "var(--text-primary)" }}>{ctaLabel || p.cta}</button>
      {error && (
        <p style={{ fontSize: 13, lineHeight: 1.5, color: dark ? "var(--sand)" : "var(--text-secondary)", margin: "12px 0 0", textAlign: "center" }}>{error}</p>
      )}
    </div>
  );
}

function PricingPage() {
  // DEFAULTS TO MONTHLY, deliberately. This read `true` until 2026-08-22, so the
  // page opened on annual and "Choose Premium" without touching the toggle bought
  // a whole year — a full year's charge taken from someone who believed they were
  // choosing the obvious option. It is a common pattern and it is good for cash
  // flow, which is exactly why it deserves to be a decision rather than a default.
  //
  // Annual is still one click away and still the better price. The difference is
  // that a member now arrives at it on purpose. VELOUR does not run a trial for the
  // same reason: no offer here should work by being the path of least resistance.
  const [annual, setAnnual] = React.useState(false);
  // What this member is already entitled to. Someone who has paid must not be
  // sold the same thing again — the fourth variant of the door-that-assumes-
  // nobody-is-signed-in bug, and the most embarrassing place to repeat it.
  const plan = window.useVelourPlan ? window.useVelourPlan() : { tier: "free" };
  const [busy, setBusy] = React.useState(false);
  const [portalBusy, setPortalBusy] = React.useState(false);
  const [portalError, setPortalError] = React.useState(null);
  const [error, setError] = React.useState(null);

  // CHECKOUT NEEDS AN ACCOUNT, and not merely because the endpoint demands one.
  // api/checkout.js stamps the session with metadata.velour_user_id, and that is
  // the only thing that later tells the webhook whose membership was bought. A
  // payment made without a session could never be attached to anyone.
  // Stripe hosts the portal, so cancelling, changing a card and reading invoices
  // all arrive at once and none of them is ours to get wrong. Same shape as
  // startCheckout: fetch a URL, then leave. Nothing is granted or revoked here —
  // whatever the member does over there comes back as a webhook.
  const openPortal = async () => {
    setPortalError(null);
    let token = null;
    try {
      const sess = window.velourAuth ? await window.velourAuth.ensureFresh() : null;
      token = sess && sess.access_token ? sess.access_token : null;
    } catch (e) { token = null; }
    if (!token) { if (window.velourOpenSignup) window.velourOpenSignup(); return; }

    setPortalBusy(true);
    try {
      const r = await fetch("/api/portal", {
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: "Bearer " + token },
      });
      const body = await r.json().catch(() => ({}));
      if (r.ok && body.url) {
        // busy stays true through the redirect, so the label cannot flick back and
        // invite a second click. Same reasoning as startCheckout.
        window.location.href = body.url;
        return;
      }
      setPortalBusy(false);
      setPortalError(body.error || "Couldn't open that just now. Try again shortly.");
    } catch (e) {
      setPortalBusy(false);
      setPortalError("Couldn't open that just now. Try again shortly.");
    }
  };

  const startCheckout = async () => {
    setError(null);
    let token = null;
    try {
      const sess = window.velourAuth ? await window.velourAuth.ensureFresh() : null;
      token = sess && sess.access_token ? sess.access_token : null;
    } catch (e) { token = null; }

    if (!token) {
      // Begin, rather than a 401 the member cannot act on.
      if (window.velourOpenSignup) window.velourOpenSignup();
      return;
    }

    setBusy(true);
    try {
      const r = await fetch("/api/checkout", {
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: "Bearer " + token },
        body: JSON.stringify({ plan: annual ? "annual" : "monthly" }),
      });
      const body = await r.json().catch(() => ({}));
      if (r.ok && body.url) {
        // Leaving for Stripe. `busy` deliberately stays true: the button must not
        // flick back to its old label during the redirect and invite a second click.
        window.location.href = body.url;
        return;
      }
      setBusy(false);
      // The server's own sentence where there is one — it already says the useful
      // thing ("Memberships aren't available just now.") and says it in voice.
      setError(body.error || "Couldn't start that just now. Try again shortly.");
    } catch (e) {
      setBusy(false);
      setError("Couldn't start that just now. Try again shortly.");
    }
  };

  const paid = plan.tier === "paid";
  const cardProps = (p) => {
    if (p.id === "premium") {
      // A paid member's button used to be a dead end that said "Your membership is
      // active" and did nothing. That was the only place in the product where a
      // member's own billing was mentioned, so it was also the only place they
      // would look to leave — and it refused to move. It opens the portal now.
      if (paid) return { onCta: openPortal, ctaLabel: portalBusy ? "One moment" : "Manage membership", disabled: portalBusy, error: portalError };
      return { onCta: startCheckout, ctaLabel: busy ? "One moment" : null, disabled: busy, error };
    }
    if (p.id === "free") return { onCta: () => window.velourOpenSignup && window.velourOpenSignup() };
    // Every remaining plan is wired. Kept as a safe default so a future tier
    // cannot ship with a button that silently does nothing.
    return {};
  };

  return (
    <window.Section pad={84}>
      <window.Eyebrow>Pricing</window.Eyebrow>
      <h1 style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 60, lineHeight: 1.04, letterSpacing: "-0.025em", color: "var(--text-primary)", margin: "20px 0 0", textWrap: "balance" }}>Start free.<br />Live deliberately.</h1>
      <div style={{ margin: "40px 0 48px" }}>
        <Segment annual={annual} onChange={setAnnual} />
        {/* Said at the moment of the decision, not buried in terms. Annual is one
            charge for twelve months, which is the part "$7 per month" does not say
            out loud — and a member who leaves in March should not be quietly funding
            a year they did not use. A product built on not punishing people cannot
            keep eleven months of someone's money because they changed their mind. */}
        <p style={{ fontSize: 13.5, lineHeight: 1.6, color: "var(--text-tertiary)", margin: "14px 0 0", maxWidth: 460, textAlign: "center" }}>
          Annual is charged once, for the year. Cancel partway through and we refund the months you haven't used.
        </p>
      </div>
      {/* Two plans now that Family has gone.
          auto-fit rather than a fixed count, so adding or removing a tier never
          leaves a hole in the row — but CAPPED, because two cards sharing the full
          section width stretched each to ~830px when the card was drawn for ~550,
          and the whole block read as loose and lopsided.
          alignItems:center rather than start: the dark card carries a deliberate
          translateY(-12px) as its "most popular" lift, which reads as elevation in
          a row of three and as a misalignment in a row of two. Centring absorbs it,
          so the lift still does its job without the two cards looking unaligned. */}
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 280px), 1fr))", gap: 24, alignItems: "center", maxWidth: 1128, margin: "0 auto" }}>
        {PLANS.map((p) => <PlanCard key={p.id} p={p} annual={annual} {...cardProps(p)} />)}
      </div>
    </window.Section>
  );
}

window.PricingPage = PricingPage;
