/* Mesh: Accrual Maturity Assessment quiz.
   Ten screens, no conditional branching (assessment_block_kit_v3_1.md
   Section B / ASSESSMENT_FRONTEND_COPY.md Section 1). Q4 is a direct
   4-way mapping to stage, not a computed score. */

const { useState } = React;

// Six vendor bands. The old "2,500 plus" catch-all was split into a
// bounded "2,501 to 5,000" and a new open-ended "5,000+" so Q1 renders
// as a uniform 3x2 grid instead of 5 options leaving an odd one out.
// "5,000+" (not "5,000 plus"), matching the "+" shorthand the sliders
// already use for their own open-ended max values (see SliderQuestion).
const VENDOR_BANDS = ['1 to 250', '251 to 500', '501 to 1,000', '1,001 to 2,500', '2,501 to 5,000', '5,000+'];

const PERSONAL_DOMAINS = new Set([
  'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
  'icloud.com', 'aol.com', 'protonmail.com', 'live.com',
]);
// Kept in sync with the same pattern in api/assessment.js, both by hand.
const EMAIL_RE = /^[^\s@,<>]+@[^\s@,<>]+\.[^\s@,<>]+$/;

const QUESTIONS = [
  {
    field: 'vendorBand',
    type: 'pill',
    question: 'Roughly how many vendors do you accrue for each month?',
    options: VENDOR_BANDS.map((v) => ({ value: v, label: v })),
  },
  {
    field: 'teamSize',
    type: 'slider',
    // Minimum is 0 (was 1): "nobody dedicated to accruals" is a real
    // answer, and the slider now opens at 0. Backend widened to match
    // (api/assessment.js), the PDF's singular check already falls through
    // to "people" for 0 since it only special-cases exactly 1.
    question: 'How many people touch accruals during close?',
    min: 0,
    max: 15,
    unit: 'people',
    default: 0,
  },
  {
    field: 'closeDays',
    type: 'slider',
    // Minimum is 1 (was 2), slider opens at 1. The PDF benchmark scale's
    // clamp/leftmost landmark were lowered to 1 to match, so a 1-day close
    // renders further left than a 2-day one instead of stacking on it.
    question: 'How many days does your monthly close take?',
    min: 1,
    max: 20,
    unit: 'days',
    default: 1,
  },
  {
    // Multi-select (was a single radio). Selections are stored as an array
    // of stage numbers in `stages`; the report is still built around ONE
    // governing stage, computed as the LOWEST selected number (most
    // conservative reading, approved with Erin). Governing stage is derived
    // where it's needed (ResultGate for the on-screen result, and again
    // server-side for the PDF) rather than stored, so the array stays the
    // single source of truth. Options keep their fixed order (Stage 1 top,
    // Stage 4 bottom), so "lowest selected" is just the highest-in-list
    // checked option.
    field: 'stages',
    type: 'multiselect',
    question: 'How do accrual estimates get built today?',
    helper: 'Select all that apply.',
    options: [
      { value: 1, label: "We roll-forward prior month's numbers and whatever invoices happen to be on hand, with no outreach to vendors or budget owners." },
      { value: 2, label: 'We email budget owners and vendors every close to track down open POs and unbilled work.' },
      { value: 3, label: 'Procurement, AP, and contract data feed our estimates automatically. We review the results rather than building them by hand.' },
      { value: 4, label: 'Estimates post on their own with support already attached. We only step in when something breaks tolerance.' },
    ],
  },
  {
    // Multi-select (was a single radio). Governing pain is whichever
    // selected option appears FIRST in this fixed order (audit, chasing,
    // cutoff, workload) -- "earliest in the list wins," same shape as Q4's
    // rule on a different list. Only the governing pain reaches the PDF
    // (and only for Stage 1/2 exposure copy); all selections are stored as
    // data. Governing pain is derived server-side, so the client just
    // sends the full array.
    field: 'pain',
    type: 'multiselect',
    question: "What's the most painful part of your close?",
    helper: 'Select all that apply.',
    options: [
      { value: 'audit', label: 'Audit and evidence requests' },
      { value: 'chasing', label: 'Chasing budget owners and vendors' },
      { value: 'cutoff', label: 'Cutoff misses and late invoices' },
      { value: 'workload', label: 'The manual JE and rec workload' },
    ],
  },
  {
    field: 'procurement',
    type: 'pill',
    question: 'What procurement system do you use, if any?',
    microcopy: 'Why it matters. This tells us what your accrual process can already draw from automatically.',
    options: [
      { value: 'coupa', label: 'Coupa' },
      { value: 'zip', label: 'Zip' },
      { value: 'ap_inbox', label: 'AP Inbox' },
      { value: 'erp_native', label: 'ERP Native' },
      { value: 'manual', label: 'None, we manage this manually' },
      { value: 'other', label: 'Something else' },
    ],
    // Reveals a required free-text field when "other" is picked (see
    // QuestionScreen), so we capture what they actually use instead of
    // losing that detail behind a generic label.
    otherField: 'procurementOther',
  },
  {
    field: 'automationStatus',
    type: 'radio',
    question: 'Are you automating this today, or building something in-house?',
    options: [
      { value: 'none', label: 'No automation in place' },
      { value: 'evaluating', label: 'Actively looking for a way to automate this' },
      { value: 'building', label: 'Building an in-house solution' },
      { value: 'point_solution', label: 'Already using a purchased tool for part of this' },
    ],
    // Reveals a required free-text field when the point-solution option
    // is picked, same reveal pattern as Q6's "other" (see QuestionScreen).
    otherField: 'automationTool',
    otherTrigger: 'point_solution',
    otherPlaceholder: 'What tool are you using?',
  },
  {
    field: 'spendCategories',
    type: 'multiselect',
    question: 'Which of these make up the most variable spend you accrue for?',
    microcopy: "Why it matters. Usage-based spend is the hardest category to estimate by hand, and it's where manual processes break first.",
    options: [
      { value: 'marketing', label: 'Marketing and advertising' },
      { value: 'ai_software', label: 'AI or usage-based software' },
      { value: 'cloud', label: 'Cloud infrastructure' },
      { value: 'contractors', label: 'Contractors and contingent labor' },
      { value: 'other', label: 'Other' },
    ],
    // Same reveal pattern as Q6, but the trigger is "other" being among
    // a multi-select array rather than the single picked value.
    otherField: 'spendOther',
    otherPlaceholder: "What category isn't listed?",
  },
  {
    field: 'auditObjection',
    type: 'radio',
    question: 'What are the typical objections you face during audit, if any?',
    options: [
      { value: 'cant_produce', label: "Can't produce support fast enough when the PBC list lands" },
      { value: 'challenged', label: 'Estimates get challenged or walked back by the auditor' },
      { value: 'finding', label: 'A finding or control deficiency has already been tied to accruals' },
      { value: 'none_raised', label: 'No significant objections raised so far' },
      { value: 'untested', label: "Hasn't been tested yet" },
    ],
  },
  {
    // No Oracle option, deliberate. Oracle owns NetSuite and the overlap
    // confused respondents; Oracle Fusion/EBS users select "Something else."
    field: 'erp',
    type: 'pill',
    question: "What's your ERP?",
    options: [
      { value: 'netsuite', label: 'NetSuite' },
      { value: 'sap', label: 'SAP' },
      { value: 'sage', label: 'Sage Intacct' },
      { value: 'other', label: 'Something else' },
    ],
    otherField: 'erpOther',
    otherPlaceholder: "What's your ERP?",
  },
];

// Inline "what is APQC" trigger: a small superscript glyph with a real
// tap/click target of 44px (same fix as the slider's touch target,
// applied to an inline element this time) so the visible glyph can stay
// small without the sentence around it looking broken. Shows on hover
// or focus for desktop (pure CSS), and toggles via click/tap for touch
// devices, which don't reliably fire :hover. Dismisses on an outside
// click or a second tap on the trigger itself.
function ApqcInfo() {
  const [open, setOpen] = useState(false);
  // A mouse user who clicks to close without moving the cursor away is
  // still hovering, and CSS :hover doesn't know about JS state, so the
  // popover would stay visible through the hover path even after the
  // click-toggle correctly flipped `open` to false. suppressHover forces
  // it hidden until the mouse actually leaves and re-enters, so a closing
  // click always closes it, whether or not the cursor moves afterward.
  const [suppressHover, setSuppressHover] = useState(false);
  // The popover defaults to centered on the trigger, but "APQC" can land
  // anywhere horizontally depending on where the sentence wraps, so a
  // fixed center-anchor overflows the card (or the viewport, on mobile)
  // whenever the trigger sits close to an edge. Measure and clamp on
  // mount/resize, applied via a CSS variable so it composes with the
  // stylesheet's own open/close transform instead of overriding it.
  const [shift, setShift] = useState(0);
  // The popover also defaults to opening upward, but the trigger always
  // sits on the first line of the teaser paragraph directly under the
  // stage gauge, so opening upward routinely lands the popover on top
  // of the gauge itself. Measured, not assumed: flip to opening downward
  // whenever there isn't enough clearance above the trigger to fit it.
  const [openDown, setOpenDown] = useState(false);
  const wrapRef = React.useRef(null);
  const btnRef = React.useRef(null);
  const popRef = React.useRef(null);

  React.useEffect(() => {
    function reposition() {
      if (!btnRef.current || !popRef.current) return;
      const triggerRect = btnRef.current.getBoundingClientRect();
      const popWidth = popRef.current.offsetWidth || 230;
      const popHeight = popRef.current.offsetHeight || 74;
      const margin = 12;
      const gap = 8;
      const idealLeft = triggerRect.left + triggerRect.width / 2 - popWidth / 2;
      const idealRight = idealLeft + popWidth;
      let delta = 0;
      if (idealLeft < margin) delta = margin - idealLeft;
      else if (idealRight > window.innerWidth - margin) delta = (window.innerWidth - margin) - idealRight;
      setShift(delta);

      // Opening upward needs popHeight + gap of space clear of other
      // content above the trigger. There's usually plenty of *viewport*
      // above it, just not clear of the stage gauge, which always sits
      // directly above the teaser paragraph this trigger lives in and
      // routinely eats that space -- measure against the gauge itself,
      // not the viewport edge. Falls back to the viewport edge if no
      // gauge is found, for any future context without one.
      const card = btnRef.current.closest('.assess-card');
      const gauge = card && card.querySelector('.assess-gauge-wrap');
      const obstructionBottom = gauge ? gauge.getBoundingClientRect().bottom : 0;
      const spaceAbove = triggerRect.top - obstructionBottom;
      setOpenDown(spaceAbove < popHeight + gap + margin);
    }
    reposition();
    window.addEventListener('resize', reposition);
    return () => window.removeEventListener('resize', reposition);
  }, []);

  React.useEffect(() => {
    if (!open) return;
    const onOutside = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) {
        setOpen(false);
        btnRef.current?.blur();
      }
    };
    document.addEventListener('click', onOutside);
    return () => document.removeEventListener('click', onOutside);
  }, [open]);

  return (
    <span
      className={'assess-apqc-wrap' + (open ? ' open' : '') + (suppressHover ? ' no-hover' : '') + (openDown ? ' open-down' : '')}
      ref={wrapRef}
      onMouseLeave={() => setSuppressHover(false)}
    >
      <button
        type="button"
        ref={btnRef}
        className="assess-apqc-trigger"
        aria-label="About the APQC benchmark source"
        aria-expanded={open}
        onClick={(e) => {
          e.stopPropagation();
          setOpen((v) => {
            const next = !v;
            // A clicked button keeps browser focus after the click, which
            // would keep :focus-within (and so the popover) satisfied even
            // once this closing click has already toggled the JS state to
            // false. Blur it here so the CSS hover/focus-within path and
            // the JS click-toggle path can't disagree with each other.
            if (!next) { btnRef.current?.blur(); setSuppressHover(true); }
            else setSuppressHover(false);
            return next;
          });
        }}
      >
        <sup aria-hidden="true">ⓘ</sup>
      </button>
      <span className="assess-apqc-popover" role="tooltip" ref={popRef} style={{ '--apqc-shift': `${shift}px` }}>
        American Productivity & Quality Center, an independent research and benchmarking organization.{' '}
        <a
          href="https://www.apqc.org/resource-library/resource-collection/general-accounting-and-reporting-key-benchmarks"
          target="_blank"
          rel="noopener noreferrer"
          className="assess-apqc-link"
        >
          See their benchmarks <Icon name="arrow-up-right" className="ic" />
        </a>
      </span>
    </span>
  );
}

function quartileTeaser(closeDays) {
  if (closeDays <= 5) return <React.Fragment>Your {closeDays} day close is top quartile per APQC<ApqcInfo /> benchmarks. The question is whether your evidence trail is keeping up.</React.Fragment>;
  if (closeDays <= 7) return <React.Fragment>Your {closeDays} day close sits right at the APQC<ApqcInfo /> median. Top quartile teams finish in under 5.</React.Fragment>;
  if (closeDays <= 9) return <React.Fragment>Your {closeDays} day close is behind the APQC<ApqcInfo /> median of about 6 days, approaching the bottom quartile.</React.Fragment>;
  return <React.Fragment>Your {closeDays} day close is bottom quartile per APQC<ApqcInfo /> benchmarks. The median is about 6 days.</React.Fragment>;
}

function PillGroup({ options, value, onChange }) {
  return (
    <div className="assess-pill-grid">
      {options.map((opt) => (
        <button
          type="button"
          key={opt.value}
          className={'assess-pill' + (value === opt.value ? ' selected' : '')}
          aria-pressed={value === opt.value}
          onClick={() => onChange(opt.value)}
        >
          {opt.label}
        </button>
      ))}
    </div>
  );
}

// Real, explicitly-centered shapes (an actual checkmark path, an actual
// dot) inside a flex-centered box, rather than absolutely-positioned
// CSS ::after pseudo-elements. The previous checkbox mark used the
// rotated-border-box checkmark trick, which rotates around its own
// unrotated bounding box's center; that box wasn't centered in its
// 18x18 parent to begin with, so the whole checkmark rendered visibly
// off-center. Flexbox centering a real shape removes that entire class
// of bug instead of re-tuning offsets.
function OptionList({ options, value, onChange, multi }) {
  const selected = multi ? (value || []) : value;
  return (
    <div className="assess-opt-list">
      {options.map((opt) => {
        const isSelected = multi ? selected.includes(opt.value) : selected === opt.value;
        return (
          <button
            type="button"
            key={opt.value}
            className={'assess-opt' + (isSelected ? ' selected' : '')}
            aria-pressed={isSelected}
            onClick={() => {
              if (!multi) return onChange(opt.value);
              const next = isSelected ? selected.filter((v) => v !== opt.value) : [...selected, opt.value];
              onChange(next);
            }}
          >
            <div className={'assess-opt-mark ' + (multi ? 'checkbox' : 'radio')}>
              {isSelected && (
                multi
                  ? (
                    <svg viewBox="0 0 16 16" className="assess-opt-check" aria-hidden="true">
                      <path d="M3.5 8.5L6.5 11.5L12.5 4.5" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
                    </svg>
                  )
                  : <span className="assess-opt-dot"></span>
              )}
            </div>
            <div className="assess-opt-text">{opt.label}</div>
          </button>
        );
      })}
    </div>
  );
}

function SliderQuestion({ q, value, onChange }) {
  const display = value >= q.max ? `${value}+` : String(value);
  return (
    <div className="assess-slider-block">
      <div className="assess-slider-readout">
        <span className="num">{display}</span>
        <span className="unit">{q.unit}</span>
      </div>
      <input
        type="range"
        className="assess-slider"
        min={q.min}
        max={q.max}
        value={value}
        onChange={(e) => onChange(Number(e.target.value))}
      />
      <div className="assess-slider-scale">
        <span>{q.min}</span>
        <span>{q.max}+</span>
      </div>
    </div>
  );
}

function QuestionScreen({ q, index, total, value, onChange, otherValue, onOtherChange, onBack, onNext, canBack }) {
  // otherTrigger defaults to "other" (Q6, Q8, Q10's shape); Q7 overrides
  // it to "point_solution", the value that should reveal its own field.
  // Multiselect (Q8) checks the trigger's membership in the array;
  // every other type checks the single picked value directly.
  const otherTrigger = q.otherTrigger || 'other';
  const needsOtherText = Boolean(q.otherField) && (
    q.type === 'multiselect' ? (value || []).includes(otherTrigger) : value === otherTrigger
  );
  const answered = q.type === 'multiselect'
    ? (value || []).length > 0 && (!needsOtherText || (otherValue || '').trim().length > 0)
    : value !== undefined && value !== null && (!needsOtherText || (otherValue || '').trim().length > 0);
  return (
    <div className="assess-card">
      <p className="assess-eyebrow">Question {index + 1} of {total}</p>
      <h1 className="assess-question">{q.question}</h1>
      {/* Instructional helper (e.g. "Select all that apply.") sits directly
          under the heading, above the first option. Distinct from `microcopy`
          (the "why it matters" explainer on Q6/Q8), which stays below the
          options. */}
      {q.helper && <p className="assess-helper">{q.helper}</p>}
      {q.type === 'pill' && <PillGroup options={q.options} value={value} onChange={onChange} />}
      {q.type === 'radio' && <OptionList options={q.options} value={value} onChange={onChange} />}
      {q.type === 'multiselect' && <OptionList options={q.options} value={value} onChange={onChange} multi />}
      {q.type === 'slider' && <SliderQuestion q={q} value={value} onChange={onChange} />}
      {needsOtherText && (
        <input
          type="text"
          className="assess-field assess-other-field"
          placeholder={q.otherPlaceholder || 'What system do you use?'}
          value={otherValue || ''}
          onChange={(e) => onOtherChange(e.target.value)}
          maxLength={60}
        />
      )}
      {q.microcopy && <p className="assess-microcopy">{q.microcopy}</p>}
      <div className="assess-nav-row">
        <button className="btn btn-ghost" onClick={onBack} disabled={!canBack}>Back</button>
        <button className="btn btn-primary" onClick={onNext} disabled={!answered}>
          {index === total - 1 ? 'See my result' : 'Continue'}
        </button>
      </div>
    </div>
  );
}

// Car-dashboard-style fuel gauge: a 180° dial split into four colored
// zones (one per stage), with a needle that sweeps to the respondent's
// stage on mount. Segments use their own clean, vivid colors (defined in
// assessment.css as --assess-gauge-1..4) rather than the site's muted
// semantic tokens, which read as murky at this size.
//
// Segments meet at exact shared angles with flat (butt) caps, not a
// small angular gap with rounded caps: at this stroke width a round cap
// (radius = strokeWidth / 2 = 12px) is wider than a subtle gap, so the
// two caps from adjacent segments collided into each other instead of
// leaving a gap, i.e. the colors visibly overlapped. A thin white
// divider line drawn on top at each internal boundary gives a crisp,
// deliberate separation instead, with no cap-size math to get wrong.
function StageGauge({ stage }) {
  const [animated, setAnimated] = useState(false);
  React.useEffect(() => {
    const t = setTimeout(() => setAnimated(true), 50);
    return () => clearTimeout(t);
  }, []);

  const cx = 120, cy = 120, r = 98, sw = 24, needleLen = r - 16;
  const toXY = (deg, radius = r) => {
    const rad = (deg * Math.PI) / 180;
    return [cx + radius * Math.cos(rad), cy - radius * Math.sin(rad)];
  };
  const SEGMENTS = [
    { from: 180, to: 135, color: 'var(--assess-gauge-1)' },
    { from: 135, to: 90, color: 'var(--assess-gauge-2)' },
    { from: 90, to: 45, color: 'var(--assess-gauge-3)' },
    { from: 45, to: 0, color: 'var(--assess-gauge-4)' },
  ];
  const DIVIDER_ANGLES = [135, 90, 45]; // internal boundaries only, not the two outer ends
  // Needle rest state (pre-animation) points at the empty/leftmost end;
  // rotation is a plain CSS transform so the sweep-in animates reliably
  // (SVG geometry attributes like x2/y2 don't animate consistently
  // across browsers, but `transform` always does).
  const targetMathAngle = 180 - (stage - 0.5) * 45;
  const rotation = animated ? 180 - targetMathAngle : 0;

  return (
    <div className="assess-gauge-wrap">
      <svg viewBox="0 0 240 140" className="assess-gauge" aria-hidden="true">
        {SEGMENTS.map((seg, i) => {
          const [x1, y1] = toXY(seg.from);
          const [x2, y2] = toXY(seg.to);
          return <path key={i} d={`M ${x1} ${y1} A ${r} ${r} 0 0 1 ${x2} ${y2}`} stroke={seg.color} strokeWidth={sw} fill="none" />;
        })}
        {DIVIDER_ANGLES.map((deg) => {
          const [ix, iy] = toXY(deg, r - sw / 2);
          const [ox, oy] = toXY(deg, r + sw / 2);
          return <line key={deg} x1={ix} y1={iy} x2={ox} y2={oy} className="assess-gauge-divider" />;
        })}
        {[1, 2, 3, 4].map((n) => {
          const [lx, ly] = toXY(180 - (n - 0.5) * 45, r + 22);
          return <text key={n} x={lx} y={ly} textAnchor="middle" dominantBaseline="middle" className="assess-gauge-tick">{n}</text>;
        })}
        <g className="assess-gauge-needle-wrap" style={{ transformOrigin: `${cx}px ${cy}px`, transform: `rotate(${rotation}deg)` }}>
          <line x1={cx} y1={cy} x2={cx - needleLen} y2={cy} className="assess-gauge-needle-halo" strokeLinecap="round" />
          <line x1={cx} y1={cy} x2={cx - needleLen} y2={cy} className="assess-gauge-needle" strokeLinecap="round" />
        </g>
        <circle cx={cx} cy={cy} r="10" className="assess-gauge-pivot-ring" />
        <circle cx={cx} cy={cy} r="4.5" className="assess-gauge-pivot-dot" />
      </svg>
      <div className="assess-gauge-label">Stage {stage} of 4</div>
    </div>
  );
}

function ResultGate({ answers, onBack, onSubmitted }) {
  const [company, setCompany] = useState('');
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');
  const [submitting, setSubmitting] = useState(false);

  // Governing stage for the on-screen result: the lowest selected stage
  // number (most conservative). Q4 requires at least one selection to get
  // this far, so `stages` is non-empty here; guard anyway. The server
  // recomputes this same value independently for the PDF from the same
  // array, so the two can't drift.
  const stageSelections = answers.stages || [];
  const governingStage = stageSelections.length ? Math.min(...stageSelections) : null;

  // Shared by submit()'s validation and the input's invalid-outline
  // below, so a personal-domain rejection ("Please use your work
  // email.") flags the field visually too, not just format failures.
  const emailDomain = email.toLowerCase().split('@').pop();
  const emailInvalid = !EMAIL_RE.test(email) || PERSONAL_DOMAINS.has(emailDomain);

  const submit = async () => {
    if (submitting) return; // double-submit guard
    const trimmedCompany = company.trim();
    if (!trimmedCompany) {
      setError('Please enter your company name.');
      return;
    }
    if (!EMAIL_RE.test(email)) {
      setError('Please enter a valid work email address.');
      return;
    }
    if (PERSONAL_DOMAINS.has(emailDomain)) {
      setError('Please use your work email.');
      return;
    }

    setError('');
    setSubmitting(true);
    try {
      const res = await fetch('/api/assessment', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...answers, companyName: trimmedCompany, email }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        setError(data.error || 'Could not send the assessment right now. Please try again shortly.');
        setSubmitting(false);
        return;
      }
      onSubmitted(email);
    } catch {
      setError('Could not send the assessment right now. Please try again shortly.');
      setSubmitting(false);
    }
  };

  return (
    <div className="assess-card">
      <div className="assess-result-eyebrow">
        <span className="assess-result-dot"></span>
        <span className="assess-eyebrow" style={{ margin: 0 }}>Your result</span>
      </div>
      <h1 className="assess-result-headline">You're running a Stage {governingStage} close.</h1>
      <StageGauge stage={governingStage} />
      <p className="assess-result-teaser">{quartileTeaser(answers.closeDays)}</p>
      <p className="assess-result-gate-copy">
        The full assessment is a shareable PDF covering where your company lands against APQC benchmarks and what the current process exposes. Enter your company name and work email and it's in your inbox in under a minute.
      </p>
      <div className="assess-field-row">
        <input
          className={'assess-field' + (error && !company.trim() ? ' invalid' : '')}
          type="text"
          placeholder="Company name"
          value={company}
          onChange={(e) => setCompany(e.target.value)}
        />
        <input
          className={'assess-field' + (error && emailInvalid ? ' invalid' : '')}
          type="email"
          placeholder="Work email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <button className="btn btn-primary assess-submit-btn" onClick={submit} disabled={submitting}>
          {submitting ? 'Sending…' : 'Get the assessment'}
        </button>
      </div>
      {error && <p className="assess-gate-error">{error}</p>}
      <p className="assess-fine-print">Sent once, from Mesh. Work email required.</p>
      <div className="assess-nav-row" style={{ marginTop: 18 }}>
        <button className="btn btn-ghost" onClick={onBack} disabled={submitting}>Back</button>
        <span></span>
      </div>
    </div>
  );
}

function Confirmation({ email }) {
  return (
    <div className="assess-card">
      <div className="assess-confirm-icon">
        <Icon name="check" className="ic" />
      </div>
      <h1 className="assess-confirm-title">Sent.</h1>
      <p className="assess-confirm-body">
        The assessment is on its way to <b>{email}</b>, it should be there in under a minute. If it isn't, check spam and drag it to your inbox so the next one lands where it should.
      </p>
    </div>
  );
}

// A small, faithful slice of the actual two-page PDF: the APQC benchmark
// bar and the four-stage maturity table with one stage marked "Current".
// Built from the same tokens the report uses and following the site's own
// product-fragment-miniature convention (small px + mono for data, same as
// the .pf-* / .ce-* / .txc components in kit.css). It is the page's single
// bold element; sample values are illustrative (aria-hidden), the real
// numbers come from the respondent's answers. Marker at 38% = a behind-median
// close, mirrored axis (low days right/green) exactly like the PDF.
function ReportPreview() {
  const stages = [
    [1, 'Spreadsheet & memory'],
    [2, 'Spreadsheet & outreach'],
    [3, 'Signal driven'],
    [4, 'Exception based'],
  ];
  const current = 2;
  return (
    <div className="al-report" aria-hidden="true">
      <div className="al-report-head">
        <span className="al-report-eyebrow">Accrual Maturity Assessment</span>
        <span className="al-report-tag">Preview</span>
      </div>
      <div className="al-report-stats">
        <div className="al-report-stat">
          <span className="al-report-stat-l">Close duration</span>
          <span className="al-report-stat-v">8 days</span>
        </div>
        <div className="al-report-stat">
          <span className="al-report-stat-l">APQC median</span>
          <span className="al-report-stat-v">~6 days</span>
        </div>
        <div className="al-report-stat">
          <span className="al-report-stat-l">Benchmark position</span>
          <span className="al-report-stat-v accent">Behind median</span>
        </div>
      </div>
      <div className="al-report-scale">
        <span className="al-report-markval" style={{ left: '38%' }}>8 days</span>
        <div className="al-report-track">
          <span className="al-report-band bad"></span>
          <span className="al-report-band warn"></span>
          <span className="al-report-band okay"></span>
          <span className="al-report-band good"></span>
          <span className="al-report-marker" style={{ left: '38%' }}></span>
        </div>
        <div className="al-report-scale-labels">
          <span>Bottom quartile</span>
          <span>Median</span>
          <span>Top quartile</span>
        </div>
      </div>
      <div className="al-report-stages">
        {stages.map(([n, name]) => (
          <div key={n} className={'al-report-stage' + (n === current ? ' current' : '')}>
            <span className="al-report-stage-n">
              Stage {n}
              {n === current && <span className="al-report-stage-tag">Current</span>}
            </span>
            <span className="al-report-stage-name">{name}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// Pre-quiz landing. Full-width like a real Mesh page (sits between the shared
// Nav and Footer), reusing the site's two-column editorial + product-visual
// hero, the .eyebrow, .btn, and .fli/.tick feature-row components. All copy
// in the site's direct peer voice; boldness is spent only on ReportPreview.
function AssessmentLanding({ onStart }) {
  const cta = (
    <div className="al-cta-row">
      <button type="button" className="btn btn-primary btn-lg" onClick={onStart}>
        Start the assessment
        <Icon name="arrow-right" className="ic" />
      </button>
      <span className="al-cost">10 questions, 1 minute</span>
    </div>
  );
  return (
    <div className="al-page">
      <section className="al-hero">
        <div className="al-wrap al-hero-grid">
          <div className="al-hero-copy">
            <div className="eyebrow al-eyebrow"><span className="bar"></span>Accrual maturity assessment</div>
            <h1 className="al-h1">See where your accrual close stands, and what to fix first.</h1>
            <p className="al-lead">Ten questions about how your team builds accrual estimates today. You get back a two-page report that benchmarks your close against APQC<ApqcInfo /> and names the next stage worth planning for.</p>
            {cta}
          </div>
          <div className="al-hero-visual">
            <ReportPreview />
          </div>
        </div>
      </section>

      <section className="al-band">
        <div className="al-wrap">
          <div className="al-band-head">
            <div className="eyebrow al-eyebrow"><span className="bar"></span>What you get</div>
            <h2 className="al-h2">A report built from your answers, not a template.</h2>
          </div>
          <div className="al-get-grid">
            <div className="fli">
              <span className="tick"><Icon name="check" className="ic" /></span>
              <div>
                <div className="ft">Your benchmark ranking against APQC<ApqcInfo /></div>
                <div className="fd">Whether your close duration lands in the top quartile, at the median, or in the bottom quartile of APQC's cross-industry benchmark.</div>
              </div>
            </div>
            <div className="fli">
              <span className="tick"><Icon name="check" className="ic" /></span>
              <div>
                <div className="ft">Which of four maturity stages you're in</div>
                <div className="fd">Where your process sits, from spreadsheet and memory to exception based, and the audit and close exposure that stage carries.</div>
              </div>
            </div>
            <div className="fli">
              <span className="tick"><Icon name="check" className="ic" /></span>
              <div>
                <div className="ft">The five requirements any tool has to clear</div>
                <div className="fd">The checklist for evaluating accrual automation, Mesh included, so you know what to ask before you buy.</div>
              </div>
            </div>
          </div>
          <p className="al-deliverable">
            <Icon name="file-text" className="ic" />
            <span>A <b>two-page PDF</b>, built from your real answers, made to forward to a CFO or auditor.</span>
          </p>
          {cta}
        </div>
      </section>
    </div>
  );
}

function AssessmentApp() {
  const [started, setStarted] = useState(false);
  const [step, setStep] = useState(0); // 0..9 = questions, 10 = result/gate, 11 = confirmation
  const [answers, setAnswers] = useState({
    vendorBand: null,
    teamSize: 0,
    closeDays: 1,
    stages: [],
    pain: [],
    procurement: null,
    procurementOther: '',
    automationStatus: null,
    automationTool: '',
    spendCategories: [],
    spendOther: '',
    auditObjection: null,
    erp: null,
    erpOther: '',
  });
  const [sentTo, setSentTo] = useState('');

  const total = QUESTIONS.length;
  const isQuestion = step < total;
  const q = isQuestion ? QUESTIONS[step] : null;

  const setField = (field, value) => setAnswers((a) => ({ ...a, [field]: value }));

  // Landing is the funnel's initial state; starting reveals the unchanged
  // quiz (step 0) and resets scroll to the top of the first question.
  if (!started) {
    return <AssessmentLanding onStart={() => { setStarted(true); window.scrollTo(0, 0); }} />;
  }

  return (
    <div className="assess-page">
      <div className="assess-wrap">
        {step <= total && (
          <div className="assess-progress">
            <div className="assess-progress-top">
              <span className="assess-progress-label">Accrual maturity assessment</span>
              {isQuestion && (
                <span className="assess-progress-count"><b>{step + 1}</b>&nbsp;/&nbsp;{total}</span>
              )}
            </div>
            <div className="assess-progress-track">
              <div
                className="assess-progress-fill"
                style={{ width: `${((isQuestion ? step : total) / total) * 100}%` }}
              ></div>
            </div>
          </div>
        )}

        {isQuestion && (
          <QuestionScreen
            q={q}
            index={step}
            total={total}
            value={answers[q.field]}
            onChange={(v) => setField(q.field, v)}
            otherValue={q.otherField ? answers[q.otherField] : undefined}
            onOtherChange={q.otherField ? (v) => setField(q.otherField, v) : undefined}
            onBack={() => setStep((s) => Math.max(0, s - 1))}
            onNext={() => setStep((s) => s + 1)}
            canBack={step > 0}
          />
        )}

        {step === total && (
          <ResultGate
            answers={answers}
            onBack={() => setStep((s) => s - 1)}
            onSubmitted={(email) => { setSentTo(email); setStep(total + 1); }}
          />
        )}

        {step === total + 1 && <Confirmation email={sentTo} />}
      </div>
    </div>
  );
}

Object.assign(window, { AssessmentApp });
