import {
  AuthError,
  FatalError,
  QuotaExhaustedError,
  RateLimitError,
  TransientError,
  type AIMessage,
  type AIRequest,
} from './index.js'

const SENTINEL_RE = /@@MOCK:([A-Z_]+)(?::([^@]*))?@@/

type Directive = {
  kind: string
  arg?: string
}

const THROW_DIRECTIVES = new Set(['ERROR', 'RATELIMIT', 'TRANSIENT', 'QUOTA', 'AUTH'])

function parseSentinel(text: string): Directive | null {
  const m = SENTINEL_RE.exec(text)
  if (!m) return null
  return { kind: m[1]!, arg: m[2]?.trim() || undefined }
}

function modelDirective(model: string): Directive | null {
  if (!model.startsWith('TEST')) return null
  const suffix = model.slice('TEST'.length).replace(/^-/, '')
  if (suffix === 'RATELIMIT') return { kind: 'RATELIMIT' }
  if (suffix === 'TRANSIENT') return { kind: 'TRANSIENT' }
  if (suffix === 'QUOTA') return { kind: 'QUOTA' }
  if (suffix === 'AUTH') return { kind: 'AUTH' }
  if (suffix === 'ERROR') return { kind: 'ERROR' }
  return null
}

export function extractMessageText(messages: AIMessage[]): string {
  return messages
    .map((message) =>
      typeof message.content === 'string'
        ? message.content
        : message.content
            .filter((block) => block.type === 'text')
            .map((block) => block.text)
            .join(''),
    )
    .join('\n')
}

export function resolveMockDirective(req: AIRequest): Directive | null {
  const text = extractMessageText(req.messages)
  return parseSentinel(text) ?? modelDirective(req.model)
}

export function throwForMockDirective(kind: string, arg?: string): never {
  const msg = arg ?? `[TEST] mock ${kind}`
  if (kind === 'RATELIMIT') throw new RateLimitError(msg, 'mock')
  if (kind === 'TRANSIENT') throw new TransientError(msg, 'mock')
  if (kind === 'QUOTA') throw new QuotaExhaustedError(msg, 'mock')
  if (kind === 'AUTH') throw new AuthError(msg, 'mock')
  throw new FatalError(msg, 'mock')
}

export function makeMockAdapter() {
  return {
    // Honest per the AIAdapter.supportsImage contract: this fixture's call()
    // ignores content and returns canned text — it maps NO image blocks to any
    // wire shape, so it does not have the capability. A test needing an
    // image-capable stand-in should use a real adapter with a faked fetch.
    provider: 'mock',
    supportsImage: false,
    async call(req: AIRequest) {
      const directive = resolveMockDirective(req)
      if (directive && THROW_DIRECTIVES.has(directive.kind)) {
        throwForMockDirective(directive.kind, directive.arg)
      }
      return {
        content: '[TEST] mock ok',
        model: req.model,
        provider: 'mock',
        usage: { promptTokens: 1, completionTokens: 1 },
      }
    },
  }
}
