#!/usr/bin/env tsx
// Agent Orchestrator E2E Test Daemon — CLI entry point
// Usage: tsx index.ts [--full] [--spec <pattern>] [--watch] [--agent] [--stack tauri|electrobun|both]

import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { watch } from 'node:fs';
import { Dashboard } from './dashboard.ts';
import { runSpecs, discoverSpecs, specDisplayName, clearCache, type RunOptions, type StackTarget } from './runner.ts';
import { AgentBridge } from './agent-bridge.ts';

const __dirname = dirname(fileURLToPath(import.meta.url));
const SPECS_DIR = resolve(__dirname, '../specs');

// ── CLI args ──
const args = process.argv.slice(2);
const fullMode = args.includes('--full');
const watchMode = args.includes('--watch');
const agentMode = args.includes('--agent');
const specIdx = args.indexOf('--spec');
const specPattern = specIdx !== -1 ? args[specIdx + 1] : undefined;
const stackIdx = args.indexOf('--stack');
const stackTarget: StackTarget = stackIdx !== -1
  ? (args[stackIdx + 1] as StackTarget) ?? 'tauri'
  : 'tauri';

// Validate stack target
if (!['tauri', 'electrobun', 'both'].includes(stackTarget)) {
  console.error(`Invalid --stack value: ${stackTarget}. Use: tauri, electrobun, or both`);
  process.exit(1);
}

console.log(`E2E Test Daemon — stack: ${stackTarget}`);

// ── Init ──
const dashboard = new Dashboard();
let bridge: AgentBridge | null = null;
let pendingRerun: RunOptions | null = null;

if (agentMode) {
  bridge = new AgentBridge(dashboard);
  bridge.onRerunRequest((opts) => { pendingRerun = opts; });
  bridge.start();
}

// ── Run cycle ──
async function runCycle(opts: RunOptions = {}): Promise<void> {
  const specs = discoverSpecs(opts.pattern ?? specPattern);
  dashboard.setTests(specs.map((s) => ({ name: specDisplayName(s), specFile: s })));
  dashboard.startRefresh();
  bridge?.setRunning(true);

  await runSpecs({
    pattern: opts.pattern ?? specPattern,
    full: opts.full ?? fullMode,
    stack: opts.stack ?? stackTarget,
    onResult: (r) => dashboard.updateTest(r.name, r.status, r.durationMs, r.error),
  });

  dashboard.markComplete();
  dashboard.stopRefresh();
  bridge?.setRunning(false);
}

function shutdown(poll?: ReturnType<typeof setInterval>, watcher?: ReturnType<typeof watch>): void {
  watcher?.close();
  if (poll) clearInterval(poll);
  dashboard.stop();
  bridge?.stop();
  process.exit(0);
}

// ── Main ──
async function main(): Promise<void> {
  if (fullMode) clearCache();
  await runCycle();

  if (watchMode || agentMode) {
    const watcher = watchMode
      ? watch(SPECS_DIR, { recursive: false }, (_ev, f) => {
          if (f?.endsWith('.test.ts')) pendingRerun = { pattern: specPattern, full: false };
        })
      : undefined;

    const poll = setInterval(async () => {
      if (pendingRerun) {
        const opts = pendingRerun;
        pendingRerun = null;
        await runCycle(opts);
      }
    }, 1000);

    process.on('SIGINT', () => shutdown(poll, watcher));
    process.on('SIGTERM', () => shutdown(poll, watcher));
  } else {
    dashboard.stop();
    bridge?.stop();
    const hasFailed = dashboard.getTests().some((t) => t.status === 'failed');
    process.exit(hasFailed ? 1 : 0);
  }
}

main().catch((err) => {
  console.error('Fatal error:', err);
  dashboard.stop();
  bridge?.stop();
  process.exit(1);
});
