/* Poise onboarding - Act 3 (Proof): S9-S13 (coaching moved to S16) */

const { useState, useEffect, useRef } = React;
const { Button: PButton, Badge: PBadge, Card: PCard, ProgressRing: PRing } = window.PoiseDesignSystem_eee0d2;

const SUPPORTED_MODELS = ['AirPods Pro (all generations)', 'AirPods 3', 'AirPods 4', 'AirPods Max', 'Beats Fit Pro'];

/* X-in-circle row mark (matches the reference's xmark.circle privacy rows) */
const XCircle = ({ size = 16 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
    <circle cx="12" cy="12" r="9"></circle>
    <line x1="9" y1="9" x2="15" y2="15"></line>
    <line x1="15" y1="9" x2="9" y2="15"></line>
  </svg>
);

/* Privacy row: x-circle mark + label, centred to match the reference card */
function PrivacyRow({ children, delay }) {
  return (
    <div className="rise" style={{ display: 'flex', alignItems: 'center', gap: 12, animationDelay: `${delay}ms` }}>
      <span style={{ color: 'var(--text-secondary)', flexShrink: 0, display: 'flex' }}><XCircle size={15} /></span>
      <span className="poise-body" style={{ fontSize: 15 }}>{children}</span>
    </div>
  );
}

/* ============ S9 · Motion permission ============ */
/* Reworked to mirror the shipped MotionPermissionScreen reference: centred
   airpods icon, "Connect Poise to Motion." headline, a privacy card, an
   on-device footer. Logic (dialog → allow/deny → track → flow) is unchanged. */
function MotionScreen() {
  const flow = useFlow();
  const [dialog, setDialog] = useState(false);
  const [denied, setDenied] = useState(flow.motionDenied);

  const allow = () => {
    setDialog(false);
    flow.setMotionDenied(false);
    track('permission_motion', { granted: true });
    flow.next();
  };
  const deny = () => {
    setDialog(false);
    setDenied(true);
    flow.setMotionDenied(true);
    track('permission_motion', { granted: false });
  };

  return (
    <Screen
      id="motion" label={denied ? 'S9 · Motion permission (denied)' : 'S9 · Motion permission'}
      topBar={<TopBar onBack={() => flow.back()} />}
      cta={
        <React.Fragment>
          <PButton variant="primary" size="lg" fullWidth onClick={() => denied ? (flow.setMotionDenied(false), flow.next()) : setDialog(true)}>
            {denied ? 'Open Settings' : 'Allow motion access'}
          </PButton>
          <span className="poise-footnote rise" style={{ marginTop: 8, animationDelay: '0ms' }}>100% on-device. Always.</span>
        </React.Fragment>
      }
    >
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
      <div style={{ position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 18, width: '100%' }}>
        <svg className="rise" xmlns="http://www.w3.org/2000/svg" viewBox="85 166 915 590" style={{ width: 80, height: 'auto', animationDelay: '0ms' }} aria-label="AirPods Pro">
          <g transform="translate(0,922) scale(1,-1)">
            <path fill="#566B5F" fillRule="evenodd" d="M206 394Q191 394 176.5 397.5Q162 401 148 408Q135 416 123.5 426.5Q112 437 103 450Q94 464 89.5 479.5Q85 495 85 513Q85 526 88.0 537.5Q91 549 96 559Q101 570 109.0 579.0Q117 588 128 595Q138 602 151.0 605.5Q164 609 179 609Q197 609 213.0 604.5Q229 600 243 591Q257 583 268.0 571.5Q279 560 287 546Q296 533 300.0 519.0Q304 505 304 490Q304 474 300.5 460.5Q297 447 290 436Q284 426 275.0 418.0Q266 410 254 404Q243 399 231.0 396.5Q219 394 206 394ZM190 444Q193 447 194.0 451.5Q195 456 193 462Q191 468 186.5 476.0Q182 484 175 493Q168 502 161.0 508.0Q154 514 148 516Q143 519 138.0 519.0Q133 519 130 516Q126 513 125.5 508.5Q125 504 126 498Q128 492 132.5 484.5Q137 477 145 468Q152 459 158.5 453.0Q165 447 171 444Q177 441 181.5 441.0Q186 441 190 444ZM879 394Q866 394 854.0 396.5Q842 399 831 404Q819 410 810.0 418.0Q801 426 795 436Q788 447 784.5 460.5Q781 474 781 490Q781 505 785.0 519.0Q789 533 798 546Q806 560 817.0 571.5Q828 583 842 591Q856 600 872.0 604.5Q888 609 906 609Q921 609 934.0 605.5Q947 602 957 595Q968 588 976.0 579.0Q984 570 989 559Q995 549 997.5 537.5Q1000 526 1000 513Q1000 495 995.5 479.5Q991 464 982 450Q973 437 961.5 426.5Q950 416 936 408Q922 401 908.0 397.5Q894 394 879 394ZM895 444Q899 441 903.5 441.0Q908 441 914 444Q920 447 926.5 453.0Q933 459 940 468Q948 477 952.5 484.5Q957 492 959 498Q960 504 959.5 508.5Q959 513 955 516Q952 519 947.0 519.0Q942 519 936 516Q931 514 924.0 508.0Q917 502 910 493Q903 484 898.5 476.0Q894 468 892 462Q890 456 891.0 451.5Q892 447 895 444Z"></path>
            <path fill="#BDEECD" fillRule="evenodd" d="M386 756Q405 756 421.5 752.0Q438 748 453 740Q469 732 481.0 720.5Q493 709 502 694Q511 679 515.5 661.0Q520 643 520 622Q520 594 512.5 570.5Q505 547 491 527Q477 508 459.5 492.5Q442 477 421 465Q412 460 402.0 455.5Q392 451 380 447Q369 443 355.5 440.0Q342 437 325 435Q331 448 333.5 461.5Q336 475 336 490Q336 508 331.5 525.0Q327 542 318 557Q310 573 298.0 586.5Q286 600 271 611Q256 622 239.5 629.0Q223 636 205 639Q207 644 209.5 648.0Q212 652 214 656Q216 659 218.5 662.5Q221 666 223 669Q234 684 246.0 696.0Q258 708 271 718Q285 728 299.0 735.0Q313 742 328 747Q343 751 357.5 753.5Q372 756 386 756ZM399 613 448 654Q451 656 452.5 659.0Q454 662 454 665Q454 668 453.0 671.0Q452 674 450 676Q448 679 445.0 680.5Q442 682 439 682Q436 683 433.0 682.0Q430 681 428 679L379 637Q376 635 374.5 632.5Q373 630 373 626Q372 623 373.0 620.0Q374 617 377 615Q379 612 381.5 610.5Q384 609 387 609Q391 609 393.5 610.0Q396 611 399 613ZM338 166Q331 166 324.5 168.0Q318 170 313 174Q308 178 306.0 184.0Q304 190 304 197V400Q317 400 329.5 401.5Q342 403 354 405Q366 408 377.0 411.0Q388 414 398 417V197Q398 190 396.0 184.0Q394 178 389 174Q384 170 378.0 168.0Q372 166 364 166H338ZM699 756Q713 756 727.5 753.5Q742 751 757 747Q772 742 786.0 735.0Q800 728 814 718Q827 708 839.0 696.0Q851 684 862 669Q864 666 866.5 662.5Q869 659 871 656Q873 652 875.5 648.0Q878 644 880 639Q862 636 845.5 629.0Q829 622 814 611Q799 600 787.5 586.5Q776 573 767 557Q758 542 753.5 525.0Q749 508 749 490Q749 475 752.0 461.5Q755 448 760 435Q744 437 730.0 440.0Q716 443 705 447Q694 451 683.5 455.5Q673 460 664 465Q643 477 625.5 492.5Q608 508 594 527Q579 547 572.0 570.5Q565 594 565 622Q565 643 569.5 661.0Q574 679 583 694Q592 709 604.0 720.5Q616 732 632 740Q647 748 663.5 752.0Q680 756 699 756ZM686 613Q688 611 691.0 610.0Q694 609 698 609Q701 609 703.5 610.5Q706 612 709 615Q711 617 711.5 620.0Q712 623 712 626Q712 630 710.5 632.5Q709 635 706 637L657 679Q655 681 652.0 682.0Q649 683 646 682Q643 682 640.0 680.5Q637 679 635 676Q633 674 632.0 671.0Q631 668 631 665Q631 662 632.5 659.0Q634 656 637 654L686 613ZM688 166H662Q655 166 648.0 168.0Q641 170 636 174Q631 178 629.0 184.0Q627 190 627 197V417Q637 414 648.0 411.0Q659 408 671 405Q683 403 695.5 401.5Q708 400 721 400V197Q721 190 719.0 184.0Q717 178 712 174Q707 170 700.5 168.0Q694 166 688 166Z"></path>
          </g>
        </svg>
        <H style={{ fontSize: 29 }}><span className="rise" style={{ display: 'inline-block', animationDelay: '0ms' }}>Connect Poise to Motion.</span></H>
        <P style={{ maxWidth: 300 }}><span className="rise" style={{ display: 'inline-block', animationDelay: '0ms' }}>Poise uses your AirPods’ motion sensors to detect when you slouch.</span></P>
        <div style={{ alignSelf: 'stretch', display: 'flex', flexDirection: 'column', gap: 12, marginTop: 12, padding: '20px', borderRadius: 'var(--radius-card)', background: 'var(--surface-card)', border: '1px solid var(--border-hairline)', textAlign: 'left' }}>
          <PrivacyRow delay={0}>We don’t collect your motion data</PrivacyRow>
          <PrivacyRow delay={0}>We don’t sell your data</PrivacyRow>
          <PrivacyRow delay={0}>We don’t track your location</PrivacyRow>
        </div>
        {denied && (
          <P className="rise" style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: '18px auto 0', fontSize: 14, color: 'var(--status-warn)', maxWidth: 320 }}>Poise needs motion data from your AirPods to detect your posture. The app cannot work without it. Please enable it in Settings.</P>
        )}
      </div>
      </div>
      {dialog && (
        <IOSAlert
          title={'“Poise” Would Like to Access Your Motion & Fitness Activity'}
          message="Poise reads head motion from your AirPods to notice when your posture slips. Data stays on this iPhone."
          onDeny={deny} onAllow={allow} allow="OK"
        />
      )}
    </Screen>
  );
}

/* ============ S10 · Notification permission ============ */
/* Reworked to mirror the shipped NotificationPermissionScreen reference:
   centred bell icon, "Stay on track." headline, spam-reassurance line, and a
   two-tap "Skip for now → Skip anyway" urge. Logic (dialog/track/flow) intact. */
function NotificationScreen() {
  const flow = useFlow();
  const [dialog, setDialog] = useState(false);
  const [denied, setDenied] = useState(false);
  const [skipUrge, setSkipUrge] = useState(false);

  const close = (granted) => {
    setDialog(false);
    if (granted) {
      track('permission_notifications', { granted: true });
      flow.next();
    } else {
      setDenied(true);
      track('permission_notifications', { granted: false });
    }
  };

  const skip = () => {
    if (!denied && !skipUrge) {
      setSkipUrge(true);
      return;
    }
    track('permission_notifications', { granted: false, skipped: true });
    flow.next();
  };

  return (
    <Screen
      id="notifications" label={denied ? 'S10 · Notification permission (denied)' : 'S10 · Notification permission'}
      topBar={<TopBar onBack={() => flow.back()} />}
      cta={
        <React.Fragment>
          <PButton variant="primary" size="lg" fullWidth onClick={() => denied ? flow.next() : setDialog(true)}>
            {denied ? 'Open Settings' : 'Allow notifications'}
          </PButton>
          <button className="ghost-link" style={{ color: 'var(--mint)', fontWeight: 600 }} onClick={skip}>
            {(skipUrge && !denied) ? 'Skip anyway' : 'Skip for now'}
          </button>
        </React.Fragment>
      }
    >
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
      <div style={{ position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 18, width: '100%' }}>
        <span className="rise" style={{ color: 'var(--mint)' }}><window.IconBell size={44} sw={1.7} /></span>
        <H><span className="rise" style={{ display: 'inline-block', animationDelay: '0ms' }}>Stay on track.</span></H>
        <P style={{ maxWidth: 320 }}><span className="rise" style={{ display: 'inline-block', animationDelay: '0ms' }}>Poise sends streak reminders and session updates to keep you consistent.</span></P>
        <P style={{ fontSize: 14, maxWidth: 300 }}><span className="rise" style={{ display: 'inline-block', animationDelay: '0ms' }}>We’ll never spam you. You control what notifications you receive.</span></P>
        {denied ? (
          <P className="rise" style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: '18px auto 0', fontSize: 14, color: 'var(--status-warn)', maxWidth: 320 }}>Notifications were denied. Poise still works without them, but you’ll miss streak reminders and session updates.</P>
        ) : skipUrge ? (
          <P className="rise" style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: '18px auto 0', fontSize: 14, color: 'var(--status-warn)', maxWidth: 320 }}>Are you sure? Notifications are how Poise keeps you consistent: streak reminders and session nudges. You can always turn them off later.</P>
        ) : null}
      </div>
      </div>
      {dialog && (
        <IOSAlert
          title={'“Poise” Would Like to Send You Notifications'}
          message="Notifications may include alerts, sounds, and icon badges. These can be configured in Settings."
          onDeny={() => close(false)} onAllow={() => close(true)}
        />
      )}
    </Screen>
  );
}

/* ============ S14 / S14a · AirPods compatibility check ============ */
/* S14 (AirpodsScreen): static searching state — stays until real AirPods connect.
   S14a (AirpodsResultScreen): result determined by the airpodsResult tweak. */

const airpodsModelList = (delayBase, rise = true) => (
  <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
    {SUPPORTED_MODELS.map((m, i) => (
      <div key={m} className={rise ? 'rise' : ''} style={{ display: 'flex', alignItems: 'center', gap: 10, animationDelay: `${delayBase}ms` }}>
        <span style={{ color: 'var(--mint)' }}><window.IconCheck size={14} sw={2.6} /></span>
        <span className="poise-body" style={{ fontSize: 15 }}>{m}</span>
      </div>
    ))}
  </div>
);

/* forceIncompatible: rendered as the frozen backdrop behind the incompatible
   modal — shows the amber state directly with no flash. */
function AirpodsScreen({ forceIncompatible }) {
  const flow = useFlow();
  const result = flow.t.airpodsResult;
  const connected = forceIncompatible || flow.airpodsConnected;
  const incompatible = forceIncompatible || (connected && result === 'incompatible');
  const compatible = !forceIncompatible && connected && result === 'compatible';
  const resolved = incompatible || compatible;
  const [instant] = useState(flow.s11Instant);

  useEffect(() => {
    if (forceIncompatible) return;
    if (flow.s11Instant) flow.setS11Instant(false);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  // Mobile has no simulate-AirPods panel (that's a desktop review-canvas
  // affordance - real hardware can't be paired from a browser tab either
  // way), so without this a mobile visitor would sit on "Searching…"
  // forever. Auto-advance to S14a after a beat, as if a pair had just
  // connected.
  useEffect(() => {
    if (forceIncompatible || !flow.isMobile || connected) return;
    const id = setTimeout(() => flow.go('airpods-result'), 2000);
    return () => clearTimeout(id);
  }, [forceIncompatible, flow.isMobile, connected]);

  const rc = instant ? '' : 'rise';

  const headline = compatible ? 'These AirPods work with Poise.' : 'Are your AirPods compatible?';
  const body = compatible
    ? 'Their motion sensors are ready to track your posture.'
    : 'Connect your AirPods and put them in your ears so Poise can check if they are able to detect motion.';

  return (
    <Screen
      id="airpods" label={incompatible ? 'S14 · AirPods check (incompatible)' : compatible ? 'S14 · AirPods check (compatible)' : 'S14 · AirPods compatibility check'}
      noEnter={instant}
      topBar={<TopBar onBack={() => flow.back()} />}
      cta={compatible
        ? <PButton variant="primary" size="lg" fullWidth onClick={() => flow.go('calibration')}>Continue</PButton>
        : <div style={{ width: '100%', opacity: 0.4, pointerEvents: 'none' }}><PButton variant="primary" size="lg" fullWidth>Continue</PButton></div>
      }
    >
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 18, textAlign: 'center' }}>
        <span className={rc} style={{ animationDelay: '0ms' }}>
          <img className={resolved ? '' : 'airpods-scan'} src={window.__resources ? window.__resources.imgAirpodsPro : "/onboarding-demo/app/airpods-pro.svg"} alt="" style={{ width: 84, height: 'auto', display: 'block', opacity: incompatible ? 0.55 : 1 }} />
        </span>
        <H style={{ fontSize: 27 }}><span className={rc} style={{ display: 'inline-block', animationDelay: '0ms' }}>{headline}</span></H>
        <P style={{ maxWidth: 296 }}><span className={rc} style={{ display: 'inline-block', animationDelay: '0ms' }}>{body}</span></P>
        <span className={rc} style={{ animationDelay: '0ms' }}>
          {incompatible ? (
            <span className="status-warn">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M10.3 3.3 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.3a2 2 0 0 0-3.4 0z"></path><line x1="12" y1="9" x2="12" y2="13.5"></line><circle cx="12" cy="17" r="0.6" fill="currentColor"></circle></svg>
              These AirPods aren't compatible
            </span>
          ) : compatible ? (
            <span className="status-ok">
              <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5"></path></svg>
              AirPods connected and compatible
            </span>
          ) : (
            <span className="search-status">
              <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7"></circle><path d="M21 21l-4.3-4.3"></path></svg>
              Searching for AirPods…
            </span>
          )}
        </span>
        <div className={rc} style={{ alignSelf: 'stretch', textAlign: 'left', padding: 16, borderRadius: 'var(--radius-card)', background: 'var(--surface-card)', border: '1px solid var(--border-hairline)', animationDelay: '0ms' }}>
          <span className="poise-caption" style={{ display: 'block', marginBottom: 12 }}>Works with</span>
          {airpodsModelList(0, !instant)}
        </div>
      </div>
    </Screen>
  );
}

/* ============ S14a · AirPods result ============ */
function AirpodsResultScreen() {
  const flow = useFlow();
  const r = flow.t.airpodsResult;
  const phase = r === 'incompatible' ? 'incompatible' : r === 'never connected' ? 'none' : 'ok';

  /* Reaching the result means a pair connected */
  useEffect(() => { flow.setAirpodsConnected(true); }, []); // eslint-disable-line react-hooks/exhaustive-deps

  if (phase === 'ok') {
    return (
      <Screen
        id="airpods-result" label="S14a · AirPods check (compatible)"
        topBar={<TopBar />}
        cta={<PButton variant="primary" size="lg" fullWidth onClick={() => flow.next()}>Continue</PButton>}
      >
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 18, textAlign: 'center' }}>
          <div style={{ position: 'relative', width: 96, height: 96, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            {flow.t.motion && <span style={{ position: 'absolute', inset: 0, borderRadius: '50%', border: '2px solid var(--mint)', animation: 'pulseRingStay 2.2s var(--ease-out) 1 forwards' }}></span>}
            <span style={{ width: 84, height: 84, borderRadius: '50%', background: 'var(--accent-muted)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--mint)', boxShadow: 'var(--glow-mint)' }}>
              <window.IconCheck size={36} sw={2.2} />
            </span>
          </div>
          <PBadge tone="good" dot>Motion sensors live</PBadge>
          <H style={{ fontSize: 28 }}>These AirPods are compatible.</H>
          <P style={{ fontSize: 16 }}>Connected and reading motion. Keep them in your ears to see how Poise works.</P>
        </div>
      </Screen>
    );
  }

  if (phase === 'incompatible') {
    /* Pure modal overlay — AirpodsScreen is literally rendered behind this in main.jsx */
    return (
      <div data-screen-label="S14a · AirPods check (incompatible)" style={{ position: 'absolute', inset: 0 }}>
        <div style={{ position: 'absolute', inset: 0, background: 'rgba(2,0,3,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px 20px', animation: 'onbFade 220ms ease-out both' }}>
          <div className="incompat-card" style={{ position: 'relative', width: '100%', background: 'var(--surface-elevated)', border: '1px solid var(--border-hairline)', borderRadius: 20, padding: '24px 20px 20px', boxShadow: '0 16px 48px rgba(0,0,0,.7)', display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center', gap: 14 }}>
            <button onClick={() => { flow.setS11Instant(true); flow.go('airpods'); }} aria-label="Close" style={{ position: 'absolute', top: 14, left: 14, background: 'none', border: 'none', color: 'var(--text-secondary)', cursor: 'pointer', padding: 4, display: 'flex', alignItems: 'center' }}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
            </button>
            <div style={{ width: 56, height: 56, borderRadius: '50%', background: 'rgba(255,183,77,0.14)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--status-warn)', marginTop: 6 }}>
              <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M10.3 3.3 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.3a2 2 0 0 0-3.4 0z"></path><line x1="12" y1="9" x2="12" y2="13.5"></line><circle cx="12" cy="17" r="0.6" fill="currentColor"></circle></svg>
            </div>
            <H style={{ fontSize: 22, lineHeight: 1.2 }}>Unfortunately, these AirPods don't have motion sensors.</H>
            <P style={{ fontSize: 15, lineHeight: 1.55 }}>Earlier generations of AirPods shipped without the motion hardware Poise needs. Poise can't work without them.</P>
            <PButton variant="primary" size="lg" fullWidth onClick={() => { flow.setAirpodsConnected(false); flow.setS11Instant(true); flow.go('airpods'); }} style={{ marginTop: 4 }}>Try another pair</PButton>
          </div>
        </div>
      </div>
    );
  }

  /* none / never connected: stay on S14 searching screen */
  useEffect(() => { flow.go('airpods'); }, []);
  return null;
}

/* ============ S16 · Coaching style ============ */
const COACH_STYLES = [
  { k: 'gentle',   name: 'Relaxed',    desc: 'Occasional reminders only. Progress takes longer, but it won’t interrupt you much.' },
  { k: 'balanced', name: 'Balanced',   desc: 'The middle ground. Steady improvement without constant nudges.' },
  { k: 'strict',   name: 'Intensive', desc: 'More nudges, faster improvement. Best if you want real change quickly.' },
];

function CoachingScreen() {
  const flow = useFlow();
  const [style, setStyle] = useState(flow.coachStyle || 'balanced');

  const pick = (k) => { setStyle(k); };

  return (
    <Screen
      id="coaching" label="S16 · Coaching style"
      topBar={<TopBar onBack={() => flow.back()} />}
      cta={
        <PButton variant="primary" size="lg" fullWidth onClick={() => { flow.setCoachStyle(style); track('sensitivity_selected', { style }); flow.next(); }}>Continue</PButton>
      }
    >
      <div style={{ display: 'flex', flexDirection: 'column', paddingTop: 26 }}>
        <H style={{ fontSize: 27, marginBottom: 8 }}>How quickly do you want to improve?</H>
        <P style={{ fontSize: 15, marginBottom: 24 }}>More reminders means faster progress. You can change this anytime.</P>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {COACH_STYLES.map((c, i) => (
            <div key={c.k} className="rise" style={{ animationDelay: `${100 + i * 70}ms` }}>
              <button className={`opt-card ${style === c.k ? 'sel' : ''}`} style={{ alignItems: 'flex-start' }} onClick={() => pick(c.k)}>
                <span style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                  <span style={{ fontWeight: 600, fontSize: 17 }}>{c.name}</span>
                  <span className="poise-footnote" style={{ fontSize: 14 }}>{c.desc}</span>
                </span>
              </button>
            </div>
          ))}
        </div>
      </div>
    </Screen>
  );
}

/* ============ S15 · Calibration - signature moment 2 ============ */
const TOTAL_READINGS = 41; // representative: ~10 Hz × 4 s (annotated outside the frame)


function CalibrationScreen() {
  const flow = useFlow();
  const [phase, setPhase] = useState('ready'); // ready | count | sampling | analysis | done
  const [count, setCount] = useState(3);
  const [readings, setReadings] = useState(0);
  const [progress, setProgress] = useState(0);
  const [aLine, setALine] = useState(0);
  const [figReady, setFigReady] = useState(false);
  const [livePitch, setLivePitch] = useState(0);
  const [failed, setFailed] = useState(false);
  const timers = useRef([]);
  const pitchAnim = useRef(null);
  const attemptRef = useRef(0);
  const highPitchSince = useRef(null);
  const frozenPitch = useRef(0);
  // Keep a ref so T always reads the live motion value, even from stale closures.
  const motionRef = useRef(flow.t.motion);
  motionRef.current = flow.t.motion;
  const T = (fn, ms) => timers.current.push(setTimeout(fn, motionRef.current ? ms : ms * 0.05));

  useEffect(() => () => {
    timers.current.forEach(clearTimeout);
    if (pitchAnim.current) cancelAnimationFrame(pitchAnim.current);
  }, []);

  useEffect(() => {
    let cancelled = false;
    const load = (src) => new Promise((res) => {
      const im = new Image();
      const done = () => res();
      im.onload = () => (im.decode ? im.decode().then(done, done) : done());
      im.onerror = done;
      im.src = src;
    });
    Promise.all([load(window.__resources ? window.__resources.imgHeadMint : '/onboarding-demo/app/head-mint.png'), load(window.__resources ? window.__resources.imgTorsoMint : '/onboarding-demo/app/torso-mint.png')]).then(() => {
      if (!cancelled) requestAnimationFrame(() => { if (!cancelled) setFigReady(true); });
    });
    return () => { cancelled = true; };
  }, []);

  const tryAgain = () => {
    setFailed(false); setPhase('count'); setCount(3); setProgress(0); setLivePitch(0); highPitchSince.current = null;
    PoiseAudio.tick();
    T(() => { setCount(2); PoiseAudio.tick(); }, 1000);
    T(() => { setCount(1); PoiseAudio.tick(); }, 2000);
    T(() => startSampling(), 3000);
  };

  const begin = () => {
    setPhase('count'); setCount(3); PoiseAudio.tick();
    T(() => { setCount(2); PoiseAudio.tick(); }, 1000);
    T(() => { setCount(1); PoiseAudio.tick(); }, 2000);
    T(() => startSampling(), 3000);
  };

  const startSampling = () => {
    setPhase('sampling');
    const shouldFail = flow.t.variantCalib === 'Fail';
    highPitchSince.current = null;
    const failedDuringRun = { current: false };
    const dur = motionRef.current ? 4000 : 400;
    const t0 = performance.now();
    const step = () => {
      const el = Math.min(1, (performance.now() - t0) / dur);
      setProgress(Math.round(el * 100));
      if (el < 1) { requestAnimationFrame(step); }
      else {
        if (pitchAnim.current) { cancelAnimationFrame(pitchAnim.current); pitchAnim.current = null; }
        if (failedDuringRun.current) { setFailed(true); }
        else { setLivePitch(0); startAnalysis(); }
      }
    };
    requestAnimationFrame(step);
    /* Pitch simulation */
    const FAIL_THRESHOLD = 15, FAIL_SUSTAIN_MS = 400;
    const pt0 = performance.now();
    const pitchStep = () => {
      const t = (performance.now() - pt0) / 1000;
      const spike = shouldFail && t > 1.6 && t < 3.4 ? (
        t < 2.4
          ? 60 * Math.sin((t - 1.6) * Math.PI / 1.6)   // swing to +60°
          : -50 * Math.sin((t - 2.4) * Math.PI / 2.0)  // arc to -50°
      ) : 0;
      const p = 3.5 * Math.sin(t * 0.7) + 1.5 * Math.sin(t * 1.9 + 1.1) + 0.8 * Math.cos(t * 3.1) + spike;
      setLivePitch(p);
      if (Math.abs(p) >= FAIL_THRESHOLD) {
        if (!highPitchSince.current) highPitchSince.current = performance.now();
        else if (performance.now() - highPitchSince.current > FAIL_SUSTAIN_MS) {
          failedDuringRun.current = true;
          highPitchSince.current = null;
        }
      } else { highPitchSince.current = null; }
      pitchAnim.current = requestAnimationFrame(pitchStep);
    };
    pitchAnim.current = requestAnimationFrame(pitchStep);
  };

  const startAnalysis = () => {
    if (pitchAnim.current) { cancelAnimationFrame(pitchAnim.current); pitchAnim.current = null; }
    setLivePitch(0);
    setPhase('analysis'); setALine(0);
    T(() => setALine(1), 1000);
    T(() => setALine(2), 2000);
    T(() => { setPhase('done'); PoiseAudio.success(); }, 3100);
  };

  if (failed) {
    return (
      <Screen id="calibration" label="S15 · Calibration (fail)" topBar={<TopBar />}>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', paddingBottom: 100, position: 'relative' }}>
          {/* Frozen sampling content */}
          <PRing value={100} size={210} strokeWidth={10}>
            <div style={{ overflow: 'hidden', height: 136, display: 'flex', justifyContent: 'center', transform: 'translateX(3px)', opacity: figReady ? 1 : 0, transition: 'opacity 200ms' }}>
              <window.S5Figure pitch={Math.max(-30, Math.min(30, livePitch))} flashKey={0} height={170} wideMode />
            </div>
          </PRing>
          <div style={{ height: 110, paddingTop: 20, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
            <H style={{ fontSize: 22 }}>Reading your posture...</H>
            <P style={{ fontSize: 16 }}>Hold still, almost done.</P>
          </div>
          {/* Centered alert overlay — ios-alert texture */}
          <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 20px', animation: 'onbFade 180ms ease-out both' }}>
            <div style={{ width: '100%', borderRadius: 20, background: 'rgba(48,47,50,0.96)', backdropFilter: 'blur(24px)', WebkitBackdropFilter: 'blur(24px)', overflow: 'hidden', animation: 'alertPop 220ms var(--ease-out) both', boxShadow: '0 24px 64px rgba(0,0,0,0.7)' }}>
              <div style={{ padding: '24px 20px 20px' }}>
                <H style={{ fontSize: 22, textAlign: 'left', marginBottom: 10 }}>Couldn't set your baseline</H>
                <P style={{ fontSize: 15, textAlign: 'left', lineHeight: 1.4 }}>You moved a bit too much. Sit still, look straight ahead, and try again.</P>
              </div>
              <div style={{ borderTop: '0.5px solid rgba(255,255,255,0.12)', padding: '0 20px 20px', paddingTop: 16 }}>
                <button onClick={tryAgain} style={{ width: '100%', padding: '15px', borderRadius: 999, background: 'rgba(255,255,255,0.1)', border: 'none', color: '#E9E7EA', fontSize: 17, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', letterSpacing: '-0.01em' }}>Try again</button>
              </div>
            </div>
          </div>
        </div>
      </Screen>
    );
  }

  if (phase === 'ready') {
    return (
      <Screen
        id="calibration" label="S15 · Calibration (ready)"
        topBar={<TopBar onBack={() => flow.back()} />}
        cta={<PButton variant="primary" size="lg" fullWidth onClick={begin}>I'm ready</PButton>}
      >
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center' }}>
          <PRing value={0} size={210} strokeWidth={10}>
            <div style={{ overflow: 'hidden', height: 136, display: 'flex', justifyContent: 'center', transform: 'translateX(3px)', opacity: figReady ? 1 : 0, transition: 'opacity 200ms' }}>
              <window.S5Figure pitch={0} flashKey={0} height={170} wideMode />
            </div>
          </PRing>
          <div style={{ height: 110, paddingTop: 20, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
            <H style={{ fontSize: 28 }}>Let's find your baseline.</H>
            <P style={{ fontSize: 16 }}>Sit up tall and look straight ahead. Tap when you're ready.</P>
          </div>
        </div>
      </Screen>
    );
  }

  if (phase === 'count') {
    return (
      <Screen id="calibration" label="S15 · Calibration (countdown)" topBar={<TopBar />}>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', paddingBottom: 100 }}>
          <PRing value={0} size={210} strokeWidth={10}>
            <span key={count} className="poise-display-xl onb-screen-enter" style={{ fontSize: 80 }}>{count}</span>
          </PRing>
          <div style={{ height: 110, paddingTop: 20, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
            <H style={{ fontSize: 26 }}>Get into position.</H>
            <P style={{ fontSize: 16 }}>Sit up tall and look forward.</P>
          </div>
        </div>
      </Screen>
    );
  }

  if (phase === 'sampling') {
    return (
      <Screen id="calibration" label="S15 · Calibration (sampling)" topBar={<TopBar cue="Hold still" />}>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', paddingBottom: 100 }}>
          <PRing value={progress} size={210} strokeWidth={10}>
            <div style={{ overflow: 'hidden', height: 136, display: 'flex', justifyContent: 'center', transform: 'translateX(3px)', opacity: figReady ? 1 : 0, transition: 'opacity 200ms' }}>
              <window.S5Figure pitch={Math.max(-30, Math.min(30, livePitch))} flashKey={0} height={170} wideMode />
            </div>
          </PRing>
          {(() => {
            const abs = Math.abs(livePitch);
            const col = abs >= 15 ? '#FF6363' : abs >= 8 ? 'var(--status-warn)' : 'var(--mint)';
            const sign = livePitch >= 0 ? '+' : '';
            return (
              <div style={{ height: 110, paddingTop: 18, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
                <H style={{ fontSize: 22 }}>Reading your posture...</H>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
                  <span style={{ fontSize: 36, fontWeight: 700, color: col, transition: 'color 200ms', minWidth: 72, textAlign: 'center', fontVariantNumeric: 'tabular-nums' }}>
                    {sign}{Math.round(livePitch)}°
                  </span>

                </div>
              </div>
            );
          })()}
        </div>
      </Screen>
    );
  }

  if (phase === 'analysis') {
    const lines = ['Analysing your posture data', 'Finding your neutral position', 'Calculating your personal baseline'];
    return (
      <Screen id="calibration" label="S15 · Calibration (analysis: staged lines)" topBar={<TopBar />}>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 18 }}>
          {lines.map((l, i) => (
            <div key={l} style={{ display: 'flex', alignItems: 'center', gap: 12, opacity: aLine >= i ? 1 : 0.18, transition: 'opacity 500ms var(--ease-out)' }}>
              <span style={{ color: 'var(--mint)', width: 18, display: 'flex' }}>
                {aLine > i ? <window.IconCheck size={16} sw={2.6} /> : <span className={aLine === i && flow.t.motion ? 'spin-slow' : ''} style={{ width: 12, height: 12, borderRadius: '50%', border: '2px solid var(--mint)', borderTopColor: 'transparent', display: 'inline-block' }}></span>}
              </span>
              <span className="poise-title" style={{ fontSize: 20 }}>{l}</span>
            </div>
          ))}
          <span className="poise-footnote" style={{ marginTop: 16, opacity: aLine >= 1 ? 1 : 0, transition: 'opacity 500ms var(--ease-out)' }}>Your baseline is personal. Every nudge is measured against it.</span>
        </div>
      </Screen>
    );
  }

  /* done */
  return (
    <Screen
      id="calibration" label="S15 · Calibration (baseline set)"
      topBar={<TopBar />}
      cta={<PButton variant="primary" size="lg" fullWidth onClick={() => flow.next()}>Continue</PButton>}
    >
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 16, textAlign: 'center' }}>
        <div style={{ position: 'relative', width: 84, height: 84 }}>
          <span style={{ position: 'absolute', inset: 0, borderRadius: '50%', border: '2px solid var(--mint)', animation: 'pulseRingStay 2.2s var(--ease-out) 1 forwards' }}></span>
          <span className="onb-screen-enter" style={{ width: 84, height: 84, borderRadius: '50%', background: 'var(--accent-muted)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--mint)', boxShadow: 'var(--glow-mint)' }}>
            <window.IconCheck size={36} sw={2.2} />
          </span>
        </div>
        <H style={{ fontSize: 29 }}>Baseline set.</H>
        <P style={{ fontSize: 16 }}>Your AirPods are now calibrated. Recalibrate anytime in Settings.</P>
      </div>
    </Screen>
  );
}

/* ============ S13 · Live demo - the aha ============ */
function DemoScreen() {
  const flow = useFlow();
  const [phase, setPhase] = useState('ready'); // ready | idle | drop | hold | chime | nudge | recover | release
  const [hold, setHold] = useState(3);
  const [displayPitch, setDisplayPitch] = useState(0);
  const [figReady, setFigReady] = useState(false);
  const timers = useRef([]);
  const animFrame = useRef(null);
  const releaseTriggered = useRef(false);

  const T = (fn, ms) => timers.current.push(setTimeout(fn, flow.t.motion ? ms : ms * 0.08));

  // Preload silhouette images
  useEffect(() => {
    let cancelled = false;
    const load = (src) => new Promise((res) => {
      const im = new Image();
      im.onload = () => (im.decode ? im.decode().then(res, res) : res());
      im.onerror = res;
      im.src = src;
    });
    Promise.all([load(window.__resources ? window.__resources.imgHeadMint : '/onboarding-demo/app/head-mint.png'), load(window.__resources ? window.__resources.imgTorsoMint : '/onboarding-demo/app/torso-mint.png')]).then(() => {
      if (!cancelled) requestAnimationFrame(() => { if (!cancelled) setFigReady(true); });
    });
    return () => { cancelled = true; };
  }, []);

  // Single animation loop: converges then oscillates during held phases
  useEffect(() => {
    cancelAnimationFrame(animFrame.current);
    const oscillating = phase === 'idle' || phase === 'hold' || phase === 'chime' || phase === 'nudge';
    const oscTarget = phase === 'idle' ? 0 : 26;
    const base = (phase === 'ready' || phase === 'release' || phase === 'recover') ? 0 : 26;
    let lp = displayPitch; // local pitch — persists across frames in this closure
    let settled = false;
    let t0 = 0;

    const step = (now) => {
      if (oscillating) {
        if (!settled) {
          const diff = oscTarget - lp;
          if (Math.abs(diff) < 0.3) { lp = oscTarget; settled = true; t0 = now; }
          else { lp = lp + diff * 0.07; }
        } else {
          const t = (now - t0) / 1000;
          lp = phase === 'idle'
            ? Math.sin(t * 1.6) * 2.6 + Math.sin(t * 3.8 + 0.7) * 1.5 + Math.sin(t * 6.5 + 2.1) * 0.9
            : 26 + Math.sin(t * 0.75) * 2.4 + Math.sin(t * 2.1 + 0.5) * 1.1 + Math.sin(t * 4.4 + 1.9) * 0.45;
        }
        setDisplayPitch(lp);
        animFrame.current = requestAnimationFrame(step);
      } else {
        const diff = base - lp;
        if (Math.abs(diff) < 0.3) { lp = base; setDisplayPitch(base); return; }
        lp = lp + diff * 0.07;
        setDisplayPitch(lp);
        animFrame.current = requestAnimationFrame(step);
      }
    };
    animFrame.current = requestAnimationFrame(step);
    return () => cancelAnimationFrame(animFrame.current);
  }, [phase]); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => () => {
    timers.current.forEach(clearTimeout);
    cancelAnimationFrame(animFrame.current);
  }, []);

  const start = () => {
    timers.current.forEach(clearTimeout);
    timers.current = [];
    releaseTriggered.current = false;
    setPhase('idle');
    T(() => setPhase('drop'), 500);
    T(() => { setPhase('hold'); setHold(3); }, 1000);
    T(() => setHold(2), 2000);
    T(() => setHold(1), 3000);
    T(() => { setPhase('chime'); PoiseAudio.chime(); }, 4000);
    T(() => setPhase('nudge'), 5900);
    T(() => setPhase('recover'), 6500);
  };

  // Fire release once head returns to neutral after recover (300ms grace delay)
  useEffect(() => {
    if (phase !== 'recover' || displayPitch >= 1.5 || releaseTriggered.current) return;
    const t = setTimeout(() => {
      releaseTriggered.current = true;
      setPhase('release');
      track('demo_nudge_played', {});
    }, 100);
    return () => clearTimeout(t);
  }, [phase, displayPitch]);

  // Auto-start as soon as images are ready — no button press needed
  useEffect(() => {
    if (!figReady) return;
    const t = setTimeout(start, 400);
    return () => clearTimeout(t);
  }, [figReady]);

  const captionText =
    phase === 'ready' || phase === 'idle' || phase === 'drop' ? 'Try it now' :
    phase === 'hold' ? 'Nudge incoming…' :
    phase === 'chime' ? 'Nudge incoming…' :
    phase === 'nudge' || phase === 'recover' ? 'Now sit back up.' :
    phase === 'release' ? 'See how easy that was?' : '';

  const bodyText =
    phase === 'ready' || phase === 'idle' || phase === 'drop' ? 'Drop your head forward and hold it there' :
    phase === 'hold' ? 'Keep holding.' :
    phase === 'chime' ? null :
    phase === 'nudge' || phase === 'recover' ? 'Lift your head back up to your natural position.' :
    phase === 'release' ? 'Poise watches your posture in the background and plays a gentle reminder when you’ve been looking down for too long.' : null;

  const captionColor =
    phase === 'nudge' || phase === 'chime' || phase === 'recover' ? 'var(--text-accent)' :
    phase === 'hold' ? 'var(--status-warn)' :
    'var(--text-primary)';

  return (
    <Screen
      id="demo" label="S13 · Live demo"
      topBar={<TopBar onBack={() => flow.back()} />}
      cta={
        phase === 'release' ? (
          <React.Fragment>
            <PButton variant="primary" size="lg" fullWidth onClick={() => flow.next()}>Continue</PButton>
            <button className="ghost-link" onClick={start}>Hear it again</button>
          </React.Fragment>
        ) : <div style={{ height: 92 }}></div>
      }
    >
      {(phase === 'chime' || phase === 'nudge') && flow.t.motion && (
        <div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at center, rgba(189,238,205,0.16), transparent 70%)', animation: 'nudgeFlash 1.6s var(--ease-out) both', pointerEvents: 'none' }}></div>
      )}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 24, textAlign: 'center', padding: '0 8px' }}>
        <div style={{ height: 195, overflow: 'hidden', display: 'flex', justifyContent: 'center', WebkitMaskImage: 'linear-gradient(to bottom, black 55%, transparent 100%)', maskImage: 'linear-gradient(to bottom, black 55%, transparent 100%)' }}>
          <div style={{ opacity: figReady ? 1 : 0, transition: 'opacity 200ms' }}>
            <window.S5Figure pitch={displayPitch} flashKey={phase === 'chime' || phase === 'nudge' ? 1 : 0} height={240} wideMode />
          </div>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16, minHeight: 185, width: '100%', marginTop: 16 }}>
          {phase !== 'release' && (
            <span style={{ fontFamily: "'SF Pro'", fontSize: 17, fontWeight: 600, fontVariantNumeric: 'tabular-nums', letterSpacing: '0.04em', color: captionColor, opacity: 0.7, transition: 'color 300ms var(--ease-out)' }}>{Math.round(displayPitch)}°</span>
          )}
          <span className="poise-title" style={{
            fontSize: 26,
            fontWeight: 700,
            color: captionColor,
            transition: 'color 300ms var(--ease-out)'
          }}>{captionText}</span>
          {phase === 'hold' && (
            <span style={{ fontFamily: "'SF Pro'", fontSize: 72, fontWeight: 900, lineHeight: 1, color: 'var(--status-warn)', fontVariantNumeric: 'tabular-nums' }}>{hold}</span>
          )}
          {bodyText && (
            <P style={{ fontSize: 15 }}>{bodyText}</P>
          )}
        </div>
      </div>
    </Screen>
  );
}

Object.assign(window, { MotionScreen, NotificationScreen, AirpodsScreen, AirpodsResultScreen, CoachingScreen, CalibrationScreen, DemoScreen, COACH_STYLES });
