/**
 * Fallback chain executor — system-ai.
 * Tries models in order [main, ...backups]; retryable errors continue chain,
 * non-retryable errors surface immediately.
 */
import type { AIRequest, AIResponse } from './adapter'
import { AIProviderError } from './errors'
import { getAdapter } from './adapters/index'
import type { AIEnv } from './adapters/index'

export interface ModelConfig {
  provider: 'anthropic' | 'openai' | 'google'
  model: string
  label: string
}

export interface AISettings {
  mainModel: ModelConfig
  backupModels: ModelConfig[]
}

/**
 * Race a promise against a timeout. On timeout, throws
 * AIProviderError{ code:'timeout', retryable:true }.
 */
export function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  let timeoutId: ReturnType<typeof setTimeout> | undefined
  const timeoutPromise = new Promise<never>((_, reject) => {
    timeoutId = setTimeout(() => {
      reject(
        new AIProviderError('timeout', 'unknown', true, `Request timed out after ${ms}ms`),
      )
    }, ms)
  })

  return Promise.race([promise, timeoutPromise]).finally(() => {
    if (timeoutId !== undefined) clearTimeout(timeoutId)
  })
}

/**
 * Execute an AI request with automatic fallback across the model chain.
 * Chain: [mainModel, ...backupModels]
 * - retryable AIProviderError → continue to next model
 * - non-retryable error → rethrow immediately
 * - chain exhausted → throw "All models failed"
 */
export async function executeWithFallback(
  request: AIRequest,
  settings: AISettings,
  env: AIEnv,
  opts?: { timeoutMs?: number },
): Promise<AIResponse> {
  const chain: ModelConfig[] = [settings.mainModel, ...settings.backupModels]
  const timeoutMs = opts?.timeoutMs ?? 30_000
  let lastError: AIProviderError | null = null

  for (const modelConfig of chain) {
    const adapter = getAdapter(modelConfig.provider, env)
    try {
      const response = await withTimeout(
        adapter.call({ ...request, model: modelConfig.model }),
        timeoutMs,
      )
      return response
    } catch (err) {
      if (err instanceof AIProviderError && err.retryable) {
        lastError = err
        continue // try next in chain
      }
      throw err // non-retryable: surface immediately
    }
  }

  throw new Error(`All models failed. Last error: ${lastError?.message ?? 'unknown'}`)
}
