/* app/set-billing.jsx — Settings pages: Billing.
   ============================================================================================
   OWNERSHIP: this file and app/set-billing.css are owned by ONE agent. Nothing else in the repo needs
   to change to build these pages — the <script>/<link> tags and the `?v=` bumps are already wired,
   and the router finds a page purely by its key in the map below.

   CONTRACT
     - Read docs/SETTINGS-REFERENCE.md for what each page must contain, then the root CLAUDE.md.
     - Build every page out of `window.E8Set` primitives. Do not hand-roll a row, card or control;
       if a primitive is missing, say so rather than inventing a local one — dimensional drift
       between pages is the single most visible defect on this surface.
     - All CSS goes in app/set-billing.css under a `.e8-set-billing-*` prefix. Never edit app/set-core.css.
     - ZERO inline `style={{…}}` touching fontSize/color/display/gap/flex/margin/padding: a new
       file's inline-style budget is 0 and one such object fails `npm run lint`.
     - Colours are `--ui-*` tokens or `color-mix()` on them. Text sizes are `--ui-text-*`
       (11.5 / 12.5 / 13.5 / 15 / 18 / 24 / 28 — there is no 14px). Icons are `--ui-icon-*`.

   WHAT IS PAGE-LOCAL HERE AND WHY
   Three shapes on this page have no primitive and are deliberately local: the stat card, the plan
   card, and the total strip. The plan card is the load-bearing one — it carries its OWN segmented
   control, so it cannot be `E8Set.RadioCard` (that primitive IS a <button>, and a segmented control
   inside it would be a button inside a button). It is therefore a plain container with a real
   `role="radio"` <button> stretched across it and the segment buttons lifted above that hit area,
   which keeps both controls real, focusable and independently clickable. Everything else on the
   page — rows, card, card head, select, input, segment, pill, buttons — is a primitive.

   EVERY PRICE ON THIS PAGE IS DERIVED. The two numbers in E8_BILL_PLANS below are the only money
   written down. The "/ seat / year" headline, the savings percentage, the arithmetic line and the
   annual total are all computed from them and from the live seat count, so they cannot disagree
   with each other — a hardcoded "Save 17%" beside a rate somebody later edits is the classic
   pricing-page lie, and the spec calls static arithmetic on this page a build failure.
   ============================================================================================ */

const E8SBilling = window.E8Set;

/* ELEV8 list price, per seat per MONTH, in USD. `yearly` is the discounted per-seat-month rate when
   the year is paid up front. Two plans, matching how the product actually splits: Core is the ATS,
   Scale adds the managed-delivery half (engagements, timesheets, commissions, Watchtower). */
const E8_BILL_PLANS = [
  {
    id: 'core',
    name: 'Core',
    desc: 'The full applicant tracking system — pipelines, sequences, the shared talent pool and on-device AI.',
    monthly: 149,
    yearly: 124,
  },
  {
    id: 'scale',
    name: 'Scale',
    desc: 'Everything in Core, plus managed engagements, timesheets, the commission engine and Watchtower.',
    monthly: 249,
    yearly: 199,
  },
];
const E8_BILL_MAX_SEATS = 500;

function e8BillPlan(id) {
  return E8_BILL_PLANS.find(function (p) { return p.id === id; }) || E8_BILL_PLANS[0];
}
function e8BillMoney(n) {
  return '$' + Math.round(n).toLocaleString('en-US');
}
function e8BillSavePct(plan) {
  return Math.round((1 - plan.yearly / plan.monthly) * 100);
}
function e8BillCount(n, one, many) {
  return n + ' ' + (n === 1 ? one : many);
}

/* The seat count only means something if it is measured against a real roster, so it is: the same
   people the commission engine and the ownership model already run on. A departed rep still owns
   historical margin rows (E8DATA.reps keeps them) but does not hold a seat, hence the filter. */
function e8BillMembers() {
  const D = window.E8DATA || {};
  const rows = (D.reps || []).filter(function (r) { return r && !r.departed; }).concat(D.csms || []);
  const seen = {};
  const out = [];
  rows.forEach(function (p) {
    if (!p || !p.name || seen[p.name]) return;
    seen[p.name] = true;
    out.push({ name: p.name, role: p.role || p.title || 'Member' });
  });
  return out;
}

/* Seats default to the roster rounded up to the next five, so the page opens on a number that is
   already true for this workspace rather than on a placeholder. */
function e8BillDefaultSeats(n) {
  return Math.max(5, Math.ceil((n || 1) / 5) * 5);
}

function e8BillEmail(name) {
  const slug = String(name || 'billing').toLowerCase().replace(/[^a-z]+/g, '.').replace(/^\.|\.$/g, '');
  return (slug || 'billing') + '@stand8.io';
}

/* ---------- page-local shapes ----------------------------------------------------------------- */

/* Stat card. `tone` picks the glyph tint and the corner wash; all three live in set-billing.css so
   the card's colour is one class, not four inline values. */
function BillStat({ tone, icon, caption, value, sub }) {
  const { Icon } = E8SBilling;
  return (
    <div className={'e8-set-billing-stat is-' + tone}>
      <span className="e8-set-billing-stat-glyph"><Icon name={icon} /></span>
      <div className="e8-set-billing-stat-cap">{caption}</div>
      <div className="e8-set-billing-stat-v">{value}</div>
      <div className="e8-set-billing-stat-sub">{sub}</div>
    </div>
  );
}

/* Plan card. Two real controls, neither nested inside the other: `-hit` is a stretched
   `role="radio"` button covering the card, and `-seg` sits above it on a higher stacking level so
   the segmented control keeps its own clicks. Selection is announced by aria-checked; the filled
   circle is decoration and is aria-hidden.

   KEYBOARD. `-hit` carries the ARIA radio pattern properly: a ROVING tabindex (only the checked
   radio is in the tab order, so the group is one tab stop rather than two) plus arrow-key movement
   supplied by the page. Two tab stops for one choice is the tell that a radiogroup was faked out of
   buttons — it is operable either way, but a screen-reader user arrowing through the group is what
   the role promises. */
function BillPlanCard({ plan, period, selected, onSelect, onPeriod, onKey, hitRef }) {
  const { Icon, Seg, Pill } = E8SBilling;
  const yearly = period === 'yearly';
  const rate = yearly ? plan.yearly : plan.monthly;
  const save = e8BillSavePct(plan);
  return (
    <div className={'e8-set-billing-plan' + (selected ? ' is-on' : '')}>
      <button type="button" role="radio" aria-checked={selected} className="e8-set-billing-plan-hit"
        ref={hitRef} tabIndex={selected ? 0 : -1} onKeyDown={onKey}
        aria-label={'Select the ELEV8 ' + plan.name + ' plan'} onClick={onSelect} />
      <div className="e8-set-billing-plan-top">
        <span className="e8-set-billing-plan-name">{plan.name}</span>
        <span className="e8-set-billing-plan-mark" aria-hidden="true"><Icon name="check" /></span>
      </div>
      <p className="e8-set-billing-plan-desc">{plan.desc}</p>
      <div className="e8-set-billing-plan-seg">
        <Seg value={period} ariaLabel={plan.name + ' billing period'} onChange={onPeriod}
          options={[{ value: 'monthly', label: 'Monthly' }, { value: 'yearly', label: 'Yearly' }]} />
      </div>
      <div className="e8-set-billing-plan-price">
        <span className="e8-set-billing-plan-amt">{e8BillMoney(rate * 12)}</span>
        <span className="e8-set-billing-plan-unit">/ seat / year</span>
      </div>
      <div className="e8-set-billing-plan-sub">
        {e8BillMoney(rate) + ' / seat / month, billed ' + (yearly ? 'once a year' : 'monthly')}
      </div>
      <div className="e8-set-billing-plan-save">
        <Pill tone={yearly ? 'ok' : null}>{yearly ? 'Saving ' + save + '%' : 'Save ' + save + '% on yearly'}</Pill>
      </div>
    </div>
  );
}

/* ---------- the page --------------------------------------------------------------------------- */

function SetBillingPage() {
  const { Page, Section, Card, CardHead, Row, NavRow, Select, Input, Btn, Pill, Icon } = E8SBilling;

  const members = React.useMemo(e8BillMembers, []);
  const persona = window.e8ActivePersona ? window.e8ActivePersona() : (window.E8DATA || {}).user;
  const org = ((window.E8DATA || {}).user || {}).org || 'this workspace';

  const [planId, setPlanId] = React.useState('core');
  const [period, setPeriod] = React.useState({ core: 'yearly', scale: 'yearly' });
  const [seatsRaw, setSeatsRaw] = React.useState(function () { return String(e8BillDefaultSeats(members.length)); });
  /* Set when "Continue to payment" is pressed, and cleared by ANY change to the order. A summary
     that outlives the numbers it summarised is worse than no summary. */
  const [order, setOrder] = React.useState(null);

  const [owner, setOwner] = React.useState(function () {
    const hit = members.find(function (m) { return persona && m.name === persona.name; });
    return hit ? hit.name : (members.length ? members[0].name : '');
  });
  const [email, setEmail] = React.useState(function () { return e8BillEmail(persona ? persona.name : ''); });

  const plan = e8BillPlan(planId);
  const activePeriod = period[planId] || 'yearly';
  const yearly = activePeriod === 'yearly';
  const rate = yearly ? plan.yearly : plan.monthly;
  const seats = Math.min(E8_BILL_MAX_SEATS, parseInt(seatsRaw, 10) || 0);
  const annual = seats * rate * 12;
  const perMonth = seats * rate;
  const savedYearly = seats * (plan.monthly - plan.yearly) * 12;
  const shortfall = members.length - seats;
  /* What is actually taken off the card, and how often. The yearly plan is charged once for the
     year; the monthly plan is charged every month. Leading the summary with the annual figure on a
     MONTHLY plan labels a number nobody is ever billed as the headline — the strip below and every
     order string derive from this pair so the label can never drift from the period again. */
  const charge = yearly ? annual : perMonth;
  const cadence = yearly ? 'a year' : 'a month';

  const pickPlan = React.useCallback(function (id) { setOrder(null); setPlanId(id); }, []);
  const pickPeriod = React.useCallback(function (id, next) {
    setOrder(null);
    setPeriod(function (prev) { const out = { ...prev }; out[id] = next; return out; });
  }, []);
  const pickSeats = React.useCallback(function (v) {
    setOrder(null);
    let s = String(v).replace(/[^0-9]/g, '').replace(/^0+(?=\d)/, '').slice(0, 4);
    if (s && Number(s) > E8_BILL_MAX_SEATS) s = String(E8_BILL_MAX_SEATS);
    setSeatsRaw(s);
  }, []);

  /* The other half of the radio pattern. The roving tabindex lives on the cards; movement lives
     here because only the page knows the order of the group. Arrow keys both MOVE and SELECT, which
     is what a radiogroup does (unlike a tablist or a menu, where focus and selection can part
     company). Focusing the destination directly is safe: React keeps the same DOM node across the
     re-render, so the element we focus is the one that just became checked. */
  const planHits = React.useRef({});
  const onPlanKey = React.useCallback(function (id, e) {
    const at = E8_BILL_PLANS.findIndex(function (p) { return p.id === id; });
    const last = E8_BILL_PLANS.length - 1;
    let to = -1;
    if (e.key === 'ArrowRight' || e.key === 'ArrowDown') to = at === last ? 0 : at + 1;
    else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') to = at === 0 ? last : at - 1;
    else if (e.key === 'Home') to = 0;
    else if (e.key === 'End') to = last;
    if (to < 0 || at < 0) return;
    e.preventDefault();
    const next = E8_BILL_PLANS[to];
    pickPlan(next.id);
    const el = planHits.current[next.id];
    if (el) el.focus();
  }, [pickPlan]);

  const submit = React.useCallback(function () {
    const placed = { plan: plan.name, period: activePeriod, seats: seats, charge: charge, cadence: cadence };
    setOrder(placed);
    if (window.e8ShowToast) {
      window.e8ShowToast('ELEV8 ' + plan.name + ' · ' + e8BillCount(seats, 'seat', 'seats') + ' · '
        + e8BillMoney(charge) + ' ' + cadence + ' — payment link sent to ' + email);
    }
  }, [plan, activePeriod, seats, charge, cadence, email]);

  return (
    <Page title="Billing"
      subtitle={'Plan, seats and invoices for ' + org + '.'}>

      <Section
        title={<span className="e8-set-billing-sectitle"><Icon name="credit_card" />Subscription plan</span>}
        desc="Choose a plan and a seat count. Nothing is charged until payment is confirmed, and seats can be added or removed at any point in the term.">

        <div className="e8-set-billing-stats">
          <BillStat tone="plan" icon="workspace_premium" caption="Current plan"
            value={order ? 'ELEV8 ' + order.plan : 'None'}
            sub={order ? 'Starts as soon as payment clears' : 'No subscription on this workspace yet'} />
          <BillStat tone="seats" icon="group" caption="Active seats"
            value={members.length + ' / ' + seats}
            sub={shortfall > 0
              ? e8BillCount(shortfall, 'person', 'people') + ' over the seat count'
              : (shortfall === 0 ? 'Exactly enough seats' : e8BillCount(-shortfall, 'spare seat', 'spare seats'))} />
          <BillStat tone="status" icon="receipt_long" caption="Billing status"
            value={order
              ? <Pill tone="warn">Awaiting payment</Pill>
              : <Pill>Not configured</Pill>}
            sub={order ? 'Payment link sent to ' + email : 'No payment method on file'} />
        </div>

        <Card>
          {/* NO leading glyph on this head, deliberately. The reference's icon rule is all-or-
              nothing WITHIN A CARD, and everything else in this one — the plan pair, the seat row,
              the total strip, the CTA — starts at the card's own 20px padding. An iconned head
              above icon-less rows put the title 48px right of every line beneath it, which reads as
              two cards fused together. The section heading above still carries the accent
              credit_card glyph, which is where the spec asks for one. */}
          <CardHead title="Get started"
            desc="Two plans, both billed per seat. Yearly is paid up front." />

          <div className="e8-set-billing-plans" role="radiogroup" aria-label="Subscription plan">
            {E8_BILL_PLANS.map(function (p) {
              return (
                <BillPlanCard key={p.id} plan={p} period={period[p.id] || 'yearly'}
                  selected={p.id === planId}
                  hitRef={function (el) { planHits.current[p.id] = el; }}
                  onKey={function (e) { onPlanKey(p.id, e); }}
                  onSelect={function () { pickPlan(p.id); }}
                  onPeriod={function (next) { pickPeriod(p.id, next); }} />
              );
            })}
          </div>

          <Row
            label={<>Number of seats <span className="e8-set-billing-req" title="Required">*</span></>}
            desc={'Everyone who works in ' + org + ' needs a seat. '
              + e8BillCount(members.length, 'person is', 'people are') + ' on the roster today.'}
            control={<Input type="number" size="sm" value={seatsRaw} onChange={pickSeats}
              placeholder="0" ariaLabel="Number of seats" />}>
            {/* One note at a time. An empty field is already "everybody is short a seat", so showing
                both reads as two faults when there is one. */}
            {!seats ? (
              <div className="e8-set-billing-note">
                <Icon name="warning" />
                Enter at least one seat to continue.
              </div>
            ) : shortfall > 0 ? (
              <div className="e8-set-billing-note">
                <Icon name="warning" />
                {e8BillCount(shortfall, 'person', 'people') + ' on the roster would have no seat.'}
              </div>
            ) : null}
          </Row>

          {/* The strip states the charge the selected PERIOD actually produces — yearly leads with
              the up-front year, monthly with the monthly debit and carries the 12-month run rate
              underneath. A fixed "Annual total" caption over a monthly plan names a figure that is
              never taken, and the arithmetic being right does not rescue a wrong label. */}
          <div className="e8-set-billing-total">
            <div className="e8-set-billing-total-l">
              <div className="e8-set-billing-total-cap">{yearly ? 'Total per year' : 'Total per month'}</div>
              <div className="e8-set-billing-total-math">
                {e8BillCount(seats, 'seat', 'seats') + ' × ' + e8BillMoney(rate) + ' / seat / month'
                  + (yearly ? ' × 12 months' : '')}
              </div>
            </div>
            <div className="e8-set-billing-total-r">
              <div className="e8-set-billing-total-v">{e8BillMoney(charge)}</div>
              <div className="e8-set-billing-total-sub">
                {yearly
                  ? 'Paid up front · ' + e8BillMoney(savedYearly) + ' less than 12 monthly payments'
                  : e8BillMoney(annual) + ' over 12 months'}
              </div>
            </div>
          </div>

          <div className="e8-set-billing-cta">
            <div className="e8-set-billing-cta-txt">
              {order
                ? 'Order held for ' + order.plan + ' · ' + e8BillCount(order.seats, 'seat', 'seats')
                  + ' · ' + e8BillMoney(order.charge) + ' ' + order.cadence + '.'
                : 'No card is charged from this screen — the next step confirms payment details.'}
            </div>
            <div className="e8-set-billing-cta-act">
              {order ? <Btn onClick={function () { setOrder(null); }}>Change the order</Btn> : null}
              <Btn kind="primary" icon={order ? 'check' : 'arrow_forward'} disabled={!seats || !!order}
                onClick={submit}>
                {order ? 'Payment link sent' : 'Continue to payment'}
              </Btn>
            </div>
          </div>
        </Card>
      </Section>

      <Section
        title={<span className="e8-set-billing-sectitle"><Icon name="contact_mail" />Billing contact</span>}
        desc="Where renewal notices, receipts and failed-payment warnings are sent.">
        <Card>
          <Row icon="account_circle" label="Billing owner"
            desc="Named on the invoice, and the person chased when a payment fails."
            control={<Select value={owner} ariaLabel="Billing owner" onChange={setOwner}
              options={members.map(function (m) { return { value: m.name, label: m.name + ' · ' + m.role }; })} />} />
          <Row icon="alternate_email" label="Invoice email"
            desc="Every invoice and receipt is copied here as a PDF."
            control={<Input size="lg" type="email" value={email} onChange={setEmail}
              ariaLabel="Invoice email" placeholder="billing@example.com" />} />
          <NavRow icon="description" label="Invoice history"
            desc="Past invoices, receipts and credit notes."
            meta={e8BillCount(0, 'invoice', 'invoices')}
            onClick={function () {
              if (window.e8ShowToast) window.e8ShowToast('No invoices yet — the first one appears here after payment clears.');
            }} />
        </Card>
      </Section>
    </Page>
  );
}

window.E8SetPages = window.E8SetPages || {};
Object.assign(window.E8SetPages, { billing: SetBillingPage });
