// enhance.jsx
// Experience layers: scramble text, boot log, command line (+ secret routes),
// idle state, transition ASCII flash, live status readouts.
// All exported to window for the other babel scripts.

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

  const SCRAMBLE_CHARS = '!<>-_\\/[]{}=+*^?#________01ÐΞ░▒';

  // ============================================================
  // SCRAMBLE TEXT — decode-on-hover, progressive left→right
  // ============================================================
  // Resolves cleanly from the start of the word to the end (no perpetual
  // glitch on the tail). Width is locked by an invisible sizer so siblings
  // never shift while characters resolve.
  function scrambleAnimate(setText, target, done) {
    const length = target.length;
    const queue = [];
    for (let i = 0; i < length; i++) {
      // progressive reveal: each glyph settles a little after the previous one
      const end = Math.round(i * 2.4) + 6 + Math.floor(Math.random() * 5);
      queue.push({ char: target[i], end, rnd: '' });
    }
    let frame = 0;
    let raf;
    function update() {
      let out = '';
      let complete = 0;
      for (let i = 0; i < queue.length; i++) {
        const q = queue[i];
        if (q.char === ' ') {out += ' ';complete++;continue;}
        if (frame >= q.end) {
          complete++;
          out += q.char;
        } else {
          // every slot always shows SOMETHING → constant glyph count
          if (!q.rnd || Math.random() < 0.5) {
            q.rnd = SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)];
          }
          out += `<span class="scr-rnd">${q.rnd}</span>`;
        }
      }
      setText(out);
      if (complete === queue.length) {done && done();return;}
      frame++;
      raf = requestAnimationFrame(update);
    }
    update();
    return () => cancelAnimationFrame(raf);
  }

  function ScrambleText({ text, className, auto = false, delay = 0 }) {
    const [html, setHtml] = useState('');
    const cancelRef = useRef(null);

    const run = useCallback(() => {
      if (cancelRef.current) cancelRef.current();
      cancelRef.current = scrambleAnimate((h) => setHtml(h), text, () => setHtml(text));
    }, [text]);

    useEffect(() => {
      setHtml(text);
      if (auto) {
        const t = setTimeout(run, delay);
        return () => {clearTimeout(t);if (cancelRef.current) cancelRef.current();};
      }
      return () => {if (cancelRef.current) cancelRef.current();};
    }, [text, auto, delay, run]);

    return (
      <span className={`scramble ${className || ''}`} onMouseEnter={run}>
        {/* invisible sizer locks the width so nothing reflows */}
        <span className="scramble__sizer" aria-hidden="true">{text}</span>
        <span className="scramble__anim" dangerouslySetInnerHTML={{ __html: html }} style={{ fontSize: "25px", lineHeight: "2.05", letterSpacing: "-1.1px" }}></span>
      </span>);

  }

  // ============================================================
  // REVEAL TEXT — machine reveal behind a travelling text caret
  // ============================================================
  // Renders `text` (may contain inline HTML) progressively, character by
  // character, with a block caret riding the leading edge. Speed is in
  // chars/sec and is live-tunable. Works on plain strings; for rich text
  // pass `html` and it reveals the raw markup's visible characters by
  // wrapping completed text in a clip.
  function RevealText({ text, html, className, speed = 42, startDelay = 200, tag = 'div', onDone }) {
    const [count, setCount] = useState(0);
    const Tag = tag;
    const source = html != null ? html : text;

    // For HTML we reveal by character index but must not split tags. Precompute
    // a list of "reveal points" = indices that are safe (outside of <...>).
    const planRef = useRef(null);
    if (!planRef.current || planRef.current.src !== source) {
      const safe = [];
      let inTag = false;
      for (let i = 0; i < source.length; i++) {
        const ch = source[i];
        if (ch === '<') inTag = true;
        if (!inTag) safe.push(i);
        if (ch === '>') inTag = false;
      }
      planRef.current = { src: source, safe };
    }
    const total = planRef.current.safe.length;

    useEffect(() => {
      setCount(0);
      let raf,startedAt = null;
      const begin = performance.now() + startDelay;
      function step(now) {
        if (now < begin) {raf = requestAnimationFrame(step);return;}
        if (startedAt === null) startedAt = now;
        const elapsed = (now - startedAt) / 1000;
        const c = Math.min(total, Math.floor(elapsed * speed));
        setCount(c);
        if (c < total) raf = requestAnimationFrame(step);else
        onDone && onDone();
      }
      raf = requestAnimationFrame(step);
      return () => cancelAnimationFrame(raf);
    }, [source, speed, startDelay, total]);

    // Build the revealed substring up to the safe index `count`
    const safe = planRef.current.safe;
    const cut = count >= total ? source.length : safe[count] !== undefined ? safe[count] : 0;
    const shown = source.slice(0, cut);
    const done = count >= total;

    return (
      <Tag className={`reveal ${done ? 'is-done' : ''} ${className || ''}`}>
        <span className="reveal__text" dangerouslySetInnerHTML={{ __html: shown }}></span>
        {!done && <span className="reveal__caret"></span>}
      </Tag>);} // ============================================================
  // BOOT LOG — terminal lines after intro, before Y/N
  // ============================================================
  const BOOT_LINES = ['ÐYS.MAINFRAME // cold boot', 'mounting narrative.core ............ OK', 'linking identity.module ............ OK', 'calibrating tone of voice .......... OK', 'loading operator hologram .......... OK', 'decrypting story.kernel ............ OK', 'handshake z użytkownikiem .......... ?'];

  function BootLog({ onComplete }) {
    const [shown, setShown] = useState(0);
    const doneRef = useRef(false);

    function finish() {
      if (doneRef.current) return;
      doneRef.current = true;
      onComplete && onComplete();
    }

    useEffect(() => {
      let i = 0;
      const timers = [];
      function next() {
        i++;
        setShown(i);
        if (window.Audio && window.Audio.isEnabled()) window.Audio.tick();
        if (i < BOOT_LINES.length) {
          timers.push(setTimeout(next, 230 + Math.random() * 180));
        } else {
          timers.push(setTimeout(finish, 700));
        }
      }
      timers.push(setTimeout(next, 250));

      function skip(e) {e.preventDefault();finish();}
      window.addEventListener('keydown', skip);
      return () => {timers.forEach(clearTimeout);window.removeEventListener('keydown', skip);};
    }, []);

    return (
      <div className="view">
        <div className="bootlog">
          {BOOT_LINES.slice(0, shown).map((l, i) => {
            const ok = l.endsWith('OK');
            const q = l.endsWith('?');
            return (
              <div className="bootlog__line" key={i}>
                <span className="bootlog__caret">&gt;</span>
                <span className="bootlog__text">{l.replace(/ OK$| \?$/, '')}</span>
                {ok && <span className="bootlog__ok">OK</span>}
                {q && <span className="bootlog__q">WAIT</span>}
              </div>);

          })}
          <div className="bootlog__line bootlog__line--cursor">
            <span className="bootlog__caret">&gt;</span>
            <span className="prompt__cursor"></span>
          </div>
          <div className="bootlog__skip">[dowolny klawisz] pomiń</div>
        </div>
      </div>);

  }

  // ============================================================
  // SYS FLASH — ASCII interstitial on folder transitions
  // ============================================================
  const ASCII_FRAME = [
  '┌─────────────────────────────┐',
  '│  ░░ ROUTING SYSTEM SIGNAL ░░ │',
  '│  ▓▓▓▓▒▒▒▒░░░░  ::  ░░░░▒▒▓▓▓  │',
  '└─────────────────────────────┘'];


  function SysFlash() {
    const [state, setState] = useState({ on: false, label: '' });
    useEffect(() => {
      window.sysFlash = (label) => {
        setState({ on: true, label: label || '' });
        setTimeout(() => setState((s) => ({ ...s, on: false })), 420);
      };
      return () => {delete window.sysFlash;};
    }, []);
    if (!state.on) return null;
    return (
      <div className="sysflash">
        <pre className="sysflash__art">{ASCII_FRAME.join('\n')}</pre>
        <div className="sysflash__label">LOADING {state.label}</div>
      </div>);

  }

  // ============================================================
  // COMMAND LINE — type to navigate; hidden secret routes
  // ============================================================
  function CommandLine({ onRoute, onToggleSfx, email }) {
    const { t, lang } = useLang();
    const en = lang === 'en';
    const [open, setOpen] = useState(false);
    const [value, setValue] = useState('');
    const [lines, setLines] = useState([]);
    const inputRef = useRef(null);

    const print = useCallback((txt, cls) => {
      setLines((prev) => [...prev.slice(-5), { txt, cls, id: Math.random() }]);
    }, []);

    const exec = useCallback((raw) => {
      const cmd = raw.trim().toLowerCase();
      if (!cmd) return;
      switch (cmd) {
        case 'help':case '?':
          print(t('cmd_help'), 'dim');
          break;
        case 'about':case '1':onRoute('about');print(en ? '> opening SYS.ABOUT' : '> otwieram SYS.ABOUT', 'ok');break;
        case 'portfolio':case '2':onRoute('portfolio');print(en ? '> opening SYS.PORTFOLIO' : '> otwieram SYS.PORTFOLIO', 'ok');break;
        case 'contact':case '3':onRoute('contact');print(en ? '> opening SYS.CONTACT' : '> otwieram SYS.CONTACT', 'ok');break;
        case 'story':case '4':case '2226':onRoute('story');print('> opening SYS.STORY // 2226', 'ok');break;
        case 'menu':case 'home':onRoute('menu');print(en ? '> back to menu' : '> powrót do menu', 'ok');break;
        case 'whoami':
          print(t('cmd_whoami'), 'dim');break;
        case 'mail':case 'email':
          print(email, 'ok');break;
        case 'insta':case 'instagram':
          print(en ? '> redirecting → instagram' : '> przekierowanie → instagram', 'ok');
          if (window.Audio && window.Audio.isEnabled()) window.Audio.woosh();
          setTimeout(() => window.open('https://www.instagram.com/dysbalans.studio/', '_blank', 'noopener,noreferrer'), 500);
          break;
        case 'behance':
          print(en ? '> redirecting → behance' : '> przekierowanie → behance', 'ok');
          if (window.Audio && window.Audio.isEnabled()) window.Audio.woosh();
          setTimeout(() => window.open('https://www.behance.net/dysbalans', '_blank', 'noopener,noreferrer'), 500);
          break;
        case 'sfx':onToggleSfx();print(en ? '> toggling audio channel' : '> przełączam kanał audio', 'ok');break;
        case 'clear':case 'cls':setLines([]);break;
        // ── secret ──
        case 'saper':case 'mines':case 'minesweeper':
          print(en ? '> loading SYS.MINEFIELD … clear the field. (RMB = flag)'
                   : '> ładowanie SYS.MINEFIELD … rozbrój pole. (PPM = flaga)', 'secret');
          if (window.Audio && window.Audio.isEnabled()) window.Audio.blip(880);
          setTimeout(() => window.dispatchEvent(new Event('dys:open-saper')), 250);
          break;
        case 'fontripper':case 'ripper':case 'fonts':
          print(en ? '> operator authorization … OK. redirecting to fontripper…'
                   : '> autoryzacja operatora … OK. przekierowanie do fontripper…', 'secret');
          if (window.Audio && window.Audio.isEnabled()) window.Audio.woosh();
          setTimeout(() => {window.location.href = 'fontripper/index.html';}, 750);
          break;
        case 'donttouchme':case 'freebies':
          print(en ? '> told you not to touch. well — freebies. redirecting…'
                   : '> mówiłem żeby nie dotykać. otóż — freebies. przekierowanie…', 'secret');
          if (window.Audio && window.Audio.isEnabled()) window.Audio.woosh();
          setTimeout(() => {window.location.href = 'freebies/index.html';}, 750);
          break;
        case 'exit':case 'q':case 'konsola':case 'console':setOpen(false);break;
        // ── secret: MU/TH/UR 6000 (Nostromo) ──
        case 'nostromo':case 'mother':case 'mother?':case 'muthur':case 'mu/th/ur':{
          const on = document.documentElement.classList.toggle('muthur');
          try { localStorage.setItem('dys_muthur', on ? '1' : '0'); } catch {}
          if (on) {
            print(en ? '> MU/TH/UR 6000 online. interface re-routed.'
                     : '> MU/TH/UR 6000 online. interfejs przekierowany.', 'secret');
          } else {
            print(en ? '> MU/TH/UR offline. interface restored.'
                     : '> MU/TH/UR offline. interfejs przywrócony.', 'secret');
          }
          if (window.Audio && window.Audio.isEnabled()) window.Audio.blip(on ? 1320 : 440);
          break;
        }
        default:
          print(`${t('cmd_unknown')}: ${cmd} ${t('cmd_unknown_tail')}`, 'err');
      }
    }, [onRoute, onToggleSfx, email, print, t, en]);

    // expose open-state so the global Esc handler (app.jsx) yields to us
    useEffect(() => { window.__consoleOpen = open; }, [open]);

    // tappable toggle (the "KONSOLA" chip in the nav prompt — primary entry on
    // touch devices where there's no "~" key). Opening also raises the keyboard.
    useEffect(() => {
      function onToggle() {
        setOpen((o) => {
          const next = !o;
          if (next) setTimeout(() => inputRef.current && inputRef.current.focus(), 30);
          else setValue('');
          if (window.Audio && window.Audio.isEnabled()) window.Audio.blip(next ? 660 : 330);
          return next;
        });
      }
      window.addEventListener('dys:toggle-console', onToggle);
      return () => window.removeEventListener('dys:toggle-console', onToggle);
    }, []);

    useEffect(() => {
      function onKey(e) {
        // open with "~" (primary), "/" or ":" — ignore when typing in a field
        const typing = document.activeElement &&
        ['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName);
        if (!open && !typing && (e.key === '~' || e.key === '`' || e.key === '/' || e.key === ':')) {
          e.preventDefault();
          setOpen(true);
          setTimeout(() => inputRef.current && inputRef.current.focus(), 30);
        } else if (open && e.key === 'Escape') {
          e.preventDefault();
          setOpen(false);
          setValue('');
        }
      }
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [open]);

    function submit(e) {
      e.preventDefault();
      exec(value);
      setValue('');
    }

    return (
      <div className={`cmdline ${open ? 'is-open' : ''}`}>
        <div className="cmdline__hint">
          <span className="cmdline__hint-title">{t('cmd_hint_title')}</span>
          <div className="cmdline__hint-grid">
            <span><b>about</b> — {en ? 'about me' : 'o mnie'}</span>
            <span><b>portfolio</b> — {en ? 'archive' : 'archiwum'}</span>
            <span><b>contact</b> — {en ? 'contact' : 'kontakt'}</span>
            <span><b>story</b> — 2226</span>
            <span><b>insta</b> — instagram</span>
            <span><b>behance</b> — behance</span>
            <span><b>whoami</b> — {en ? 'who am i' : 'kim jestem'}</span>
            <span><b>home</b> — {en ? 'main menu' : 'menu główne'}</span>
            <span><b>sfx</b> — {en ? 'sound on/off' : 'dźwięk on/off'}</span>
            <span><b>clear</b> — {en ? 'clear' : 'wyczyść'}</span>
            <span className="cmdline__hint-secret is-full"><b>saper</b> — {en ? 'minefield' : 'pole minowe'}</span>
            <span className="cmdline__hint-secret is-left"><b>donttouchme</b> — {en ? "don't touch" : 'nie dotykaj'}</span>
            <span className="cmdline__hint-secret is-right"><b>mother</b> — {en ? 'wake her' : 'obudź ją'}</span>
          </div>
        </div>
        <div className="cmdline__log">
          {lines.map((l) =>
          <div key={l.id} className={`cmdline__out cmdline__out--${l.cls || 'dim'}`}>{l.txt}</div>
          )}
        </div>
        <form className="cmdline__row" onSubmit={submit}>
          <span className="cmdline__prompt">ÐYS:~$</span>
          <span className="cmdline__inputwrap">
            <input
              ref={inputRef}
              className="cmdline__input"
              value={value}
              onChange={(e) => setValue(e.target.value)}
              spellCheck="false"
              autoComplete="off"
              placeholder={t('cmd_placeholder')} />
            
          </span>
        </form>
      </div>);

  }

  // ============================================================
  // IDLE OVERLAY — after inactivity: dim, eagle crest, console cursor
  // ============================================================
  function IdleOverlay({ timeout = 30000 }) {
    const { t } = useLang();
    const [idle, setIdle] = useState(false);
    const idleRef = useRef(false);
    const eagleRef = useRef(null);

    // inject the eagle SVG once so the eye element can pulse via CSS
    useEffect(() => {
      let cancelled = false;
      const host = eagleRef.current;
      if (!host) return;
      if (window.__eagleSvg) {host.innerHTML = window.__eagleSvg;return;}
      fetch(window.__res('eagle', 'assets/logo/eagle-idle.svg')).
      then((r) => r.text()).
      then((txt) => {if (!cancelled) {window.__eagleSvg = txt;host.innerHTML = txt;}}).
      catch(() => {});
      return () => {cancelled = true;};
    }, []);

    useEffect(() => {
      let timer = null;
      function wake() {
        if (idleRef.current) {idleRef.current = false;setIdle(false);}
        clearTimeout(timer);
        timer = setTimeout(() => {idleRef.current = true;setIdle(true);}, timeout);
      }
      const events = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'wheel'];
      events.forEach((ev) => window.addEventListener(ev, wake, { passive: true }));
      wake();
      return () => {
        clearTimeout(timer);
        events.forEach((ev) => window.removeEventListener(ev, wake));
      };
    }, [timeout]);

    return (
      <div className={`idle ${idle ? 'is-idle' : ''}`} aria-hidden={!idle}>
        <div className="idle__eagle" ref={eagleRef}></div>
        <div className="idle__msg">
          {t('idle_press')}
          <span className="prompt__cursor"></span>
        </div>
      </div>);

  }

  // ============================================================
  // LIVE READOUTS — drifting status numbers (#8)
  // ============================================================
  function LiveReadouts() {
    const [vals, setVals] = useState({ sig: 98, core: 41.2, up: 0 });
    const startRef = useRef(Date.now());
    useEffect(() => {
      const t = setInterval(() => {
        setVals((v) => ({
          sig: Math.max(72, Math.min(100, v.sig + (Math.random() * 6 - 3))),
          core: Math.max(36, Math.min(52, v.core + (Math.random() * 1.4 - 0.7))),
          up: Math.floor((Date.now() - startRef.current) / 1000)
        }));
      }, 1400);
      return () => clearInterval(t);
    }, []);
    const upm = String(Math.floor(vals.up / 60)).padStart(2, '0');
    const ups = String(vals.up % 60).padStart(2, '0');
    return (
      <div className="readouts">
        <span className="readouts__item"><b>SIG</b>{Math.round(vals.sig)}%</span>
        <span className="readouts__item"><b>CORE</b>{vals.core.toFixed(1)}°</span>
        <span className="readouts__item"><b>UP</b>{upm}:{ups}</span>
      </div>);

  }

  Object.assign(window, {
    ScrambleText, RevealText, BootLog, SysFlash, CommandLine, IdleOverlay, LiveReadouts
  });
})();