import React, { useCallback, useEffect, useRef, useState } from 'react';
import { api } from './api.js';

// Native web terminal — polls the instance's tui frames and (for the owner,
// who holds the accesskey) sends keystrokes through amerc's input queue. No
// relay, no iframe: just HTTP. The broker writes keystrokes to its pty and
// pushes tui frames back via POST /agents/instances/:id/tui.

const stripAnsi = (s) => String(s || '')
  .replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '')   // CSI sequences
  .replace(/\x1b[()][0-9A-Za-z]/g, '')          // charset selects
  .replace(/\x1b[=>]/g, '').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');

function keyToData(e) {
  if (e.ctrlKey && /^[a-z]$/i.test(e.key)) { const c = e.key.toLowerCase().charCodeAt(0) - 96; return String.fromCharCode(c); }
  switch (e.key) {
    case 'Enter': return '\r';
    case 'Backspace': return '\x7f';
    case 'Tab': return '\t';
    case 'Escape': return '\x1b';
    case 'ArrowUp': return '\x1b[A';
    case 'ArrowDown': return '\x1b[B';
    case 'ArrowRight': return '\x1b[C';
    case 'ArrowLeft': return '\x1b[D';
    case 'Home': return '\x1b[H';
    case 'End': return '\x1b[F';
    case 'Delete': return '\x1b[3~';
    default: return e.key.length === 1 ? e.key : null;
  }
}

export default function PtyPanel({ instance, accesskey }) {
  const [tui, setTui] = useState(instance.tui || '');
  const [status, setStatus] = useState(instance.status || '');
  const [focused, setFocused] = useState(false);
  const [err, setErr] = useState('');
  const screenRef = useRef(null);
  const interactive = !!accesskey;

  // poll tui frames
  useEffect(() => {
    let alive = true;
    const q = accesskey ? `?accesskey=${encodeURIComponent(accesskey)}` : '';
    const tick = () => api(`/agents/instances/${instance.id}${q}`)
      .then((d) => { if (alive && d.instance) { if (d.tui != null) setTui(d.tui); setStatus(d.instance.status); } })
      .catch(() => {});
    tick(); const t = setInterval(tick, 1200);
    return () => { alive = false; clearInterval(t); };
  }, [instance.id, accesskey]);

  // keep scrolled to the bottom
  useEffect(() => { const el = screenRef.current; if (el) el.scrollTop = el.scrollHeight; }, [tui]);

  const send = useCallback(async (data) => {
    if (!interactive) return;
    try { await api(`/agents/instances/${instance.id}/input`, { method: 'POST', body: { data, accesskey } }); }
    catch (e) { setErr(e.message); }
  }, [instance.id, accesskey, interactive]);

  const onKeyDown = (e) => {
    if (!interactive) return;
    if (e.metaKey || (e.ctrlKey && (e.key === 'c' || e.key === 'v') && !focused)) return; // allow copy/paste chrome
    const d = keyToData(e);
    if (d != null) { e.preventDefault(); send(d); }
  };
  const onPaste = (e) => { if (!interactive) return; const t = e.clipboardData?.getData('text'); if (t) { e.preventDefault(); send(t); } };

  return (
    <div className="pty">
      <div className="pty-bar">
        <span className={`ch-dot ${status}`} />
        <span className="pty-title">terminal · #{instance.id}</span>
        <span className="pty-mode">{interactive ? (focused ? '⌨ typing — keys go to the agent' : 'click the screen to type') : '👁 view only (owner drives)'}</span>
      </div>
      <pre
        ref={screenRef}
        className={`pty-screen${focused ? ' on' : ''}${interactive ? '' : ' ro'}`}
        tabIndex={interactive ? 0 : -1}
        onKeyDown={onKeyDown}
        onPaste={onPaste}
        onFocus={() => setFocused(true)}
        onBlur={() => setFocused(false)}
      >{stripAnsi(tui) || (status === 'online' ? 'waiting for the agent to paint its terminal…' : `instance is ${status} — no live terminal`)}</pre>
      {err && <p className="px-auth-err">{err}</p>}
      {interactive && <p className="ap-hint pty-hint">Keystrokes (incl. Enter, arrows, Ctrl-C) stream to the agent's pty; the screen refreshes ~1×/sec. Your broker must long-poll <code>GET /api/agents/instances/{instance.id}/input</code> and push frames to <code>POST …/tui</code>.</p>}
    </div>
  );
}
