/* Poise onboarding — web export entry point.
   Differs from main.jsx (the in-tool review prototype) in three ways:
   1. No localStorage anywhere — every refresh starts clean at S1, defaults only.
   2. Responsive: desktop keeps the review canvas (subtitle, zoom bar, tweaks);
      mobile drops all of that and the phone fills the viewport edge-to-edge,
      like the app itself rather than a mockup of it.
   3. The tweaks panel has no host to open it, so a small self-contained
      toggle drives it directly (open by default on desktop). */

const { useState, useEffect, useRef, useCallback } = React;

/* Visitors arriving from the landing-page CTA (/onboarding-demo?visitor=1) get
   the phone on its own: no tweaks panel, no screen-ID note. Bare
   /onboarding-demo is unchanged, review canvas and all. A default, not a lock:
   drop the param and the panel is back. */
const IS_VISITOR = new URLSearchParams(window.location.search).get('visitor') === '1';

const TWEAK_DEFAULTS = {
  variantCalib: 'Success',
  forceBranch: 'Auto',
  airpodsResult: 'compatible',
  motion: true,
  sound: true,
  variantPaywall: 'Free trial',
};

function useIsMobile() {
  const read = () => {
    const p = new URLSearchParams(window.location.search).get('mobile');
    if (p === '1') return true;
    if (p === '0') return false;
    return window.matchMedia('(max-width: 768px)').matches;
  };
  const [isMobile, setIsMobile] = useState(read);
  useEffect(() => {
    const mq = window.matchMedia('(max-width: 768px)');
    const onChange = () => setIsMobile(read());
    mq.addEventListener ? mq.addEventListener('change', onChange) : mq.addListener(onChange);
    window.addEventListener('resize', onChange);
    return () => {
      mq.removeEventListener ? mq.removeEventListener('change', onChange) : mq.removeListener(onChange);
      window.removeEventListener('resize', onChange);
    };
  }, []);
  return isMobile;
}

const SCREEN_COMPONENTS = {
  start: StartGateScreen,
  splash: SplashScreen,
  welcome: WelcomeScreen,
  hook: HookScreen,
  'why-read': WhyReadScreen,
  'why-compound': WhyCompoundScreen,
  'why-you': WhyYouScreen,
  q0: (p) => <QuestionScreen index={0} {...p} />,
  q1: (p) => <QuestionScreen index={1} {...p} />,
  q2: (p) => <QuestionScreen index={2} {...p} />,
  q3: (p) => <QuestionScreen index={3} {...p} />,
  insight: InsightScreen,
  motion: MotionScreen,
  notifications: NotificationScreen,
  airpods: AirpodsScreen,
  'airpods-result': AirpodsResultScreen,
  coaching: CoachingScreen,
  'desk-schedule': DeskScheduleScreen,
  calibration: CalibrationScreen,
  demo: DemoScreen,
  commit: CommitScreen,
  'commit-day1': Day1Screen,
  preparing: PreparingScreen,
  showcase: ShowcaseScreen,
  anchor: AnchorScreen,
  paywall: PaywallScreen,
  success: SuccessScreen,
  end: EndScreen,
};

function useViewportScale(zoom) {
  const [, forceUpdate] = useState(0);
  useEffect(() => {
    const onR = () => forceUpdate((n) => n + 1);
    window.addEventListener('resize', onR);
    return () => window.removeEventListener('resize', onR);
  }, []);
  return Math.min(1, (window.innerHeight - 92) / 864, (window.innerWidth - 32) / 410) * zoom;
}

// The 5 static SF Pro Text weights (~220KB total, subsetted) aren't preloaded
// like the variable master - they load lazily whenever a browser first needs
// that exact weight. Prefetching them at low priority while the visitor is
// still on the start gate warms the cache during natural dwell time, so
// nothing downloads mid-flow.
const PREFETCH_FONTS = [
  '/onboarding-demo/_ds/PoiseDesignSystem_eee0d2/fonts/SF-Pro-Text-Regular.woff2',
  '/onboarding-demo/_ds/PoiseDesignSystem_eee0d2/fonts/SF-Pro-Text-Medium.woff2',
  '/onboarding-demo/_ds/PoiseDesignSystem_eee0d2/fonts/SF-Pro-Text-Semibold.woff2',
  '/onboarding-demo/_ds/PoiseDesignSystem_eee0d2/fonts/SF-Pro-Text-Bold.woff2',
  '/onboarding-demo/_ds/PoiseDesignSystem_eee0d2/fonts/SF-Pro-Text-Heavy.woff2',
];
function prefetchFonts() {
  PREFETCH_FONTS.forEach((href) => {
    if (document.querySelector(`link[href="${href}"]`)) return;
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.as = 'font';
    link.type = 'font/woff2';
    link.crossOrigin = 'anonymous';
    link.href = href;
    document.head.appendChild(link);
  });
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const isMobile = useIsMobile();

  const [screenId, setScreenId] = useState('start');
  const [answers, setAnswers] = useState({});
  const [coachStyle, setCoachStyle] = useState('balanced');
  const [schedules, setSchedulesState] = useState({});
  const [motionDenied, setMotionDenied] = useState(false);
  const [exitSheetShown, setExitSheetShownState] = useState(false);
  const [airpodsConnected, setAirpodsConnected] = useState(false);
  const [s11Instant, setS11Instant] = useState(false);
  const [showcaseReachedEnd, setShowcaseReachedEnd] = useState(false);
  const [progressFading, setProgressFading] = useState(false);
  const [zoom, setZoom] = useState(1);
  const [tweaksOpen, setTweaksOpen] = useState(!IS_VISITOR);

  const scaleCalc = useViewportScale(zoom);
  const scale = isMobile ? 1 : scaleCalc;

  useEffect(() => { setProgressFading(false); }, [screenId]);
  useEffect(() => { document.body.classList.toggle('no-motion', !t.motion); }, [t.motion]);
  useEffect(() => { PoiseAudio.setEnabled(t.sound); }, [t.sound]);
  useEffect(() => { if (screenId !== 'start') track('onboarding_started', {}); }, [screenId === 'start']);
  useEffect(() => { prefetchFonts(); }, []);

  // Drive the tweaks panel directly — there's no editor host in a hosted
  // export to send it activation messages, so we post them to ourselves.
  useEffect(() => {
    if (isMobile) return;
    window.postMessage({ type: tweaksOpen ? '__activate_edit_mode' : '__deactivate_edit_mode' }, '*');
  }, [tweaksOpen, isMobile]);
  useEffect(() => {
    const onMsg = (e) => { if (e?.data?.type === '__edit_mode_dismissed') setTweaksOpen(false); };
    window.addEventListener('message', onMsg);
    return () => window.removeEventListener('message', onMsg);
  }, []);

  const answersRef = useRef(answers);
  answersRef.current = answers;
  const go = useCallback((id) => setScreenId(id), []);
  const next = useCallback(() => setScreenId((cur) => {
    const i = SCREENS.findIndex((s) => s.id === cur);
    let j = i + 1;
    while (j < SCREENS.length && !screenEnabled(SCREENS[j].id, answersRef.current)) j++;
    return SCREENS[Math.min(SCREENS.length - 1, j)].id;
  }), []);
  const back = useCallback(() => setScreenId((cur) => {
    const i = SCREENS.findIndex((s) => s.id === cur);
    let j = i - 1;
    while (j > 0 && !screenEnabled(SCREENS[j].id, answersRef.current)) j--;
    let target = SCREENS[Math.max(0, j)].id;
    if (target === 'airpods-result' && airpodsConnected && t.airpodsResult === 'compatible') {
      target = 'airpods';
    }
    return target;
  }), [airpodsConnected, t.airpodsResult]);
  const restart = useCallback(() => {
    setAnswers({});
    setSchedulesState({});
    setExitSheetShownState(false);
    setMotionDenied(false);
    setAirpodsConnected(false);
    setS11Instant(false);
    setShowcaseReachedEnd(false);
    setScreenId('splash');
  }, []);

  const setAnswer = useCallback((k, v) => setAnswers((a) => {
    const n = { ...a };
    if (v === undefined) delete n[k]; else n[k] = v;
    return n;
  }), []);
  const setSchedule = useCallback((k, v) => setSchedulesState((s) => ({ ...s, [k]: v })), []);
  const setExitSheetShown = useCallback((v) => setExitSheetShownState(v), []);

  useEffect(() => {
    if (isMobile) return;
    const onKey = (e) => {
      if (e.target && /INPUT|TEXTAREA/.test(e.target.tagName)) return;
      if (e.key === 'ArrowRight') next();
      if (e.key === 'ArrowLeft') back();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [next, back, isMobile]);

  const flow = {
    t, setTweak, screenId, go, next, back, restart,
    answers, setAnswer, coachStyle, setCoachStyle, schedules, setSchedule,
    motionDenied, setMotionDenied, exitSheetShown, setExitSheetShown,
    airpodsConnected, setAirpodsConnected, s11Instant, setS11Instant,
    showcaseReachedEnd, setShowcaseReachedEnd,
    progressFading, setProgressFading, isMobile,
  };

  const ScreenComp = SCREEN_COMPONENTS[screenId] || WelcomeScreen;
  const idx = Math.max(0, SCREENS.findIndex((s) => s.id === screenId));
  const screenMeta = SCREENS[idx];
  const prewarmFilm = screenId === 'splash';
  const branch = screenId === 'insight' ? pickBranch(answers, t.forceBranch) : null;

  const phoneContent = (
    <React.Fragment>
      {prewarmFilm && (
        <div className="onb-prewarm" style={{ position: 'absolute', inset: 0 }} key="welcome">
          <WelcomeScreen prewarm={true} />
        </div>
      )}
      {screenId === 'airpods-result' && t.airpodsResult === 'incompatible' ? (
        <React.Fragment>
          <div className="onb-frozen" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }} key="airpods-frozen">
            <AirpodsScreen forceIncompatible />
          </div>
          <div style={{ position: 'absolute', inset: 0 }} key="airpods-result">
            <AirpodsResultScreen />
          </div>
        </React.Fragment>
      ) : (
        <div style={{ position: 'absolute', inset: 0 }} key={screenId}>
          <ScreenComp />
        </div>
      )}
      <OnbProgressBar screenId={screenId} fading={progressFading} />
    </React.Fragment>
  );

  if (isMobile) {
    return (
      <FlowCtx.Provider value={flow}>
        <div className="onb-mobile-stage">
          <window.PhoneShell showTabBar={false} fullBleed hideStatusBar hideHomeIndicator>
            {phoneContent}
          </window.PhoneShell>
        </div>
      </FlowCtx.Provider>
    );
  }

  const scrollPad = 24;
  return (
    <FlowCtx.Provider value={flow}>
      {/* Desktop only: a way out of the demo that doesn't depend on the browser
          back button. Mobile gets nothing here - the phone runs edge-to-edge and
          a floating chrome pill would sit on top of the app - so the exit there
          is the one on the end-of-flow screen, which both layouts share. */}
      <a className="onb-home-link" href="/">
        <span aria-hidden="true" style={{ fontSize: 14, lineHeight: 1 }}>←</span>
        Poise home
      </a>

      <div className="onb-stage" style={{ overflow: 'auto' }}>
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          minHeight: '100%', padding: scrollPad, boxSizing: 'border-box',
        }}>
          <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 20 }}>
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
              <div style={{ width: 410 * scale, height: 864 * scale, flexShrink: 0 }}>
                <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', position: 'relative' }}>
                  <window.PhoneShell showTabBar={false}>
                    {screenId === 'start' ? (
                      <div style={{ position: 'absolute', inset: 0, background: 'var(--black)' }} />
                    ) : phoneContent}
                  </window.PhoneShell>
                  {screenId === 'start' && <window.StartGateCard />}
                </div>
              </div>
              {screenId !== 'start' && !IS_VISITOR && (
                <div className="onb-note">
                  <b>{screenMeta.label}</b>{branch ? <span> · branch fired: <b>{branch.name}</b></span> : null}
                  <span> · ← → to step through</span>
                </div>
              )}
            </div>
            {screenId === 'airpods' && (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: 216, flexShrink: 0, background: 'rgba(2,0,3,0.85)', border: '1px solid rgba(233,231,234,0.12)', borderRadius: 16, padding: 16 }}>
                <span style={{ color: '#828183', fontSize: 11, fontWeight: 600, fontFamily: 'var(--font-sans)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>Simulate AirPods</span>
                <button
                  onClick={() => { setTweak('airpodsResult', 'compatible'); go('airpods-result'); }}
                  style={{ background: 'rgba(189,238,205,0.12)', border: '1px solid rgba(189,238,205,0.35)', color: '#BDEECD', borderRadius: 10, padding: '10px 12px', fontSize: 13, fontWeight: 600, fontFamily: 'var(--font-sans)', cursor: 'pointer', textAlign: 'left', lineHeight: 1.3 }}
                >
                  Compatible AirPods connected
                </button>
                <button
                  onClick={() => { setTweak('airpodsResult', 'incompatible'); go('airpods-result'); }}
                  style={{ background: 'rgba(255,183,77,0.10)', border: '1px solid rgba(255,183,77,0.35)', color: '#FFB74D', borderRadius: 10, padding: '10px 12px', fontSize: 13, fontWeight: 600, fontFamily: 'var(--font-sans)', cursor: 'pointer', textAlign: 'left', lineHeight: 1.3 }}
                >
                  Incompatible AirPods connected
                </button>
              </div>
            )}
          </div>
        </div>
      </div>

      <div style={{ position: 'fixed', bottom: 16, left: 16, zIndex: 99, display: 'flex', alignItems: 'center', gap: 10, background: 'rgba(2,0,3,0.85)', border: '1px solid rgba(233,231,234,0.12)', borderRadius: 999, padding: '6px 12px' }}>
        <span style={{ color: '#828183', fontSize: 12, fontFamily: 'var(--font-sans)' }}>Zoom</span>
        <input type="range" min="1" max="4" step="0.25" value={zoom} onChange={(e) => setZoom(parseFloat(e.target.value))} style={{ width: 100, accentColor: '#BDEECD' }} />
        <span style={{ color: '#BDEECD', fontSize: 12, fontFamily: 'var(--font-sans)', minWidth: 30 }}>{zoom.toFixed(2)}×</span>
        <button onClick={() => setZoom(1)} style={{ background: 'none', border: 'none', color: '#828183', cursor: 'pointer', fontSize: 12, fontFamily: 'var(--font-sans)', padding: '0 4px' }}>Reset</button>
      </div>

      <button
        onClick={() => setTweaksOpen((v) => !v)}
        style={{ position: 'fixed', bottom: 16, right: 16, zIndex: 98, background: 'rgba(2,0,3,0.85)', border: '1px solid rgba(233,231,234,0.12)', borderRadius: 999, padding: '8px 16px', color: tweaksOpen ? '#BDEECD' : '#828183', fontSize: 12, fontWeight: 600, fontFamily: 'var(--font-sans)', cursor: 'pointer', display: (tweaksOpen || IS_VISITOR) ? 'none' : 'block' }}
      >
        Tweaks
      </button>

      <TweaksPanel>
        <TweakSection label="Flow" />
        <TweakSelect label="Screen" value={screenMeta.label} options={SCREENS.filter((s) => !s.hideFromJump).map((s) => s.label)}
          onChange={(v) => go(SCREENS.find((s) => s.label === v).id)} />
        <TweakButton label="Restart flow" onClick={restart} />

        <TweakSection label="Variants" />
        <TweakRadio label="S15 calibration" value={t.variantCalib} options={['Success', 'Fail']}
          onChange={(v) => { setTweak('variantCalib', v); go('calibration'); }} />
        <TweakRadio label="S23 paywall" value={t.variantPaywall} options={['Free trial', 'Trial used']}
          onChange={(v) => { setTweak('variantPaywall', v); go('paywall'); }} />

        <TweakSection label="Simulation" />
        <TweakSelect label="Insight branch (S11)" value={t.forceBranch} options={['Auto', ...BRANCHES.map((b) => b.name)]}
          onChange={(v) => { setTweak('forceBranch', v); go('insight'); }} />
        <TweakButton label="Re-arm exit sheet (S23x)" onClick={() => setExitSheetShown(false)} />

        <TweakSection label="Playback" />
        <TweakToggle label="Choreography (motion)" value={t.motion} onChange={(v) => setTweak('motion', v)} />
        <TweakToggle label="Sound" value={t.sound} onChange={(v) => setTweak('sound', v)} />
      </TweaksPanel>
    </FlowCtx.Provider>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
