// Agent bridge — NDJSON stdio interface for Claude Agent SDK integration
// Accepts queries on stdin, responds on stdout. Allows an agent to control
// and query the test daemon programmatically.

import { createInterface } from 'node:readline';
import type { Dashboard } from './dashboard.ts';
import { clearCache, type RunOptions } from './runner.ts';

// ── Query/Response types ──

type Query =
  | { type: 'status' }
  | { type: 'rerun'; pattern?: string }
  | { type: 'failures' }
  | { type: 'reset-cache' };

type Response =
  | { type: 'status'; running: boolean; passed: number; failed: number; skipped: number; pending: number; total: number; failures: Array<{ name: string; error?: string }> }
  | { type: 'rerun'; specsQueued: number }
  | { type: 'failures'; failures: Array<{ name: string; specFile: string; error?: string }> }
  | { type: 'reset-cache'; ok: true }
  | { type: 'error'; message: string };

// ── Bridge ──

export class AgentBridge {
  private dashboard: Dashboard;
  private running = false;
  private triggerRerun: ((opts: RunOptions) => void) | null = null;
  private rl: ReturnType<typeof createInterface> | null = null;

  constructor(dashboard: Dashboard) {
    this.dashboard = dashboard;
  }

  /** Register callback that triggers a new test run from the main loop */
  onRerunRequest(cb: (opts: RunOptions) => void): void {
    this.triggerRerun = cb;
  }

  setRunning(running: boolean): void {
    this.running = running;
  }

  start(): void {
    this.rl = createInterface({
      input: process.stdin,
      terminal: false,
    });

    this.rl.on('line', (line) => {
      const trimmed = line.trim();
      if (!trimmed) return;

      try {
        const query = JSON.parse(trimmed) as Query;
        const response = this.handleQuery(query);
        this.send(response);
      } catch (err: unknown) {
        const msg = err instanceof Error ? err.message : String(err);
        this.send({ type: 'error', message: `Invalid query: ${msg}` });
      }
    });

    this.rl.on('close', () => {
      this.stop();
    });
  }

  stop(): void {
    if (this.rl) {
      this.rl.close();
      this.rl = null;
    }
  }

  private send(response: Response): void {
    // Write to stdout as NDJSON — use fd 3 or stderr if stdout is used by dashboard
    // Since dashboard writes to stdout, we use stderr for NDJSON responses
    // when the dashboard is active. The agent reads from our stderr.
    process.stderr.write(JSON.stringify(response) + '\n');
  }

  private handleQuery(query: Query): Response {
    switch (query.type) {
      case 'status':
        return this.handleStatus();
      case 'rerun':
        return this.handleRerun(query);
      case 'failures':
        return this.handleFailures();
      case 'reset-cache':
        return this.handleResetCache();
      default:
        return { type: 'error', message: `Unknown query type: ${(query as { type: string }).type}` };
    }
  }

  private handleStatus(): Response {
    const tests = this.dashboard.getTests();
    const passed = tests.filter((t) => t.status === 'passed').length;
    const failed = tests.filter((t) => t.status === 'failed').length;
    const skipped = tests.filter((t) => t.status === 'skipped').length;
    const pending = tests.filter((t) => t.status === 'pending' || t.status === 'running').length;
    const failures = tests
      .filter((t) => t.status === 'failed')
      .map((t) => ({ name: t.name, error: t.error }));

    return {
      type: 'status',
      running: this.running,
      passed,
      failed,
      skipped,
      pending,
      total: tests.length,
      failures,
    };
  }

  private handleRerun(query: Extract<Query, { type: 'rerun' }>): Response {
    if (this.running) return { type: 'rerun', specsQueued: 0 };
    const opts: RunOptions = { full: true };
    if (query.pattern) opts.pattern = query.pattern;
    this.triggerRerun?.(opts);
    return { type: 'rerun', specsQueued: 1 };
  }

  private handleFailures(): Response {
    const tests = this.dashboard.getTests();
    const failures = tests
      .filter((t) => t.status === 'failed')
      .map((t) => ({
        name: t.name,
        specFile: t.specFile,
        error: t.error,
      }));

    return { type: 'failures', failures };
  }

  private handleResetCache(): Response {
    clearCache();
    return { type: 'reset-cache', ok: true };
  }
}
