import { jsonrepair } from "jsonrepair";
import { randomUUID } from "node:crypto";
import type {
  CanonicalContentBlock,
  CanonicalModelResponse,
  CanonicalToolCallBlock,
} from "../../protocol/canonical.js";
import { ModelProviderError } from "../../protocol/errors.js";
import { normalizeOpenAIFinishReason } from "../../response/normalizeFinishReason.js";
import { normalizeOpenAIUsage } from "../../response/normalizeUsage.js";

export function parseOpenAIResponse(raw: unknown, provider = "openai"): CanonicalModelResponse {
  const response = asRecord(raw);
  const choices = Array.isArray(response.choices) ? response.choices : [];
  const firstChoice = asRecord(choices[0]);
  const message = asRecord(firstChoice.message);
  const content: CanonicalContentBlock[] = [];

  if (typeof message.content === "string" && message.content.length > 0) {
    content.push({ type: "text", text: message.content });
  } else if (Array.isArray(message.content)) {
    for (const part of message.content) {
      const p = asRecord(part);
      if (p.type === "text" && typeof p.text === "string" && p.text.length > 0) {
        content.push({ type: "text", text: p.text });
      }
    }
  }

  if (Array.isArray(message.tool_calls)) {
    content.push(...message.tool_calls.map((toolCall) => toCanonicalToolCall(toolCall, provider)));
  }

  return {
    role: "assistant",
    content,
    usage: normalizeOpenAIUsage(response.usage),
    finishReason: normalizeOpenAIFinishReason(firstChoice.finish_reason),
    raw,
  };
}

function toCanonicalToolCall(toolCall: unknown, provider: string): CanonicalToolCallBlock {
  const record = asRecord(toolCall);
  const fn = asRecord(record.function);
  const rawArguments = typeof fn.arguments === "string" ? fn.arguments : "{}";

  let input: unknown;
  try {
    input = JSON.parse(rawArguments);
  } catch {
    try {
      const repaired = jsonrepair(rawArguments);
      input = JSON.parse(repaired);
      console.warn(`[openai-response] repaired invalid JSON for tool call (len=${rawArguments.length})`);
    } catch {
      throw new ModelProviderError({
        provider,
        protocol: "openai",
        code: "invalid_tool_arguments",
        message: "OpenAI tool call arguments are not valid JSON.",
        retryable: true,
        raw: toolCall,
      });
    }
  }

  return {
    type: "tool_call",
    id: readNonEmptyString(record.id) ?? generateToolCallId(),
    name: typeof fn.name === "string" ? fn.name : "",
    input,
    raw: toolCall,
  };
}

function asRecord(value: unknown): Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function readNonEmptyString(value: unknown): string | undefined {
  return typeof value === "string" && value.trim().length > 0 ? value : undefined;
}

function generateToolCallId(): string {
  return `call_${randomUUID().slice(0, 8)}`;
}