// drover-shared.jsx — shared hooks, state machine, types for all Drover-Go variants.
// Each variant imports these via window globals and renders them in its own visual language.

// ─── Test catalog ──────────────────────────────────────────────────────────
const ALL_TESTS = [
  { id: 'tcp',      label: 'TCP reachability',         desc: 'TCP-соединение до прокси установлено' },
  { id: 'greet',    label: 'SOCKS5 greeting',          desc: 'Прокси отвечает по протоколу SOCKS5' },
  { id: 'auth',     label: 'SOCKS5 authentication',    desc: 'Аутентификация по логину/паролю принята', authOnly: true },
  { id: 'connect',  label: 'TCP CONNECT to Discord',   desc: 'Прокси проксирует TCP к gateway' },
  { id: 'udp',      label: 'UDP ASSOCIATE',            desc: 'Прокси выдал UDP-релей' },
  { id: 'stun',     label: 'UDP round-trip via STUN',  desc: 'UDP-пакеты ходят туда-обратно' },
  { id: 'api',      label: 'Discord API reachable',    desc: 'HTTPS до discord.com через прокси' },
];

// Pre-baked scenarios so the prototype feels alive. Each entry per test:
//   { result: 'passed'|'failed'|'skipped', metric: '12 ms' | 'ok' | …, error?: 'short msg', hint?: 'what to try' }
const SCENARIOS = {
  // Default happy path (no auth)
  happy: {
    tcp:     { result: 'passed', metric: '14 ms' },
    greet:   { result: 'passed', metric: 'SOCKS5/0x05' },
    connect: { result: 'passed', metric: 'gateway.discord.gg' },
    udp:     { result: 'passed', metric: 'relay 95.165.72.59:54321' },
    stun:    { result: 'passed', metric: '38 ms RTT' },
    api:     { result: 'passed', metric: '204 OK · 89 ms' },
  },
  // With auth
  happyAuth: {
    tcp:     { result: 'passed', metric: '14 ms' },
    greet:   { result: 'passed', metric: 'SOCKS5/0x05' },
    auth:    { result: 'passed', metric: 'user/pass · ok' },
    connect: { result: 'passed', metric: 'gateway.discord.gg' },
    udp:     { result: 'passed', metric: 'relay 95.165.72.59:54321' },
    stun:    { result: 'passed', metric: '38 ms RTT' },
    api:     { result: 'passed', metric: '204 OK · 89 ms' },
  },
  // UDP fails — common Discord scenario
  udpFail: {
    tcp:     { result: 'passed', metric: '17 ms' },
    greet:   { result: 'passed', metric: 'SOCKS5/0x05' },
    connect: { result: 'passed', metric: 'gateway.discord.gg' },
    udp:     { result: 'failed', metric: 'X\'07 cmd not supported',
               error: 'Прокси не поддерживает UDP ASSOCIATE.',
               hint:  'Голос и демонстрация экрана работать не будут. Текст и API — будут. Попробуйте другой SOCKS5-сервер с поддержкой UDP.' },
    stun:    { result: 'skipped', metric: 'требует UDP ASSOCIATE' },
    api:     { result: 'passed', metric: '204 OK · 92 ms' },
  },
};

function getTests(authEnabled) {
  return ALL_TESTS.filter(t => !t.authOnly || authEnabled);
}

// ─── Drover state hook ─────────────────────────────────────────────────────
// Owns: form values, diagnostic phase, per-test results, drover-active state,
// live stats counter, log buffer.
// phase: 'idle' | 'checking' | 'checked' | 'active'
function useDrover(initial = {}) {
  const [form, setForm] = React.useState({
    host: '95.165.72.59',
    port: '12334',
    auth: false,
    login: '',
    password: '',
    ...initial,
  });
  const [phase, setPhase]       = React.useState('idle');
  const [results, setResults]   = React.useState({});  // testId -> {result, metric, error, hint, expanded}
  const [running, setRunning]   = React.useState(null); // currently-running test id
  const [scenario, setScenario] = React.useState('happy'); // for tweaks
  const [stats, setStats]       = React.useState({ up: 0, down: 0, tcp: 0, udp: 0, uptimeS: 0 });
  const [logs, setLogs]         = React.useState(() => seedLogs());
  const [logsOpen, setLogsOpen] = React.useState(false);
  const tests = getTests(form.auth);
  const lastSummary = React.useMemo(() => {
    if (phase !== 'checked' && phase !== 'active') return null;
    const ids = tests.map(t => t.id);
    const failed = ids.filter(id => results[id]?.result === 'failed').length;
    return { total: ids.length, failed };
  }, [phase, results, tests]);

  // ── actions ────────────────────────────────────────────────────────────
  function update(patch) { setForm(f => ({ ...f, ...patch })); }

  function pushLog(level, msg) {
    setLogs(l => [...l.slice(-499), { t: Date.now(), level, msg }]);
  }

  async function runCheck() {
    if (phase === 'checking') return;
    setPhase('checking');
    setResults({});
    setRunning(null);
    pushLog('INFO', `connect ${form.host}:${form.port}${form.auth ? ' (auth)' : ''}`);
    const list = getTests(form.auth);
    const sc = form.auth && scenario === 'happy' ? SCENARIOS.happyAuth : SCENARIOS[scenario];
    for (const t of list) {
      setRunning(t.id);
      // shorter when scenario is failing past skipped tests
      const r = sc[t.id];
      if (r?.result === 'skipped') {
        await sleep(120);
      } else {
        await sleep(380 + Math.random()*220);
      }
      setResults(prev => ({ ...prev, [t.id]: { ...r, expanded: r?.result === 'failed' } }));
      pushLog(r?.result === 'failed' ? 'ERROR' : r?.result === 'skipped' ? 'WARN' : 'INFO',
              `${t.label}: ${r?.result}${r?.metric ? ' · ' + r.metric : ''}`);
    }
    setRunning(null);
    setPhase('checked');
  }

  function startProxy() {
    if (phase !== 'checked') return;
    if (lastSummary?.failed === tests.length) return;
    setPhase('active');
    pushLog('INFO', 'drover: bound 127.0.0.1:1080 · routing discord traffic');
  }

  function stopProxy() {
    if (phase !== 'active') return;
    setPhase('checked');
    setStats({ up: 0, down: 0, tcp: 0, udp: 0, uptimeS: 0 });
    pushLog('INFO', 'drover: stopped');
  }

  // Live stats while active
  React.useEffect(() => {
    if (phase !== 'active') return;
    const id = setInterval(() => {
      setStats(s => ({
        up:      Math.max(0, s.up   + (Math.random()*40000 - 18000)),
        down:    Math.max(0, s.down + (Math.random()*120000 - 50000)),
        tcp:     Math.max(2, Math.min(28, s.tcp + (Math.random() < 0.3 ? (Math.random()<.5?-1:1) : 0))),
        udp:     Math.max(0, Math.min(8,  s.udp + (Math.random() < 0.2 ? (Math.random()<.5?-1:1) : 0))),
        uptimeS: s.uptimeS + 1,
      }));
    }, 1000);
    // initial values
    setStats({ up: 42000, down: 180000, tcp: 6, udp: 2, uptimeS: 0 });
    return () => clearInterval(id);
  }, [phase]);

  // Toggle a test's expanded explanation
  function toggleExpand(id) {
    setResults(r => ({ ...r, [id]: { ...r[id], expanded: !r[id]?.expanded } }));
  }

  return {
    form, update,
    phase, setPhase,
    tests, results, running,
    scenario, setScenario,
    stats,
    logs, logsOpen, setLogsOpen, pushLog, clearLogs: () => setLogs([]),
    lastSummary,
    runCheck, startProxy, stopProxy,
    toggleExpand,
  };
}

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

function seedLogs() {
  const t = Date.now();
  return [
    { t: t-9200, level: 'INFO',  msg: 'drover-go v0.4.2 starting' },
    { t: t-9100, level: 'INFO',  msg: 'config: ~/.drover/config.toml' },
    { t: t-9000, level: 'INFO',  msg: 'no active session' },
  ];
}

function fmtBytes(n) {
  if (n < 1024) return n.toFixed(0) + ' B/s';
  if (n < 1024*1024) return (n/1024).toFixed(1) + ' KB/s';
  return (n/1024/1024).toFixed(2) + ' MB/s';
}
function fmtUptime(s) {
  const h = Math.floor(s/3600), m = Math.floor((s%3600)/60), ss = s%60;
  if (h) return `${h}h ${m}m`;
  if (m) return `${m}m ${ss}s`;
  return `${ss}s`;
}
function fmtTime(t) {
  const d = new Date(t);
  return d.toLocaleTimeString('en-GB', { hour12: false }) + '.' + String(d.getMilliseconds()).padStart(3,'0');
}

// ─── Shared icons (small, original) ────────────────────────────────────────
// Drover-Go mark: a downward chevron through a ring — "tunneled traffic".
function BrandMark({ size = 16, color = 'currentColor', strokeWidth = 1.6 }) {
  const s = size;
  return (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" aria-hidden="true">
      <circle cx="12" cy="12" r="9" stroke={color} strokeWidth={strokeWidth}/>
      <path d="M7 9 L12 14 L17 9" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"/>
      <path d="M12 14 L12 19" stroke={color} strokeWidth={strokeWidth} strokeLinecap="round"/>
    </svg>
  );
}

function IconGear({ size=14, color='currentColor' }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none">
      <circle cx="8" cy="8" r="2.2" stroke={color} strokeWidth="1.2"/>
      <path d="M8 1.5v2M8 12.5v2M14.5 8h-2M3.5 8h-2M12.6 3.4l-1.4 1.4M4.8 11.2l-1.4 1.4M12.6 12.6l-1.4-1.4M4.8 4.8L3.4 3.4"
            stroke={color} strokeWidth="1.2" strokeLinecap="round"/>
    </svg>
  );
}
function IconMin({ size=14, color='currentColor' }) {
  return <svg width={size} height={size} viewBox="0 0 16 16"><path d="M3 8h10" stroke={color} strokeWidth="1.2" strokeLinecap="round"/></svg>;
}
function IconClose({ size=14, color='currentColor' }) {
  return <svg width={size} height={size} viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8" stroke={color} strokeWidth="1.2" strokeLinecap="round"/></svg>;
}
function IconChevron({ size=12, color='currentColor', dir='down' }) {
  const r = { down: 0, up: 180, left: 90, right: -90 }[dir];
  return <svg width={size} height={size} viewBox="0 0 12 12" style={{ transform: `rotate(${r}deg)` }}>
    <path d="M3 4.5 L6 7.5 L9 4.5" stroke={color} strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
  </svg>;
}
function IconCopy({ size=12, color='currentColor' }) {
  return <svg width={size} height={size} viewBox="0 0 12 12" fill="none">
    <rect x="3" y="3" width="7" height="7" rx="1" stroke={color} strokeWidth="1.2"/>
    <path d="M2 8.5V2.5C2 1.95 2.45 1.5 3 1.5h6" stroke={color} strokeWidth="1.2"/>
  </svg>;
}
function IconArrowUp({ size=10, color='currentColor' }) {
  return <svg width={size} height={size} viewBox="0 0 10 10" fill="none">
    <path d="M5 8.5V1.5M5 1.5L2 4.5M5 1.5L8 4.5" stroke={color} strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
  </svg>;
}
function IconArrowDown({ size=10, color='currentColor' }) {
  return <svg width={size} height={size} viewBox="0 0 10 10" fill="none">
    <path d="M5 1.5V8.5M5 8.5L2 5.5M5 8.5L8 5.5" stroke={color} strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
  </svg>;
}

// ─── Test row state icons (per visual variant supplies its own colors) ─────
function StatusDot({ state, palette, size = 12 }) {
  // state: 'pending' | 'running' | 'passed' | 'failed' | 'skipped'
  const c = palette[state] || palette.pending;
  if (state === 'running') {
    return (
      <span style={{ display:'inline-block', width:size, height:size, position:'relative' }}>
        <svg width={size} height={size} viewBox="0 0 16 16" style={{ animation: 'drv-spin 0.8s linear infinite' }}>
          <circle cx="8" cy="8" r="6" stroke={c} strokeOpacity="0.25" strokeWidth="2" fill="none"/>
          <path d="M8 2 a6 6 0 0 1 6 6" stroke={c} strokeWidth="2" strokeLinecap="round" fill="none"/>
        </svg>
      </span>
    );
  }
  if (state === 'passed') {
    return <svg width={size} height={size} viewBox="0 0 16 16">
      <circle cx="8" cy="8" r="7" fill={c}/>
      <path d="M5 8.2l2 2 4-4.4" stroke="white" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
    </svg>;
  }
  if (state === 'failed') {
    return <svg width={size} height={size} viewBox="0 0 16 16">
      <circle cx="8" cy="8" r="7" fill={c}/>
      <path d="M5.5 5.5l5 5M10.5 5.5l-5 5" stroke="white" strokeWidth="1.6" strokeLinecap="round"/>
    </svg>;
  }
  if (state === 'skipped') {
    return <svg width={size} height={size} viewBox="0 0 16 16">
      <circle cx="8" cy="8" r="7" fill="none" stroke={c} strokeWidth="1.4" strokeDasharray="2 2"/>
      <path d="M5 8h6" stroke={c} strokeWidth="1.4" strokeLinecap="round"/>
    </svg>;
  }
  // pending
  return <svg width={size} height={size} viewBox="0 0 16 16">
    <circle cx="8" cy="8" r="3" fill="none" stroke={c} strokeWidth="1.4"/>
  </svg>;
}

// CSS for the spinner — injected once.
if (typeof document !== 'undefined' && !document.getElementById('drv-shared-css')) {
  const s = document.createElement('style');
  s.id = 'drv-shared-css';
  s.textContent = `
    @keyframes drv-spin { to { transform: rotate(360deg); } }
    @keyframes drv-pulse { 0%,100% { opacity:1; transform:scale(1);} 50% { opacity:.55; transform:scale(0.7);} }
    @keyframes drv-blink { 0%,100% { opacity:1;} 50% { opacity:.35;} }
    @keyframes drv-fadein { from { opacity:0; transform:translateY(-2px);} to { opacity:1; transform:none;} }
    .drv-fadein { animation: drv-fadein .18s ease-out; }
    .drv-pulsedot { animation: drv-pulse 1.4s ease-in-out infinite; }
    .drv-shimmer::after {
      content:''; position:absolute; inset:0; background: linear-gradient(90deg,transparent,rgba(255,255,255,.25),transparent);
      transform:translateX(-100%); animation: drv-shim 1.6s linear infinite;
    }
    @keyframes drv-shim { to { transform: translateX(100%); } }
    /* Hide scrollbars for log panes inside artboards */
    .drv-log::-webkit-scrollbar { width:6px; }
    .drv-log::-webkit-scrollbar-thumb { background: rgba(127,127,127,.35); border-radius: 3px; }
  `;
  document.head.appendChild(s);
}

// Expose globals
Object.assign(window, {
  useDrover, getTests, ALL_TESTS, SCENARIOS,
  fmtBytes, fmtUptime, fmtTime,
  BrandMark, StatusDot,
  IconGear, IconMin, IconClose, IconChevron, IconCopy, IconArrowUp, IconArrowDown,
});
