// Hisui//OS — services, terminal, hero, app composition

const { useState: uS, useEffect: uE, useRef: uR, useCallback: uC, useMemo: uM } = React;

// ───── Services list ─────
function ServicesList({ onHover }) {
  const [tick, setTick] = uS(0);
  uE(() => {
    const t = setInterval(() => setTick(x => x + 1), 4000);
    return () => clearInterval(t);
  }, []);
  return (
    <div>
      {SERVICES.map(s => (
        <div key={s.name} className={`svc ${s.status === 'warn' ? 'warn' : ''}`}
             onMouseEnter={() => onHover && onHover(s.name)}>
          <span className="led" />
          <span className="name">{s.name}</span>
          <span className="uptime">{s.uptime}</span>
        </div>
      ))}
    </div>
  );
}

// ───── Metrics ─────
function Metrics() {
  const [bars, setBars] = uS({
    req: Array.from({ length: 14 }, () => Math.random()),
    cpu: Array.from({ length: 14 }, () => Math.random() * 0.6),
    mem: Array.from({ length: 14 }, () => 0.4 + Math.random() * 0.3),
  });
  uE(() => {
    const t = setInterval(() => {
      setBars(b => ({
        req: [...b.req.slice(1), Math.random()],
        cpu: [...b.cpu.slice(1), Math.random() * 0.7],
        mem: [...b.mem.slice(1), 0.4 + Math.random() * 0.35],
      }));
    }, 1200);
    return () => clearInterval(t);
  }, []);
  const row = (label, vals, unit, fmt) => (
    <div className="metric-row">
      <span className="label">{label}</span>
      <span className="bars">
        {vals.map((v, i) => (
          <span key={i} className={i === vals.length - 1 ? 'hot' : ''} style={{ height: `${Math.max(8, v * 100)}%` }} />
        ))}
      </span>
      <span className="val">{fmt(vals[vals.length - 1])}{unit}</span>
    </div>
  );
  return (
    <div>
      {row('req/s', bars.req, '', v => Math.round(v * 480))}
      {row('cpu', bars.cpu, '%', v => Math.round(v * 100))}
      {row('mem', bars.mem, '%', v => Math.round(v * 100))}
      <div className="mono faint" style={{ fontSize: 10, marginTop: 6 }}>
        uptime · 41d 02h · load 0.42
      </div>
    </div>
  );
}

// ───── Terminal ─────
function Terminal({ onCommand, projectMap, onTail }) {
  const [lines, setLines] = uS(() => seedLines());
  const [input, setInput] = uS('');
  const [history, setHistory] = uS([]);
  const [histIdx, setHistIdx] = uS(-1);
  const logRef = uR(null);

  function seedLines() {
    return [
      { kind: 'info', svc: 'iis',         msg: 'app pool warm-up complete' },
      { kind: 'ok',   svc: 'tfs-bridge',  msg: 'pulled changeset 49824' },
      { kind: 'ok',   svc: 'auth-gateway',msg: '200 POST /token issued (sub=svc-runner)' },
      { kind: 'ok',   svc: 'ph-riot-api', msg: '200 GET /summoner/Hisui (84ms)' },
      { kind: 'info', svc: 'ccwss-core',  msg: 'ws connected · peer=147' },
    ].map((l, i) => ({ ...l, ts: nowStamp(), id: 'seed-' + i }));
  }

  // Ambient log feed
  uE(() => {
    let alive = true;
    function tickOnce() {
      if (!alive) return;
      const tpl = LOG_TEMPLATES[Math.floor(Math.random() * LOG_TEMPLATES.length)];
      setLines(l => {
        const next = [...l, { id: 'a' + Math.random(), kind: tpl.kind, svc: tpl.svc, msg: tpl.fmt(), ts: nowStamp() }];
        return next.slice(-200);
      });
      window.hisuiPulse && window.hisuiPulse(tpl.kind);
      const delay = 800 + Math.random() * 2200;
      setTimeout(tickOnce, delay);
    }
    const t = setTimeout(tickOnce, 1500);
    return () => { alive = false; clearTimeout(t); };
  }, []);

  uE(() => {
    if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
    if (onTail && lines.length) onTail(lines[lines.length - 1]);
  }, [lines]);

  const push = (kind, msg) => {
    setLines(l => [...l, { id: 'u' + Math.random(), kind, svc: '', msg, ts: nowStamp() }].slice(-200));
  };

  function execute(raw) {
    const cmd = raw.trim();
    if (!cmd) return;
    setHistory(h => [cmd, ...h].slice(0, 30));
    setHistIdx(-1);
    setLines(l => [...l, { id: 'cmd' + Math.random(), kind: 'user', svc: '', msg: '$ ' + cmd, ts: nowStamp() }]);

    const [head, ...rest] = cmd.split(/\s+/);
    const arg = rest.join(' ');

    switch (head) {
      case 'help':
        Object.entries(COMMANDS).forEach(([k, v]) => push('echo', `  ${k.padEnd(10)} ${v}`));
        break;
      case 'ls':
        push('echo', PROJECTS.map(p => {
          const star = p.swaggerUrl || p.swagger ? '*' : '';
          return (p.slug || p.id) + star;
        }).join('   '));
        push('muted', '* swagger portal available · run `swagger <project>`');
        break;
      case 'about':
        const af = ABOUT_FILES[arg] || Object.values(ABOUT_FILES).find(f => f.file === arg);
        if (!arg) {
          push('echo', 'about files:');
          Object.values(ABOUT_FILES).forEach(f => push('echo', `  ${f.file}`));
          push('muted', 'usage: about <bio|resume|contact>');
        } else if (af) {
          onCommand && onCommand({ type: 'about', id: af.id });
          push('ok', `opening about/${af.file} →`);
        } else push('err', `no about file "${arg}"`);
        break;
      case 'cat':
        const sn = SNIPPETS[arg] || Object.values(SNIPPETS).find(s => s.file === arg);
        if (sn) {
          onCommand && onCommand({ type: 'snippet', id: sn.id });
          push('ok', `opening snippets/${sn.file} →`);
          break;
        }
        const cp = PROJECTS.find(p => (p.slug || p.id) === arg);
        if (cp) {
          push('echo', `# ${cp.title || cp.name || cp.slug}`);
          push('echo', cp.description || cp.blurb || '');
          const tags = (cp.tags || []).map(t => t.label || t).join(' · ') || (cp.stack || []).join(' · ');
          push('echo', `tags: ${tags}  exposure: ${cp.exposure}`);
        } else push('err', `no readme for "${arg}"`);
        break;
      case 'open':
        const op = PROJECTS.find(p => (p.slug || p.id) === arg);
        if (op) {
          const slug = op.slug || op.id;
          onCommand && onCommand({ type: 'open', id: slug });
          push('ok', `loading ${slug} in detail pane →`);
        }
        else push('err', `no project "${arg}"`);
        break;
      case 'services':
        SERVICES.forEach(s => push('echo', `  ${s.status === 'warn' ? '⚠' : '●'} ${s.name.padEnd(16)} ${s.uptime}`));
        break;
      case 'whoami':
        const wu = window.PH_USER;
        if (wu && wu.email) {
          push('echo', `uid=1(${(wu.name || wu.email.split('@')[0])}) gid=1 groups=${(wu.roles||[]).join(',') || '— none —'}`);
          push('echo', `email=${wu.email}  admin=${wu.isAdmin ? 'true' : 'false'}`);
        } else {
          push('echo', 'uid=0(guest) gid=0 groups=anonymous · read-only');
          push('muted', 'tip: type `login` to sign in');
        }
        break;
      case 'uptime':
        push('echo', `${nowStamp()} up 41 days · load 0.42 0.51 0.48 · 1 user (justin)`);
        break;
      case 'contact':
        push('echo', 'mail   · justin@projecthisui.com');
        push('echo', 'tfs    · ph/justin');
        push('echo', 'github · github.com/projecthisui  (mirror, read-only)');
        break;
      case 'admin':
        if (window.PH_USER && window.PH_USER.isAdmin) {
          push('ok', 'opening /admin · users · roles · projects');
          setTimeout(() => { window.location.href = window.PH_ADMIN_URL || '/Admin'; }, 350);
        } else {
          push('err', '403 forbidden · admin-only · ask a super-admin');
        }
        break;
      case 'clear':
        setLines([]);
        break;
      case 'login':
        push('ok', 'redirecting to /login ...');
        setTimeout(() => { window.location.href = '/Login'; }, 350);
        break;
      case 'logout':
        push('warn', 'signing out · clearing session cookie');
        setTimeout(() => {
          // POST to /Logout via a hidden form so the antiforgery token rides along.
          const f = document.createElement('form');
          f.method = 'POST'; f.action = '/Logout';
          const tokenInput = document.querySelector('input[name="__RequestVerificationToken"]');
          if (tokenInput) { const c = tokenInput.cloneNode(true); f.appendChild(c); }
          document.body.appendChild(f); f.submit();
        }, 350);
        break;
      case 'account':
        push('ok', 'opening /account ...');
        setTimeout(() => { window.location.href = '/Account'; }, 350);
        break;
      case 'matrix':
        push('ok', 'wake up, neo...');
        window.hisuiPulse && window.hisuiPulse('ok');
        break;
      case 'swagger':
        const sp = PROJECTS.find(p => (p.slug || p.id) === arg);
        const sroles = (window.PH_USER && window.PH_USER.roles) || [];
        const swaggerOf = (p) => p.swaggerUrl || (p.swagger && p.swagger.url);
        const reqRoleOf = (p) => p.swaggerRequiredRole || (p.swagger && p.swagger.requiresRole);
        if (!arg) {
          push('echo', 'projects with swagger portals:');
          PROJECTS.filter(swaggerOf).forEach(p => {
            const req = reqRoleOf(p);
            const lockGlyph = (req && !sroles.includes(req) && !sroles.includes('super-admin')) ? '🔒' : '  ';
            const slug = p.slug || p.id;
            push('echo', `  ${lockGlyph} ${slug.padEnd(16)} ${swaggerOf(p)}`);
          });
          push('muted', 'usage: swagger <project>');
        } else if (!sp) {
          push('err', `no project "${arg}"`);
        } else if (!swaggerOf(sp)) {
          push('err', `${arg} has no swagger portal`);
        } else {
          const req = reqRoleOf(sp);
          const allowed = !req || sroles.includes(req) || sroles.includes('super-admin');
          const slug = sp.slug || sp.id;
          if (!allowed) {
            push('err', `401 unauthorized · ${slug}`);
            push('warn', `  auth required · role: ${req}`);
            push('muted', `  you have: ${sroles.length ? sroles.join(' · ') : '— none —'}`);
            push('muted', `  ask an admin to grant '${req}' (open /Admin)`);
            onCommand && onCommand({ type: 'swagger', id: slug });
          } else {
            push('ok', `opening swagger portal · ${swaggerOf(sp)}`);
            push('info', `  ph_jwt cookie validated · role=${req || 'n/a'}`);
            onCommand && onCommand({ type: 'swagger', id: slug });
          }
        }
        break;
      default:
        push('err', `command not found: ${head} · try 'help'`);
    }
  }

  function onKey(e) {
    if (e.key === 'Enter') { execute(input); setInput(''); }
    else if (e.key === 'ArrowUp') {
      e.preventDefault();
      const ni = Math.min(history.length - 1, histIdx + 1);
      if (history[ni] !== undefined) { setHistIdx(ni); setInput(history[ni]); }
    } else if (e.key === 'ArrowDown') {
      e.preventDefault();
      const ni = histIdx - 1;
      if (ni < 0) { setHistIdx(-1); setInput(''); }
      else { setHistIdx(ni); setInput(history[ni]); }
    }
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <div className="term-log" ref={logRef}>
        {lines.map(l => (
          <div key={l.id} className={`term-line ${l.kind}`}>
            <span className="ts">{l.ts}</span>{' '}
            {l.svc ? <span className="svc">[{l.svc}]</span> : null}{l.svc ? ' ' : ''}
            <span className={l.kind === 'echo' || l.kind === 'user' ? '' : l.kind}>{l.msg}</span>
          </div>
        ))}
      </div>
      <div className="term-input-row">
        <span className="term-prompt">justin@hisui ~$</span>
        <input
          className="term-input"
          autoFocus
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyDown={onKey}
          spellCheck={false}
          placeholder="try: help · ls · open ph-riot-api · services · matrix"
        />
      </div>
    </div>
  );
}

// ───── Hero (legacy · unused; FocusPane → BioReadme replaces it) ─────
function Hero() {
  return (
    <div className="hero">
      <div className="greeting">// $ whoami</div>
      <div className="name">Justin Davis <span className="alias">// hisui</span></div>
      <div className="role">backend engineer · iis · .net · sql · tfs</div>
      <div className="blurb">
        I build the services that stay quiet. Plumbing, gateways, schedulers — the kind of code that wakes you up only when it doesn't.
        Most of these run on my own iron behind IIS; some make it out into the world.
      </div>
    </div>
  );
}

// ───── Konami ─────
function useKonami(onTrigger) {
  uE(() => {
    const seq = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];
    let pos = 0;
    function onKey(e) {
      const k = e.key.length === 1 ? e.key.toLowerCase() : e.key;
      if (k === seq[pos]) { pos++; if (pos === seq.length) { pos = 0; onTrigger(); } }
      else { pos = (k === seq[0]) ? 1 : 0; }
    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onTrigger]);
}

// ───── App ─────
function App() {
  const [phase, setPhase] = uS('boot');
  // Unified selection: {kind: 'about'|'project'|'snippet', id: string}.
  // Initial focus = about/bio.md, unless ?slug=foo in the URL.
  const [selected, setSelected] = uS(() => {
    try {
      const params = new URLSearchParams(window.location.search);
      const slug = params.get('slug');
      if (slug) return { kind: 'project', id: slug };
    } catch (_) {}
    return { kind: 'about', id: 'bio' };
  });
  const selectItem = (kind, id) => setSelected({ kind, id });
  const [konami, setKonami] = uS(false);
  const [time, setTime] = uS(() => nowStamp());
  const [swaggerProject, setSwaggerProject] = uS(null);
  const [lastLog, setLastLog] = uS(null);
  const [, forceTick] = uS(0);

  // Hydrate projects from /api/projects on mount; fall back to the seed.
  uE(() => {
    if (typeof loadProjectsFromApi === 'function') {
      loadProjectsFromApi().then(ok => { if (ok) forceTick(t => t + 1); });
    }
  }, []);

  uE(() => {
    const t = setInterval(() => setTime(nowStamp()), 1000);
    return () => clearInterval(t);
  }, []);

  // Keyboard shortcuts: ⌘A → /Account, ⌘. → /Admin (if admin)
  uE(() => {
    function onKey(e) {
      const mod = e.metaKey || e.ctrlKey;
      if (!mod) return;
      const tag = (document.activeElement && document.activeElement.tagName) || '';
      if (tag === 'INPUT' || tag === 'TEXTAREA') return;
      if (e.key === 'a' || e.key === 'A') {
        e.preventDefault();
        window.location.href = window.PH_ACCOUNT_URL || '/Account';
      } else if (e.key === '.') {
        const u = window.PH_USER;
        if (u && u.isAdmin) { e.preventDefault(); window.location.href = window.PH_ADMIN_URL || '/Admin'; }
      }
    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  useKonami(() => {
    setKonami(true);
    for (let i = 0; i < 12; i++) setTimeout(() => window.hisuiPulse('ok'), i * 80);
    setTimeout(() => setKonami(false), 2400);
  });

  const servicesOk = SERVICES.filter(s => s.status === 'ok').length;

  return (
    <>
      {phase === 'boot' && <Boot onDone={() => setPhase('desktop')} durationMs={4000} />}
      <div className={`desktop ${phase === 'desktop' ? 'ready' : ''}`}>
        <TopBar time={time} />
        <div className="body">
          <Tile id="explorer" title="explorer" style={{ gridColumn: 1, gridRow: 1, height: '100%' }}>
            <FileTree selected={selected} onSelect={selectItem} />
          </Tile>

          <div style={{ gridColumn: 2, gridRow: 1, display: 'flex', flexDirection: 'column', gap: 12, minHeight: 0, height: '100%' }}>
            <Tile
              id="focus"
              title={focusTitle(selected)}
              defaultOpen={true}
              style={{ flex: 1, minHeight: 0 }}
              headerExtra={
                selected && selected.kind !== 'about' ? (
                  <span
                    className="focus-home"
                    onClick={(e) => { e.stopPropagation(); selectItem('about', 'bio'); }}
                    title="back to ~/about/bio.md"
                  >← ~/</span>
                ) : null
              }
            >
              <FocusPane selected={selected} onSwagger={(p) => setSwaggerProject(p)} onSelect={selectItem} />
            </Tile>
          </div>

          <div style={{ gridColumn: 3, gridRow: 1, display: 'flex', flexDirection: 'column', gap: 12, minHeight: 0, height: '100%' }}>
            <Tile id="services" title="services">
              <ServicesList />
            </Tile>
            <Tile id="metrics" title="metrics">
              <Metrics />
            </Tile>
            <Tile id="skills" title="skills">
              <div className="skill-cloud">
                {SKILLS.map(s => <span key={s} className="skill">{s}</span>)}
              </div>
            </Tile>
          </div>

          <div className="tile term-tile" style={{ gridColumn: '1 / -1', gridRow: 2 }}>
            <div className="tile-head">
              <span className="dot" style={{ background: 'var(--teal-bright)', boxShadow: '0 0 6px var(--teal-bright)' }} />
              <span>events · live tail · interactive</span>
              <span className="actions"><span>▾</span></span>
            </div>
            <div className="tile-body">
              <Terminal onTail={setLastLog} onCommand={(c) => {
                if (c.type === 'open')    selectItem('project', c.id);
                if (c.type === 'snippet') selectItem('snippet', c.id);
                if (c.type === 'about')   selectItem('about', c.id);
                if (c.type === 'swagger') {
                  const p = PROJECTS.find(x => (x.slug || x.id) === c.id);
                  if (p) setSwaggerProject(p);
                }
              }} />
            </div>
          </div>
        </div>
        <StatusBar time={time} lastLog={lastLog} servicesOk={servicesOk} servicesTotal={SERVICES.length} />
      </div>

      <div className="vignette" />
      <div className="scanlines" />

      <div className={`konami ${konami ? 'on' : ''}`}>
        <div className="konami-msg">↑ ↑ ↓ ↓ ← → ← → B A</div>
      </div>

      {swaggerProject && (
        <SwaggerPortal
          project={swaggerProject}
          onClose={() => setSwaggerProject(null)}
        />
      )}
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
