/**
 * Adapts the internal ProviderRegistry into the public LLMProvider interface
 * that stages use via ctx.llm.
 *
 * Implements tier-aware failover: if the primary provider fails, tries
 * remaining providers in the same tier, then cross-tier, then legacy active.
 */
import type { ProviderRegistry } from '../providers/registry.js';
import type { LlmProvider, CompletionOptions } from '../providers/types.js';
import type { LLMProvider, LLMCompleteOptions } from './types.js';

export class LLMProviderAdapter implements LLMProvider {
  constructor(
    private readonly registry: ProviderRegistry,
    private readonly providerName?: string,
    private readonly modelOverride?: string,
  ) {}

  async complete(prompt: string, options?: LLMCompleteOptions): Promise<string> {
    const candidates = this.getCandidates();
    if (candidates.length === 0) {
      throw new Error('No LLM provider available');
    }

    const opts = this.buildOpts(prompt, options);
    let lastError: Error | null = null;

    for (const provider of candidates) {
      try {
        const result = await provider.complete(opts);
        return result.content;
      } catch (err) {
        lastError = err as Error;
        process.stderr.write(
          `[llm-adapter] ${provider.name} failed, trying next: ${lastError.message}\n`,
        );
      }
    }

    throw lastError ?? new Error('All LLM providers failed');
  }

  available(): boolean {
    return this.getCandidates().length > 0;
  }

  /**
   * Build an ordered list of providers to try:
   *   1. Named provider (if specified)
   *   2. All 'fast' tier providers
   *   3. All 'heavy' tier providers
   *   4. Active provider (legacy fallback)
   * Deduplicates by name.
   */
  private getCandidates(): LlmProvider[] {
    const seen = new Set<string>();
    const candidates: LlmProvider[] = [];

    const add = (p: LlmProvider | null | undefined) => {
      if (p && !seen.has(p.name)) {
        seen.add(p.name);
        candidates.push(p);
      }
    };

    // Preferred provider first
    if (this.providerName) {
      add(this.registry.get(this.providerName));
    }

    // All fast tier providers
    for (const name of this.registry.getTierProviders('fast')) {
      add(this.registry.get(name));
    }

    // All heavy tier providers
    for (const name of this.registry.getTierProviders('heavy')) {
      add(this.registry.get(name));
    }

    // Legacy fallback
    add(this.registry.getActive());

    return candidates;
  }

  private buildOpts(prompt: string, options?: LLMCompleteOptions): CompletionOptions {
    const messages = [];
    if (options?.system) {
      messages.push({ role: 'system' as const, content: options.system });
    }
    messages.push({ role: 'user' as const, content: prompt });

    const opts: CompletionOptions = {
      messages,
      temperature: 0,
    };
    if (this.modelOverride) {
      opts.model = this.modelOverride;
    }
    if (options?.maxTokens !== undefined) {
      opts.maxTokens = options.maxTokens;
    }
    return opts;
  }
}
