// shared.jsx — state machine + shared icons/utilities for all Drover-Go variants.
//
// Original prototype loaded everything via window globals (babel script-tag
// build). For Wails + Vite we use real ESM imports/exports — additions:
//   - `import * as React from 'react'` so `React.useState/useMemo/useEffect`
//     keep working unchanged.
//   - `export` on everything the variant components need.
//   - `useDrover` no longer simulates with `SCENARIOS`; it calls the Wails
//     bindings on `window.go.main.App` and listens for the events the Go
//     side emits (`check:result`, `check:done`, `stats:update`, ...).
//
// The state surface (form/phase/results/stats/logs) is unchanged, so the
// UI components don't need to be rewritten — only their imports.

import * as React from 'react'
import { RunCheck, CancelCheck, StartEngine, StopEngine, GetStatus } from '../../wailsjs/go/gui/App'
import { EventsOn, EventsOff } from '../../wailsjs/runtime/runtime'

// ─── Test catalog ──────────────────────────────────────────────────────────
export 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: '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' },
  },
};

export 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'
export 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'); // kept for compat with prototype, unused with real backend
  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;
    const warnings = ids.filter(id => results[id]?.result === 'warn').length;
    return { total: ids.length, failed, warnings };
  }, [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 }]);
  }

  // Subscribe to backend events once. The Go side emits:
  //   check:result   → one test result (id, status, metric, error, hint)
  //   check:done     → diagnostic finished, summary {total, passed, failed}
  //   engine:status  → {running: bool}
  //   stats:update   → {up, down, tcp, udp, uptimeS}
  React.useEffect(() => {
    const offResult = EventsOn('check:result', (r) => {
      if (r.status === 'running') {
        setRunning(r.id);
        return;
      }
      // Convert backend "status" field to the frontend's "result" field used
      // by the Classic/Fluent/etc components.
      setResults(prev => ({
        ...prev,
        [r.id]: {
          result:  r.status,
          metric:  r.metric,
          error:   r.error,
          hint:    r.hint,
          rawHex:  r.rawHex,
          attempt: r.attempt,
          expanded: r.status === 'failed' || r.status === 'warn',
        },
      }));
      pushLog(r.status === 'failed' ? 'ERROR' : (r.status === 'skipped' || r.status === 'warn') ? 'WARN' : 'INFO',
              `${r.id}: ${r.status}${r.metric ? ' · ' + r.metric : ''}`);
    });
    const offDone = EventsOn('check:done', (s) => {
      setRunning(null);
      setPhase('checked');
      pushLog('INFO', `check finished — ${s.passed}/${s.total} passed`);
    });
    const offStatus = EventsOn('engine:status', (s) => {
      setPhase(s.running ? 'active' : 'checked');
      pushLog('INFO', s.running ? 'engine: started' : 'engine: stopped');
    });
    const offStats = EventsOn('stats:update', (s) => setStats(s));

    return () => {
      offResult();
      offDone();
      offStatus();
      offStats();
    };
  }, []);

  async function runCheck() {
    if (phase === 'checking') return;
    setPhase('checking');
    setResults({});
    setRunning(null);
    pushLog('INFO', `connect ${form.host}:${form.port}${form.auth ? ' (auth)' : ''}`);
    await RunCheck({
      host: form.host,
      port: parseInt(form.port, 10) || 0,
      auth: form.auth,
      login: form.login,
      password: form.password,
    });
    // The rest is event-driven (check:result, check:done) — see useEffect above.
  }

  function cancelCheck() {
    CancelCheck();
    pushLog('WARN', 'check cancelled by user');
  }

  async function startProxy() {
    if (phase !== 'checked') return;
    if (lastSummary?.failed === tests.length) return;
    try {
      await StartEngine({
        host: form.host,
        port: parseInt(form.port, 10) || 0,
        auth: form.auth,
        login: form.login,
        password: form.password,
      });
    } catch (e) {
      pushLog('ERROR', 'startEngine failed: ' + (e?.message || e));
      return;
    }
    // engine:status event will flip phase to 'active'.
  }

  async function stopProxy() {
    if (phase !== 'active') return;
    await StopEngine();
    setStats({ up: 0, down: 0, tcp: 0, udp: 0, uptimeS: 0 });
  }

  // Reflect initial backend state (in case the engine was already running
  // when the GUI was opened — e.g. via service mode).
  React.useEffect(() => {
    GetStatus().then((s) => {
      if (s?.running) setPhase('active');
    }).catch(() => {});
  }, []);

  // 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, cancelCheck, startProxy, stopProxy,
    toggleExpand,
  };
}

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

// Sun + moon icons for the theme-toggle button in the title bar. Style
// matches the rest (1.2 stroke, 14px square viewBox).
export function IconSun({ size=14, color='currentColor' }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none">
      <circle cx="8" cy="8" r="3" stroke={color} strokeWidth="1.2"/>
      <path d="M8 1.5v1.5M8 13v1.5M14.5 8H13M3 8H1.5M12.6 3.4l-1 1M4.4 11.6l-1 1M12.6 12.6l-1-1M4.4 4.4l-1-1"
            stroke={color} strokeWidth="1.2" strokeLinecap="round"/>
    </svg>
  );
}
export function IconMoon({ size=14, color='currentColor' }) {
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" fill="none">
      <path d="M13.5 9.5A5.5 5.5 0 1 1 6.5 2.5a4 4 0 0 0 7 7z"
            stroke={color} strokeWidth="1.2" strokeLinejoin="round"/>
    </svg>
  );
}

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' },
  ];
}

export 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';
}
export 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`;
}
export 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".
export 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>
  );
}

export 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>
  );
}
export 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>;
}
export 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>;
}
export 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>;
}
export 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>;
}
export 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>;
}
export 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) ─────
export 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 === 'warn') {
    return <svg width={size} height={size} viewBox="0 0 16 16">
      <circle cx="8" cy="8" r="7" fill={c}/>
      <path d="M8 4v5" stroke="white" strokeWidth="1.6" strokeLinecap="round"/>
      <circle cx="8" cy="11.5" r="0.9" fill="white"/>
    </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,
});
