import { Logger } from '@nestjs/common';
import Anthropic from '@anthropic-ai/sdk';
import { LlmProvider, LlmRequest, LlmResponse, LlmContentBlock } from './llm.provider';

/**
 * Anthropic Messages API adapter. Maps the neutral LlmRequest into the SDK
 * shape (system prompt, tool definitions, tool_use / tool_result blocks) and
 * back. No business logic lives here.
 */
export class AnthropicProvider implements LlmProvider {
  readonly name = 'anthropic';
  private readonly logger = new Logger(AnthropicProvider.name);
  private readonly client: Anthropic;

  constructor(apiKey: string, readonly model: string, private readonly defaultMaxTokens: number) {
    this.client = new Anthropic({ apiKey, maxRetries: 2, timeout: 60_000 });
  }

  async complete(req: LlmRequest): Promise<LlmResponse> {
    const messages: Anthropic.MessageParam[] = req.messages.map((m) => ({
      role: m.role,
      content: typeof m.content === 'string' ? m.content : m.content.map(toSdkBlock),
    }));
    const tools: Anthropic.Tool[] | undefined = req.tools?.map((t) => ({
      name: t.name,
      description: t.description,
      input_schema: t.inputSchema as Anthropic.Tool.InputSchema,
    }));

    const system = req.json ? `${req.system}\n\nRespond with a single JSON object and nothing else. No prose, no markdown fences.` : req.system;

    const res = await this.client.messages.create({
      model: this.model,
      max_tokens: req.maxTokens ?? this.defaultMaxTokens,
      temperature: req.temperature ?? 0,
      system,
      messages,
      ...(tools && tools.length ? { tools } : {}),
    });

    const text = res.content.filter((b): b is Anthropic.TextBlock => b.type === 'text').map((b) => b.text).join('\n').trim();
    const toolCalls = res.content
      .filter((b): b is Anthropic.ToolUseBlock => b.type === 'tool_use')
      .map((b) => ({ id: b.id, name: b.name, input: (b.input ?? {}) as Record<string, unknown> }));

    const stopReason: LlmResponse['stopReason'] =
      res.stop_reason === 'tool_use' ? 'tool_use' : res.stop_reason === 'max_tokens' ? 'max_tokens' : res.stop_reason === 'end_turn' || res.stop_reason === 'stop_sequence' ? 'end' : 'other';

    if (stopReason === 'max_tokens') this.logger.warn(`Response truncated at max_tokens (${req.maxTokens ?? this.defaultMaxTokens})`);

    return { text, toolCalls, stopReason, usage: { inputTokens: res.usage.input_tokens, outputTokens: res.usage.output_tokens }, model: res.model };
  }
}

function toSdkBlock(b: LlmContentBlock): Anthropic.ContentBlockParam {
  switch (b.type) {
    case 'text':
      return { type: 'text', text: b.text };
    case 'tool_use':
      return { type: 'tool_use', id: b.id, name: b.name, input: b.input };
    case 'tool_result':
      return { type: 'tool_result', tool_use_id: b.toolUseId, content: b.content, is_error: b.isError };
  }
}
