import { spawn, ChildProcess } from 'child_process';
import {
  AIProvider,
  ProviderOptions,
  ProviderResult,
  StreamChunk,
  ProviderConfig,
  ProviderError,
  ProviderUnavailableError,
} from './provider.js';

export class CodingAgentProvider implements AIProvider {
  readonly name = 'coding-agent';
  readonly type = 'claude' as const; // coding-agent uses Claude by default

  private config: ProviderConfig;
  private defaultModel: string;
  private process: ChildProcess | null = null;

  constructor(config: ProviderConfig = {}) {
    this.config = config;
    this.defaultModel = config.model || 'claude-sonnet-4-20250514';
  }

  async execute(
    prompt: string,
    options?: ProviderOptions,
  ): Promise<ProviderResult> {
    if (!this.isAvailable()) {
      throw new ProviderUnavailableError(
        this.name,
        'coding-agent binary not found in PATH',
      );
    }

    return new Promise((resolve, reject) => {
      const args = this.buildArgs(prompt, options);

      this.process = spawn('coding-agent', args, {
        cwd: this.config.baseUrl || process.cwd(),
        env: {
          ...process.env,
          ANTHROPIC_API_KEY:
            this.config.apiKey || process.env.ANTHROPIC_API_KEY,
          AGENT_CODE_OAUTH_TOKEN: process.env.AGENT_CODE_OAUTH_TOKEN,
        },
      });

      let stdout = '';
      let stderr = '';

      this.process.stdout?.on('data', (data) => {
        stdout += data.toString();
      });

      this.process.stderr?.on('data', (data) => {
        stderr += data.toString();
      });

      this.process.on('close', (code) => {
        if (code === 0) {
          resolve({
            content: stdout.trim(),
            usage: undefined, // coding-agent doesn't report token usage
          });
        } else {
          reject(
            new ProviderError(
              `coding-agent exited with code ${code}: ${stderr}`,
              this.name,
              'PROCESS_ERROR',
              code ?? undefined,
            ),
          );
        }
      });

      this.process.on('error', (error) => {
        reject(
          new ProviderError(
            `Failed to spawn coding-agent: ${error.message}`,
            this.name,
          ),
        );
      });

      // Send prompt via stdin
      this.process.stdin?.write(prompt);
      this.process.stdin?.end();
    });
  }

  async *stream(
    prompt: string,
    options?: ProviderOptions,
  ): AsyncIterable<StreamChunk> {
    // coding-agent doesn't support streaming in the same way
    // Execute and yield the complete result
    const result = await this.execute(prompt, options);

    // Yield text in chunks for consistency
    const chunkSize = 100;
    for (let i = 0; i < result.content.length; i += chunkSize) {
      yield {
        type: 'text',
        delta: result.content.slice(i, i + chunkSize),
      };
    }

    if (result.usage) {
      yield { type: 'usage', usage: result.usage };
    }
  }

  isAvailable(): boolean {
    // Check if coding-agent binary exists
    try {
      const result = spawn('which', ['coding-agent'], { shell: true });
      return result.exitCode === 0;
    } catch {
      return false;
    }
  }

  async getModels(): Promise<string[]> {
    // coding-agent uses Claude models
    return [
      'claude-opus-4-20250514',
      'claude-sonnet-4-20250514',
      'claude-haiku-3-5-20241022',
    ];
  }

  getDefaultModel(): string {
    return this.defaultModel;
  }

  private buildArgs(prompt: string, options?: ProviderOptions): string[] {
    const args: string[] = [
      '--non-interactive',
      '--model',
      options?.model || this.defaultModel,
    ];

    if (options?.systemPrompt) {
      args.push('--system', options.systemPrompt);
    }

    if (options?.maxTokens) {
      args.push('--max-tokens', options.maxTokens.toString());
    }

    if (options?.temperature !== undefined) {
      args.push('--temperature', options.temperature.toString());
    }

    // Read from stdin
    args.push('--stdin');

    return args;
  }

  kill(): void {
    if (this.process) {
      this.process.kill();
      this.process = null;
    }
  }
}

// Factory function for convenience
export function createCodingAgentProvider(
  config?: ProviderConfig,
): CodingAgentProvider {
  return new CodingAgentProvider(config);
}
