// teaser-app.jsx — Octant Systems teaser host app + Tweaks.
const { useEffect: useEffApp, useMemo: useMemoApp } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "direction": "b",
  "motion": true,
  "glow": 30,
  "cubeStyle": "luminous",
  "telemetry": true,
  "scanlines": true,
  "careersAccess": "fixed"
}/*EDITMODE-END*/;

const DIRECTIONS = {
  a: { label: 'I · Transmission', Comp: () => window.DirectionA },
  b: { label: 'II · World Engine', Comp: () => window.DirectionB },
  c: { label: 'III · Empty Square', Comp: () => window.DirectionC },
};

// Careers + partner login are reachable from anywhere. Default: the careers
// page's fixed header (logo left, links right, always visible). Alternatives
// kept as Tweaks: corner link / slide-in header after the hero / both.
// PARTNER LOGIN renders quiet (faint, no dot) so CAREERS stays the primary CTA.
// If the visitor holds a live portal session, the label becomes
// "PARTNER PORTAL (username)" and links straight into the portal.
const PORTAL_URL = 'https://portal.octant.systems';

function usePortalUser(){
  const [user, setUser] = React.useState(null);
  useEffApp(() => {
    let alive = true;
    fetch(PORTAL_URL + '/whoami', { credentials: 'include' })
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => { if (alive && d && d.user) setUser(d.user); })
      .catch(() => {});   // portal unreachable -> stay in login mode
    return () => { alive = false; };
  }, []);
  return user;
}

function CareersAccess({ mode }){
  const portalUser = usePortalUser();
  const partnerLabel = portalUser ? `PARTNER PORTAL (${portalUser})` : 'PARTNER LOGIN';
  const partnerHref = portalUser ? PORTAL_URL : 'partners.html';
  const [scrolled, setScrolled] = React.useState(false);
  useEffApp(() => {
    if (mode === 'corner' || mode === 'fixed'){ setScrolled(false); return; }
    const check = () => setScrolled((window.scrollY || 0) > (window.innerHeight || 800) * 0.6);
    window.addEventListener('scroll', check, { passive:true });
    const id = setInterval(check, 350); // scroll events can drop in embed contexts
    check();
    return () => { window.removeEventListener('scroll', check); clearInterval(id); };
  }, [mode]);
  if (mode === 'fixed'){
    return <header className="ca-head">
      <a href="#top" onClick={(e) => { e.preventDefault(); window.scrollTo({ top:0, behavior:'smooth' }); }}
        style={{ display:'inline-flex' }} aria-label="Back to top"><StaticLockup h={22} /></a>
      <nav className="ca-nav">
        <a className="ca-ret quiet mono" href={partnerHref}>{partnerLabel}</a>
        <span className="ca-sep" aria-hidden="true"></span>
        <a className="ca-ret mono" href="careers.html">CAREERS</a>
      </nav>
    </header>;
  }
  const corner = mode !== 'header';
  const header = mode !== 'corner';
  return <>
    {corner && <div className={'corner-nav' + (header && scrolled ? ' hide' : '')}>
      <a className="careers-corner quiet" href={partnerHref}>{partnerLabel}</a>
      <a className="careers-corner" href="careers.html">
        <span className="ca-dot blink"></span>CAREERS</a>
    </div>}
    {header && <div className={'careers-head' + (scrolled ? ' in' : '')}>
      <a href="#top" onClick={(e) => { e.preventDefault(); window.scrollTo({ top:0, behavior:'smooth' }); }}
        style={{ display:'inline-flex' }} aria-label="Back to top"><StaticLockup h={20} /></a>
      <nav className="ca-nav">
        <a className="ch-link quiet" href={partnerHref}>{partnerLabel}</a>
        <span className="ca-sep" aria-hidden="true"></span>
        <a className="ch-link" href="careers.html">CAREERS</a>
      </nav>
    </div>}
  </>;
}

function usePrefersReduced(){
  const [r, setR] = React.useState(() =>
    window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
  useEffApp(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const on = () => setR(mq.matches);
    mq.addEventListener ? mq.addEventListener('change', on) : mq.addListener(on);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', on) : mq.removeListener(on); };
  }, []);
  return r;
}

function App(){
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const prefersReduced = usePrefersReduced();
  const motion = !!t.motion && !prefersReduced;

  useScrollVars();
  const tel = useTelemetry(motion && !!t.telemetry);

  // body-level flags + glow intensity
  useEffApp(() => {
    document.body.classList.toggle('no-motion', !motion);
    document.body.classList.toggle('no-scan', !t.scanlines);
    document.documentElement.style.setProperty('--glow-k', (Math.max(0, Math.min(120, t.glow)) / 100).toFixed(3));
  }, [motion, t.scanlines, t.glow]);

  // reset scroll when switching concept
  useEffApp(() => { window.scrollTo(0, 0); }, [t.direction]);

  const dir = DIRECTIONS[t.direction] ? t.direction : 'a';
  const Comp = DIRECTIONS[dir].Comp();
  const shared = { motion, glow: t.glow, showTel: !!t.telemetry, tel, cubeStyle: t.cubeStyle };

  return <>
    <div key={dir}>
      {Comp ? <Comp {...shared} /> : null}
    </div>

    <CareersAccess mode={t.careersAccess || 'fixed'} />

    <TweaksPanel title="Tweaks">
      <TweakSection label="Direction" />
      <TweakRadio label="Concept" value={dir}
        options={Object.keys(DIRECTIONS).map(k => ({ value: k, label: DIRECTIONS[k].label }))}
        onChange={(v) => setTweak('direction', v)} />

      <TweakSection label="Careers" />
      <TweakRadio label="Access" value={t.careersAccess || 'fixed'}
        options={[
          { value:'fixed', label:'Fixed header' },
          { value:'corner', label:'Corner link' },
          { value:'header', label:'Scroll header' },
          { value:'both', label:'Corner + scroll' },
        ]}
        onChange={(v) => setTweak('careersAccess', v)} />

      {dir === 'b' && <>
        <TweakSection label="World engine" />
        <TweakRadio label="Cube style" value={t.cubeStyle}
          options={[
            { value:'solid', label:'Solid' },
            { value:'faithful', label:'Faithful' },
            { value:'luminous', label:'Luminous' },
            { value:'depth', label:'Depth' },
          ]}
          onChange={(v) => setTweak('cubeStyle', v)} />
      </>}

      <TweakSection label="Motion & feel" />
      <TweakToggle label="Motion" value={!!t.motion}
        onChange={(v) => setTweak('motion', v)} />
      <TweakSlider label="Glow" value={t.glow} min={0} max={120} step={5} unit="%"
        onChange={(v) => setTweak('glow', v)} />
      <TweakToggle label="Telemetry" value={!!t.telemetry}
        onChange={(v) => setTweak('telemetry', v)} />
      <TweakToggle label="Scanlines" value={!!t.scanlines}
        onChange={(v) => setTweak('scanlines', v)} />
    </TweaksPanel>
  </>;
}

/* Load the editable copy from content.md, then mount. If the fetch fails
   (e.g. the page was opened via file://), applyContent is skipped and the
   built-in DEFAULT_COPY is used — the site still renders. */
(function boot(){
  const mount = () => ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  fetch('content.md', { cache: 'no-cache' })
    .then((r) => r.ok ? r.text() : Promise.reject(new Error('HTTP ' + r.status)))
    .then((md) => applyContent(md))
    .catch((err) => console.warn('[octant] content.md not loaded; using built-in copy.', err))
    .finally(mount);
})();
