/**
 * Anthropic adapter — ported from the extension. Server-side variant
 * (no dangerouslyAllowBrowser). OAuth tokens (sk-ant-oat-*) get the
 * special beta header + authToken handling.
 */

import Anthropic from '@anthropic-ai/sdk';
import type { Message } from '../types';
import type { LlmAdapter, LlmCallbacks, LlmRequest, LlmTurnResult, StopReason } from './types';

function historyToApi(history: Message[]): Anthropic.MessageParam[] {
  const out: Anthropic.MessageParam[] = [];
  for (const m of history) {
    const content: Anthropic.ContentBlockParam[] = [];
    for (const b of m.blocks) {
      if (b.type === 'text') {
        if (b.text.length > 0) content.push({ type: 'text', text: b.text });
      } else if (b.type === 'tool_use') {
        // Same encoding as tool definitions — names with `.` must
        // round-trip through Anthropic as `__` to satisfy its tool name
        // regex when this turn's history is re-sent next turn.
        content.push({
          type: 'tool_use',
          id: b.id,
          name: b.name.replace(/\./g, '__'),
          input: (b.input ?? {}) as Record<string, unknown>,
        });
      } else if (b.type === 'tool_result') {
        content.push({
          type: 'tool_result',
          tool_use_id: b.tool_use_id,
          content: b.content,
          is_error: b.is_error,
        });
      }
    }
    if (content.length === 0) continue;
    out.push({ role: m.role, content });
  }
  return out;
}

function mapStop(r: Anthropic.Message['stop_reason']): StopReason {
  switch (r) {
    case 'end_turn': return 'end_turn';
    case 'tool_use': return 'tool_use';
    case 'max_tokens': return 'max_tokens';
    case 'refusal': return 'refusal';
    default: return 'other';
  }
}

function isOauthToken(key: string): boolean {
  return key.startsWith('sk-ant-oat');
}

export function makeAnthropicClient(apiKey: string, baseUrl: string): Anthropic {
  const opts: ConstructorParameters<typeof Anthropic>[0] = {
    baseURL: baseUrl || undefined,
  };
  if (isOauthToken(apiKey)) {
    opts.authToken = apiKey;
    opts.defaultHeaders = { 'anthropic-beta': 'oauth-2025-04-20' };
  } else {
    opts.apiKey = apiKey;
  }
  return new Anthropic(opts);
}

/**
 * Anthropic restricts tool names to `^[a-zA-Z0-9_-]{1,128}$`. Forge's
 * connector tools use a `connector.tool` namespace (e.g.
 * `gitlab.list_my_mrs`), so we encode the dot as `__` on the way out and
 * decode it back when Anthropic returns a tool_use. Internal dispatcher
 * still sees the canonical dotted form.
 */
function encodeToolName(name: string): string {
  return name.replace(/\./g, '__');
}
function decodeToolName(name: string): string {
  return name.replace(/__/g, '.');
}

export const anthropicAdapter: LlmAdapter = {
  async stream(req: LlmRequest, cb: LlmCallbacks): Promise<LlmTurnResult> {
    const client = makeAnthropicClient(req.apiKey, req.baseUrl);

    const stream = client.messages.stream({
      model: req.model,
      max_tokens: req.maxTokens,
      system: req.system,
      tools: req.tools.map((t) => ({
        name: encodeToolName(t.name),
        description: t.description,
        input_schema: t.input_schema as Anthropic.Tool.InputSchema,
      })),
      messages: historyToApi(req.history),
    });

    stream.on('text', (delta: string) => cb.onTextDelta(delta));

    const final = await stream.finalMessage();

    const content: LlmTurnResult['content'] = [];
    for (const block of final.content) {
      if (block.type === 'text') {
        content.push({ type: 'text', text: block.text });
      } else if (block.type === 'tool_use') {
        const decodedName = decodeToolName(block.name);
        cb.onToolUse({ id: block.id, name: decodedName, input: block.input });
        content.push({ type: 'tool_use', id: block.id, name: decodedName, input: block.input });
      }
    }

    return { stopReason: mapStop(final.stop_reason), content };
  },
};
