// ─────────────────────────────────────────────────
//  billing.jsx — plan picker + return-from-checkout
//
//  PAY-FIRST: a customer chooses a plan and pays BEFORE their cookbook exists.
//  This picker slots exactly where the waitlist form sits today (the in-app
//  no-family screen), and "name your cookbook" happens after payment.
//
//  Prices are NEVER hardcoded here — they come from /api/plans, which reads
//  billing.js on the server. One source of truth, so a price change cannot
//  leave the UI quoting a number Stripe will not charge.
// ─────────────────────────────────────────────────
const { useState: useStateB, useEffect: useEffectB } = React;

const usd = cents => {
  const s = (cents / 100).toFixed(2);
  return '$' + s.replace(/\.00$/, '');
};

function PlanCard({ plan, featured, busy, disabled, onChoose }) {
  return (
    <div className={`plan-card${featured ? ' featured' : ''}`}>
      {featured && <span className="plan-flag">most families pick this</span>}
      <h3 className="plan-name">{plan.name}</h3>
      <p className="plan-price">
        <span className="plan-amount">{usd(plan.amount)}</span>
        <span className="plan-per">/{plan.interval}</span>
      </p>
      <p className="plan-blurb">{plan.blurb}</p>
      <ul className="plan-list">
        <li>{plan.seats === 1 ? '1 cook' : `${plan.seats} cooks`}</li>
        <li>{plan.imports} AI recipe imports a month</li>
        <li>{plan.enhances} photo touch-{plan.enhances === 1 ? 'up' : 'ups'} a month</li>
        <li>Unlimited recipes, notes and meal plans</li>
      </ul>
      <button
        className={`primary-btn big${featured ? '' : ' quiet'}`}
        disabled={busy || disabled}
        onClick={() => onChoose(plan.key)}
      >
        {busy ? 'Taking you to checkout…' : `Choose ${plan.name}`}
      </button>
    </div>
  );
}

// The plan picker. Rendered on the in-app no-family screen once the doors are
// open and the signed-in user has not paid yet.
function PlanPicker() {
  const { getAuthHeaders } = useAuthState();
  const [cat, setCat]   = useStateB(null);
  const [busy, setBusy] = useStateB('');
  const [err, setErr]   = useStateB('');

  useEffectB(() => {
    fetch('/api/plans').then(r => r.json()).then(setCat)
      .catch(() => setErr("We couldn't load the plans. Please refresh."));
  }, []);

  async function choose(planKey) {
    setBusy(planKey); setErr('');
    try {
      const authHdrs = await getAuthHeaders();
      const res  = await fetch('/api/checkout/subscription', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json', ...authHdrs },
        body:    JSON.stringify({ plan: planKey }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || 'Could not start checkout');
      // Hand off to Stripe's hosted page. We never see card details.
      window.location.href = data.url;
    } catch (e) {
      setErr(e.message || 'Something went wrong');
      setBusy('');
    }
  }

  if (err && !cat) return <div className="onboard"><p className="ob-err">{err}</p></div>;
  if (!cat)        return <div className="onboard"><p className="ob-sub">Loading plans…</p></div>;

  const topupImports = cat.topups.find(t => t.kind === 'import');

  return (
    <div className="onboard plans-onboard">
      <p className="ob-eyebrow">one last thing</p>
      <h2>Pick a plan to start your cookbook.</h2>
      <p className="ob-sub">
        Family Cookbook is paid up front, once a year. No free tier, no ads, and
        nothing in here is ever sold or shared.
      </p>

      <div className="plan-grid">
        {cat.plans.map(p => (
          <PlanCard
            key={p.key}
            plan={p}
            featured={p.key === 'family'}
            busy={busy === p.key}
            disabled={!!busy}
            onChoose={choose}
          />
        ))}
      </div>

      {err && <p className="ob-err">{err}</p>}

      {/* Say the awkward parts out loud rather than burying them in Terms. */}
      <div className="plan-smallprint">
        <p>
          Cancel whenever you like — you keep the cookbook until the year you paid
          for runs out, then it becomes read-only. Your recipes are never deleted.
        </p>
        <p>
          Monthly AI imports and touch-ups don't roll over. If you run out,
          {topupImports ? ` ${usd(topupImports.amount)} buys ${topupImports.credits} more` : ' top-ups are available'} and
          those never expire.
        </p>
        <p>
          Need more than {cat.plans[1].seats} cooks? You can add seats later, up
          to {cat.maxSeats}, for {usd(cat.extraSeat)} each a year.
        </p>
      </div>
    </div>
  );
}

// Shown when somebody comes back from Stripe with no session — the payment went
// through, but their login expired mid-checkout (slow payment, a 3DS challenge,
// a phone backgrounding the browser). Bouncing them to the marketing page
// seconds after they paid is the worst possible dead end, so say what happened.
//
// The payment is VERIFIED before we claim it: Stripe hands back the checkout
// session id in the URL, and /api/checkout/session/:id confirms it was paid.
// Without that we would be asserting "your plan is active" from a query
// parameter anyone could type.
function CheckoutReturnSignedOut() {
  // 'checking' | 'paid' | 'unconfirmed'
  const [state, setState] = useStateB('checking');
  const [plan, setPlan]   = useStateB(null);

  useEffectB(() => {
    const sessionId = new URLSearchParams(window.location.search).get('session_id');
    if (!sessionId) { setState('unconfirmed'); return; }
    let cancelled = false;
    fetch(`/api/checkout/session/${encodeURIComponent(sessionId)}`)
      .then(r => r.json())
      .then(d => {
        if (cancelled) return;
        setPlan(d.plan || null);
        setState(d && d.paid ? 'paid' : 'unconfirmed');
      })
      .catch(() => { if (!cancelled) setState('unconfirmed'); });
    return () => { cancelled = true; };
  }, []);

  if (state === 'checking') {
    return (
      <div className="onboard checkout-return">
        <p className="ob-sub">Checking your payment…</p>
      </div>
    );
  }

  // Confirmed by Stripe — safe to say so plainly.
  if (state === 'paid') {
    return (
      <div className="onboard checkout-return">
        <p className="ob-eyebrow">payment received</p>
        <h2>Thank you — your {plan || ''} plan is active.</h2>
        <p className="ob-sub">
          You got signed out while you were paying, so we couldn't finish setting
          things up. Sign back in with the same email and you'll go straight to
          naming your cookbook. Nothing was lost, and you won't be charged again.
        </p>
        <button className="primary-btn big" onClick={() => { window.location.href = '/sign-in'; }}>
          Sign in to finish
        </button>
        <p className="ob-skip">
          Trouble getting back in? Email hello@familycookbook.ai and we'll sort it out.
        </p>
      </div>
    );
  }

  // Could not confirm — an old link, a mistyped URL, or Stripe was unreachable.
  // Deliberately claims NOTHING about a payment, while still not dead-ending
  // somebody who really did pay.
  return (
    <div className="onboard checkout-return">
      <h2>Sign in to pick up where you left off.</h2>
      <p className="ob-sub">
        We couldn't confirm a payment from this link. If you just paid, don't
        worry — sign in with the same email and your plan will be waiting.
      </p>
      <button className="primary-btn big" onClick={() => { window.location.href = '/sign-in'; }}>
        Sign in
      </button>
      <p className="ob-skip">
        Something not right? Email hello@familycookbook.ai and we'll sort it out.
      </p>
    </div>
  );
}


// ── Billing section (family page) ────────────────────────────────────────────
// Lives on the family page because that is already the admin surface. The OWNER
// gets the controls; members see the same usage numbers but no buttons — they
// share the family's allowance, so hiding it from them would be unhelpful, but
// billing is not theirs to change.
function UsageBar({ label, used, included, topup }) {
  const pct = included > 0 ? Math.min(100, Math.round((used / included) * 100)) : 0;
  const spent = included > 0 && used >= included;
  return (
    <div className="usage-row">
      <div className="usage-head">
        <span className="usage-label">{label}</span>
        <span className={`usage-count${spent ? ' spent' : ''}`}>
          {Math.min(used, included)} of {included} used
          {topup > 0 && <span className="usage-topup"> · {topup} bought</span>}
        </span>
      </div>
      <div className="usage-track">
        <div className={`usage-fill${spent ? ' spent' : ''}`} style={{ width: pct + '%' }} />
      </div>
    </div>
  );
}

function BillingSection({ role, getAuthHeaders }) {
  const [credits, setCredits] = useStateB(undefined);  // undefined = loading, null = failed
  const [cat, setCat]         = useStateB(null);
  const [busy, setBusy]       = useStateB('');
  const [err, setErr]         = useStateB('');
  const isOwner = role === 'owner';

  async function load() {
    try {
      const authHdrs = await getAuthHeaders();
      const r = await fetch('/api/credits', { headers: authHdrs });
      setCredits(r.ok ? await r.json() : null);
    } catch { setCredits(null); }
  }
  useEffectB(() => { load(); }, []);
  useEffectB(() => {
    fetch('/api/plans').then(r => r.json()).then(setCat).catch(() => {});
  }, []);

  async function go(url, body, key) {
    setBusy(key); setErr('');
    try {
      const authHdrs = await getAuthHeaders();
      const r = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHdrs },
        body: JSON.stringify(body || {}),
      });
      const d = await r.json();
      if (!r.ok) {
        // A brand-new owner (after a hand-over) has no Stripe customer of their
        // own — the subscription belongs to whoever's card is paying. We can't
        // and shouldn't open someone else's billing portal, so explain instead
        // of showing a bare error.
        if (d.code === 'no_customer') {
          throw new Error("This cookbook's plan is on another member's account, so there's nothing here for you to manage yet. When their plan ends you can start your own from this page.");
        }
        throw new Error(d.error || 'Something went wrong');
      }
      window.location.href = d.url;
    } catch (e) {
      setErr(e.message);
      setBusy('');
    }
  }

  if (credits === undefined) {
    return (
      <div className="fam-set-section">
        <div className="fam-set-eyebrow">Plan &amp; usage</div>
        <p className="fam-set-hint">Loading…</p>
      </div>
    );
  }
  if (credits === null) {
    return (
      <div className="fam-set-section">
        <div className="fam-set-eyebrow">Plan &amp; usage</div>
        <p className="fam-set-hint">Couldn't load your plan right now.</p>
      </div>
    );
  }

  const planName = credits.plan === 'family' ? 'Family' : credits.plan === 'single' ? 'Single' : null;
  const comped   = credits.status === 'comped';

  const fmtDate = iso => {
    if (!iso) return null;
    try {
      return new Date(iso).toLocaleDateString(undefined, { day: 'numeric', month: 'long', year: 'numeric' });
    } catch { return null; }
  };
  const daysLeft = iso => {
    if (!iso) return null;
    const d = Math.ceil((new Date(iso).getTime() - Date.now()) / 86400000);
    return d > 0 ? d : 0;
  };
  const renewsOn  = fmtDate(credits.currentPeriodEnd);
  const graceDays = daysLeft(credits.graceUntil);

  const importPack  = cat && cat.topups.find(t => t.kind === 'import');
  const enhancePack = cat && cat.topups.find(t => t.kind === 'enhance');
  const money = c => '$' + (c / 100).toFixed(2).replace(/\.00$/, '');

  return (
    <div className="fam-set-section">
      <div className="fam-set-eyebrow">Plan &amp; usage</div>

      <div className="billing-head">
        <h2 className="fam-set-title">
          {comped ? 'Complimentary' : planName ? `${planName} plan` : 'No plan'}
        </h2>
        {credits.status === 'grace' && (
          <span className="billing-flag warn">payment problem</span>
        )}
        {credits.status === 'lapsed' && (
          <span className="billing-flag warn">lapsed</span>
        )}
      </div>

      {credits.status === 'grace' && (
        <p className="billing-alert">
          We couldn't take your renewal payment. Update your card
          {graceDays !== null && graceDays > 0
            ? ` in the next ${graceDays} ${graceDays === 1 ? 'day' : 'days'}`
            : ' soon'} and nothing changes. After that the cookbook becomes
          read-only until it's sorted — your recipes are never deleted.
        </p>
      )}
      {credits.status === 'lapsed' && (
        <p className="billing-alert">
          Your subscription has lapsed, so the cookbook is read-only for now.
          Everything is still here — renew and it all comes straight back.
        </p>
      )}
      {credits.status === 'canceled' && (
        <p className="billing-alert">
          Your subscription was cancelled{renewsOn ? ` and ended ${renewsOn}` : ''}.
          The cookbook is read-only. Nothing has been deleted.
        </p>
      )}
      {credits.status === 'active' && renewsOn && (
        <p className="fam-set-hint billing-renews">Renews {renewsOn}.</p>
      )}

      {/* Monthly allowances reset; bought credits do not. Say so, because the
          difference is exactly what people get annoyed about later. */}
      <UsageBar label="AI recipe imports"
                used={credits.imports.used} included={credits.imports.included}
                topup={credits.imports.topup} />
      <UsageBar label="Photo touch-ups"
                used={credits.enhances.used} included={credits.enhances.included}
                topup={credits.enhances.topup} />
      <p className="fam-set-hint billing-note">
        {comped
          ? "This cookbook isn't metered."
          : 'Monthly allowances reset each month and don\'t roll over. Credits you buy never expire and are only used once the month\'s allowance is gone.'}
      </p>

      {err && <div className="fam-set-error">{err}</div>}

      {isOwner && !comped && (
        <div className="billing-actions">
          {/* Top-ups are hidden unless the plan is actually usable. The server
              refuses them without full access, so showing them to a lapsed
              customer would just be a button that errors. Manage billing —
              the thing they DO need — always shows. */}
          {credits.access === 'full' && importPack && (
            <button className="ghost-btn" disabled={!!busy}
              onClick={() => go('/api/checkout/topup', { pack: importPack.key }, 'imports')}>
              {busy === 'imports' ? 'Opening…' : `Buy ${importPack.credits} imports — ${money(importPack.amount)}`}
            </button>
          )}
          {credits.access === 'full' && enhancePack && (
            <button className="ghost-btn" disabled={!!busy}
              onClick={() => go('/api/checkout/topup', { pack: enhancePack.key }, 'enhances')}>
              {busy === 'enhances' ? 'Opening…' : `Buy ${enhancePack.credits} touch-ups — ${money(enhancePack.amount)}`}
            </button>
          )}
          <button className="ghost-btn" disabled={!!busy}
            onClick={() => go('/api/billing/portal', null, 'portal')}>
            {busy === 'portal' ? 'Opening…' : 'Manage billing'}
          </button>
        </div>
      )}

      {isOwner && !comped && (
        <p className="fam-set-hint billing-note">
          Manage billing opens Stripe, where you can update your card, see past
          invoices, or cancel.{credits.access === 'full'
            ? ' Cancelling keeps the cookbook until the year you paid for runs out.'
            : ''}
        </p>
      )}

      {!isOwner && (
        <p className="fam-set-hint billing-note">
          These are shared across everyone in the cookbook. Only the owner can
          buy more or change the plan.
        </p>
      )}
    </div>
  );
}


// ── Account banner ───────────────────────────────────────────────────────────
// Shown on every screen when the subscription needs attention. Not dismissible:
// this is a real problem with a real deadline, and quietly hiding it is how
// somebody discovers the cookbook is read-only by trying to save a recipe.
//
// The call to action goes to the family page rather than straight to Stripe.
// The full billing section there already knows which button is correct for the
// situation (portal vs new plan vs "it's on someone else's account"), and
// duplicating that judgement in a banner would mean two places to get it wrong.
function AccountBanner({ familyData, onOpenBilling }) {
  const fam = familyData && familyData.family;
  if (!fam) return null;

  const status = fam.subscriptionStatus;
  if (status !== 'grace' && status !== 'lapsed' && status !== 'canceled') return null;

  const isOwner = familyData.role === 'owner';
  const owner   = (familyData.members || []).find(m => m.role === 'owner');
  const ownerName = owner ? (owner.name || owner.email || 'the owner') : 'the owner';

  const days = (() => {
    if (!fam.graceUntil) return null;
    const d = Math.ceil((new Date(fam.graceUntil).getTime() - Date.now()) / 86400000);
    return d > 0 ? d : 0;
  })();

  let message;
  if (status === 'grace') {
    message = isOwner
      ? <>We couldn't take your renewal payment.{days !== null && days > 0
          ? ` You have ${days} ${days === 1 ? 'day' : 'days'} to update your card`
          : ' Update your card'} before the cookbook becomes read-only.</>
      : <>There's a payment problem with this cookbook. {ownerName} needs to update the card before it becomes read-only.</>;
  } else {
    message = isOwner
      ? <>This cookbook is read-only until you renew. Everything is still here — nothing has been deleted.</>
      : <>This cookbook is read-only until {ownerName} renews. Everything is still here — nothing has been deleted.</>;
  }

  return (
    <div className={`account-banner${status === 'grace' ? '' : ' locked'}`} role="status">
      <span className="ab-text">{message}</span>
      {isOwner && (
        <button className="ab-action" onClick={onOpenBilling}>
          {status === 'grace' ? 'Update card' : 'Renew'}
        </button>
      )}
    </div>
  );
}

Object.assign(window, { PlanPicker, CheckoutReturnSignedOut, BillingSection, UsageBar, AccountBanner });
