// app.jsx
// Root app: orchestrates intro → scan → menu → section windows.
// Also owns the status bar + prompt and threads Tweaks down to effects.

(function () {
  const { useEffect, useState, useRef } = React;

  const EMAIL = 'studiodysbalans@gmail.com';

  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "gridOpacity": 0.85,
    "trail": 0.3,
    "glitch": true,
    "readouts": true,
    "waveform": true,
    "storySpeed": 26,
    "sfx": true,
    "scanline": true,
    "palette": "default"
  }/*EDITMODE-END*/;

  function StatusBar({ now, route, sfxOn, onSfxToggle, onSkip, showReadouts, voOn, onVoToggle }) {
    return (
      <div className="statusbar">
        <div className="statusbar__left">
          <div className="statusbar__brand"><span className="dot"></span>ÐYSBALANS.MAINFRAME</div>
          <div className="statusbar__meta">v0.1 // terminal/data {now}</div>
          {showReadouts && <LiveReadouts />}
        </div>
        <div className="statusbar__right">
          {route === 'story' && (
            <button onClick={onVoToggle} className={voOn ? 'is-active' : ''}>
              SCORE: {voOn ? 'ON' : 'OFF'}
            </button>
          )}
          <LangToggle />
          <button onClick={onSfxToggle} className={sfxOn ? 'is-active' : ''}>
            SFX: {sfxOn ? 'ON' : 'OFF'}
          </button>
          {route !== 'scan' && (
            <button onClick={onSkip}>
              [HOME]
            </button>
          )}
        </div>
      </div>
    );
  }

  function Prompt({ route, openSection }) {
    const { t } = useLang();
    const labels = {
      boot: '/system/boot',
      scan: '/system/auth.retina',
      menu: '/system/menu',
      about: '/sys.about',
      portfolio: '/sys.portfolio',
      contact: '/sys.contact',
      story: '/sys.story/2226',
    };
    const C = t('hint_console'), M = t('hint_module'), MENU = t('hint_menu');
    const hints = {
      boot:      [['~', C]],
      scan:      [['Esc', t('hint_skip')], ['~', C]],
      menu:      [['1–4', t('hint_select')], ['~', C], ['Esc', t('hint_exit')]],
      about:     [['1–4', M], ['Esc', MENU], ['~', C]],
      portfolio: [['1–4', M], ['Esc', MENU], ['~', C]],
      contact:   [['1–4', M], ['C', t('hint_copy_short')], ['Esc', MENU], ['~', C]],
      story:     [['1–4', M], ['Esc', MENU], ['~', C]],
    };
    const toggleConsole = () => window.dispatchEvent(new Event('dys:toggle-console'));
    return (
      <div className="prompt">
        <div className="prompt__left">
          <span className="prompt__cursor"></span>
          <span className="prompt__path">ÐYS:</span>{labels[route] || ''}
          {['menu', 'about', 'portfolio', 'contact', 'story'].includes(route) && (
            <span className="prompt__copy">© CD/studio.dysbalans.MMXXVI</span>
          )}
        </div>
        <div className="prompt__hints">
          {(hints[route] || []).map(([k, label], i) =>
            k === '~' ? (
              <button
                type="button"
                className="prompt__hint prompt__hint--btn"
                key={i}
                onClick={toggleConsole}
                aria-label={label}
              ><kbd>{k}</kbd>{label}</button>
            ) : (
              <span className="prompt__hint" key={i}><kbd>{k}</kbd>{label}</span>
            )
          )}
        </div>
      </div>
    );
  }

  function useClock() {
    const [now, setNow] = useState(() => formatNow());
    useEffect(() => {
      const t = setInterval(() => setNow(formatNow()), 1000);
      return () => clearInterval(t);
    }, []);
    return now;
  }
  function formatNow() {
    const d = new Date();
    const pad = (n) => String(n).padStart(2, '0');
    const date = `${pad(d.getDate())}.${pad(d.getMonth()+1)}.${String(d.getFullYear()).slice(2)}`;
    const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
    return `(${date}) // (${time})`;
  }

  function App() {
    const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
    const { t: tr } = useLang();
    // Returning from the shame screen → skip intro + retina scan, land on menu.
    // Primary signal is the #home hash (survives any storage quirk); the
    // sessionStorage flag is a belt-and-braces fallback. Either one wins.
    const returnToMenu = (() => {
      let hit = false;
      try {
        if (location.hash === '#home' || location.hash === '#menu') hit = true;
      } catch (e) {}
      try {
        if (sessionStorage.getItem('dys_return') === '1') {
          sessionStorage.removeItem('dys_return');
          hit = true;
        }
      } catch (e) {}
      if (hit) {
        // scrub the hash so a manual refresh starts clean from the intro
        try { history.replaceState(null, '', location.pathname + location.search); } catch (e) {}
        return true;
      }
      return false;
    })();
    // Intro is parent-controlled: decide once whether to show, replay bumps playKey.
    const [showIntro, setShowIntro] = useState(() => returnToMenu ? false : window.shouldShowIntro());
    const [introPlay, setIntroPlay] = useState(0);
    const [route, setRoute] = useState(returnToMenu ? 'menu' : 'scan'); // scan | menu | about | portfolio | contact | story
    const [sfxOn, setSfxOn] = useState(false);
    const [voOn, setVoOn] = useState(() => localStorage.getItem('dys_story_score') !== '0');
    const now = useClock();
    const onceRef = useRef(false);

    // Apply palette + tweak vars to root
    useEffect(() => {
      const root = document.documentElement;
      root.style.setProperty('--grid-opacity', String(t.gridOpacity));
      root.style.setProperty('--trail-intensity', String(t.trail));

      // Palette swaps
      const palettes = {
        default:  { red: '#a90000', yellow: '#f2af0d' },
        cooler:   { red: '#0044aa', yellow: '#f2af0d' },
        crt:      { red: '#a90000', yellow: '#00ff88' },
        warning:  { red: '#ff2200', yellow: '#ffaa00' },
      };
      const p = palettes[t.palette] || palettes.default;
      root.style.setProperty('--red', p.red);
      root.style.setProperty('--yellow', p.yellow);
    }, [t]);

    // Toggle scanline visibility
    useEffect(() => {
      const el = document.querySelector('.scanline');
      if (el) el.style.display = t.scanline ? 'block' : 'none';
    }, [t.scanline]);

    // Fire a CRT glitch burst + ASCII flash on every folder/route transition,
    // EXCEPT direct module-to-module jumps (suppressFxRef) which feel instant.
    const prevRouteRef = useRef(route);
    const suppressFxRef = useRef(false);
    useEffect(() => {
      if (prevRouteRef.current !== route) {
        const prev = prevRouteRef.current;
        prevRouteRef.current = route;
        if (suppressFxRef.current) { suppressFxRef.current = false; return; }
        if (t.glitch && window.crtBurst) window.crtBurst();
        const labels = { about: 'SYS.ABOUT', portfolio: 'SYS.PORTFOLIO', contact: 'SYS.CONTACT', story: 'SYS.STORY', menu: 'SYS.MENU' };
        if (prev !== 'boot' && labels[route] && window.sysFlash) window.sysFlash(labels[route]);
      }
    }, [route]);

    // Direct keyboard nav between modules (1–4) without leaving to menu / no glitch
    useEffect(() => {
      const SECTIONS = ['about', 'portfolio', 'contact', 'story'];
      const MAP = { '1': 'about', '2': 'portfolio', '3': 'contact', '4': 'story' };
      function onKey(e) {
        const typing = document.activeElement &&
          ['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName);
        if (typing) return;
        if (window.__saperOpen) return;
        if (!SECTIONS.includes(route)) return;
        const target = MAP[e.key];
        if (target && target !== route) {
          e.preventDefault();
          suppressFxRef.current = true;
          window.Audio && window.Audio.blip(660);
          setRoute(target);
        }
      }
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [route]);

    function toggleSfx() {
      const enabled = window.Audio.toggle();
      setSfxOn(enabled);
      setTweak('sfx', enabled);
    }

    // Score toggle (2226 soundtrack) — default ON; persists choice.
    function toggleVo() {
      setVoOn((on) => {
        const next = !on;
        try { localStorage.setItem('dys_story_score', next ? '1' : '0'); } catch {}
        window.Audio && window.Audio.blip(next ? 880 : 300);
        return next;
      });
    }

    // One-time migration: older builds defaulted SFX off and persisted that in
    // the tweak store, which shadows the new default-on. Force it on once; the
    // user can still toggle it off afterwards and that choice sticks.
    useEffect(() => {
      try {
        if (!localStorage.getItem('dys_sfx_on_v2')) {
          localStorage.setItem('dys_sfx_on_v2', '1');
          if (!t.sfx) setTweak('sfx', true);
        }
      } catch {}
    }, []);

    // Returning from the shame screen is a continuation, not a fresh visit — the
    // ambient bed was ON before we left, so it should be ON again. A full
    // navigation drops the old AudioContext and a new page load carries no user
    // activation, so we can't just play; instead we (a) reflect the intended ON
    // state in the toggle immediately and (b) prime the engine so the bed wakes
    // on the very next gesture (on desktop that's the user's first click, which
    // is usually instant). Guarded by t.sfx so a user who muted stays muted.
    useEffect(() => {
      if (!returnToMenu || !t.sfx || !window.Audio) return;
      window.Audio.enable();        // arms the unlock + schedules the bed
      setSfxOn(true);               // toggle shows ON, matching pre-shame state
    }, []);

    // Base SFX (ambient) defaults ON and arms on the FIRST interaction from page
    // load (alongside the intro). Browser autoplay needs that one gesture, so on
    // a cold refresh it's silent until the first click/keypress, then it plays.
    //
    // iOS gotcha: `pointerdown`/`touchstart` do NOT count as a valid activation
    // gesture for resuming an AudioContext on Safari — only `touchend`/`click`/
    // `keydown` (and `pointerup`, which maps to touchend) do. The old build armed
    // on `pointerdown`, so the WEJDŹ tap created the context but the resume() it
    // fired was ignored, leaving SFX visibly ON but silent. Arm on the
    // gesture-END events instead so the unlock runs on a gesture iOS honours.
    useEffect(() => {
      if (onceRef.current || !t.sfx) return;
      onceRef.current = true;
      const EVENTS = ['pointerup', 'touchend', 'click', 'keydown'];
      const start = () => {
        window.Audio.enable();
        setSfxOn(true);
        EVENTS.forEach((ev) => window.removeEventListener(ev, start, true));
      };
      EVENTS.forEach((ev) => window.addEventListener(ev, start, { capture: true }));
    }, [t.sfx]);

    // Esc on menu re-runs the retina auth
    useEffect(() => {
      function onKey(e) {
        if (e.key === 'Escape' && route === 'menu') {
          if (window.__saperOpen) return;
          if (window.__consoleOpen) return;
          e.preventDefault();
          setRoute('scan');
        }
      }
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [route]);

    function go(r) {
      setRoute(r);
    }

    function backToMenu() {
      window.Audio && window.Audio.woosh();
      setRoute('menu');
    }

    return (
      <React.Fragment>
        {/* Background layers */}
        <BgGrid />
        {t.waveform && <GhostWaveform />}
        <div className="vignette"></div>
        <div className="scanline"></div>
        <div className="noise"></div>
        <CrtGlitch />
        <Cursor intensity={t.trail} />

        {/* Intro overlay (parent-controlled) */}
        <Intro
          show={showIntro}
          playKey={introPlay}
          onDone={() => setShowIntro(false)}
        />

        {/* Main shell */}
        <div className="shell">
          <span className="bracket-bl"></span>
          <span className="bracket-br"></span>

          <StatusBar
            now={now}
            route={route}
            sfxOn={sfxOn}
            onSfxToggle={toggleSfx}
            onSkip={() => go('menu')}
            showReadouts={t.readouts}
            voOn={voOn}
            onVoToggle={toggleVo}
          />

          <main className="stage" data-screen-label={route}>
            {route === 'scan'      && !showIntro && <RetinaScan onComplete={() => go('menu')} onSkip={() => go('menu')} />}
            {route === 'menu'      && <Menu onOpen={go} />}
            {route === 'about'     && <About onClose={backToMenu} />}
            {route === 'portfolio' && <Portfolio onClose={backToMenu} />}
            {route === 'contact'   && <Contact onClose={backToMenu} email={EMAIL} />}
            {route === 'story'     && <Story onClose={backToMenu} speed={t.storySpeed} audioOn={voOn} />}
          </main>

          <Prompt route={route} />
        </div>

        {/* Experience layers */}
        <SysFlash />
        <CommandLine
          onRoute={(r) => { if (r === 'menu') backToMenu(); else go(r); }}
          onToggleSfx={toggleSfx}
          email={EMAIL}
        />
        <IdleOverlay timeout={40000} />
        <Saper />

        {/* Tweaks panel */}
        <TweaksPanel title="Tweaks">
          <TweakSection label={tr('tw_atmosphere')} />
          <TweakSlider label={tr('tw_grid')} value={t.gridOpacity} min={0} max={1} step={0.05}
                       onChange={(v) => setTweak('gridOpacity', v)} />
          <TweakSlider label={tr('tw_trail')} value={t.trail} min={0} max={1} step={0.05}
                       onChange={(v) => setTweak('trail', v)} />
          <TweakToggle label={tr('tw_scanline')} value={t.scanline}
                       onChange={(v) => setTweak('scanline', v)} />
          <TweakToggle label={tr('tw_glitch')} value={t.glitch}
                       onChange={(v) => setTweak('glitch', v)} />
          <TweakToggle label={tr('tw_readouts')} value={t.readouts}
                       onChange={(v) => setTweak('readouts', v)} />
          <TweakToggle label={tr('tw_waveform')} value={t.waveform}
                       onChange={(v) => setTweak('waveform', v)} />

          <TweakSection label={tr('tw_story')} />
          <TweakSlider label={tr('tw_tempo')} value={t.storySpeed} min={8} max={70} step={2}
                       onChange={(v) => setTweak('storySpeed', v)} />

          <TweakSection label={tr('tw_palette')} />
          <TweakRadio label={tr('tw_mode')} value={t.palette}
                      options={['default', 'cooler', 'crt', 'warning']}
                      onChange={(v) => setTweak('palette', v)} />

          <TweakSection label={tr('tw_system')} />
          <TweakButton label={tr('tw_replay')} onClick={() => {
            window.Audio && window.Audio.woosh();
            setShowIntro(true);
            setIntroPlay((n) => n + 1);
          }} />
        </TweaksPanel>
      </React.Fragment>
    );
  }

  // Mount
  const root = ReactDOM.createRoot(document.getElementById('app'));
  root.render(<LangProvider><App /></LangProvider>);
})();
