import {
  AnthropicRequestSchema,
  AnthropicResponseSchema,
} from "../../../proxy/formats/anthropic/schemas";
import type { AnatomySegment } from "../anatomy/types";
import { countCharacters, estimateTokens } from "../anatomy/tokenEstimate";
import type { LogFormatAdapter } from "./types";
import { emptyRequestAnalysis, EMPTY_RESPONSE_ANALYSIS } from "./types";

/**
 * Flatten an Anthropic `content` field to the text used for token
 * estimation. The function never throws: unknown content shapes are
 * treated as empty. We deliberately do NOT use JSON.stringify on the
 * content because base64 image data would dominate the size.
 */
function contentToText(content: unknown): string {
  if (typeof content === "string") return content;
  if (!Array.isArray(content)) return "";
  const parts: string[] = [];
  for (const block of content) {
    if (block === null || typeof block !== "object") continue;
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    const b = block as Record<string, unknown>;
    const type = b.type;
    switch (type) {
      case "text":
        if (typeof b.text === "string") parts.push(b.text);
        break;
      case "thinking":
      case "think":
        if (typeof b.thinking === "string") parts.push(b.thinking);
        break;
      case "tool_use": {
        const name = typeof b.name === "string" ? b.name : "";
        const input = b.input !== undefined ? JSON.stringify(b.input) : "";
        parts.push(`${name} ${input}`.trim());
        break;
      }
      case "tool_result": {
        const inner = b.content;
        if (typeof inner === "string") {
          parts.push(inner);
        } else if (Array.isArray(inner)) {
          for (const item of inner) {
            if (item !== null && typeof item === "object") {
              // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
              const t = (item as Record<string, unknown>).text;
              if (typeof t === "string") parts.push(t);
            }
          }
        }
        break;
      }
      case "image":
        // Skip base64 payload but record the image type so the segment is not empty.
        parts.push("[image]");
        break;
    }
  }
  return parts.join("\n");
}

/** Flatten an Anthropic `system` value to its text. The API accepts
 * either a string or an array of text blocks. */
function systemToText(system: unknown): string {
  if (typeof system === "string") return system;
  if (!Array.isArray(system)) return "";
  const parts: string[] = [];
  for (const block of system) {
    if (block !== null && typeof block === "object") {
      // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
      const t = (block as { type?: unknown; text?: unknown }).text;
      // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
      const type = (block as { type?: unknown }).type;
      if (type === "text" && typeof t === "string") parts.push(t);
    }
  }
  return parts.join("\n");
}

/** Flatten Anthropic `tools` array to its schema text. */
function toolsToText(tools: unknown): string {
  if (!Array.isArray(tools)) return "";
  const parts: string[] = [];
  for (const tool of tools) {
    if (tool === null || typeof tool !== "object") continue;
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    const t = tool as { name?: unknown; description?: unknown; input_schema?: unknown };
    if (typeof t.name === "string") parts.push(t.name);
    if (typeof t.description === "string") parts.push(t.description);
    if (t.input_schema !== undefined) parts.push(JSON.stringify(t.input_schema));
  }
  return parts.join("\n");
}

/** Build one segment from a piece of text + a stable label and path. */
function segment(
  role: AnatomySegment["role"],
  label: string,
  text: string,
  path: string,
): AnatomySegment {
  return {
    role,
    label,
    text,
    size: estimateTokens(text),
    characters: countCharacters(text),
    path,
  };
}

export const anthropicLogFormatAdapter: LogFormatAdapter = {
  format: "anthropic",

  analyzeRequest(rawBody) {
    if (rawBody === null) return emptyRequestAnalysis(rawBody);
    try {
      const result = AnthropicRequestSchema.safeParse(JSON.parse(rawBody));
      if (!result.success) return emptyRequestAnalysis(rawBody);
      return {
        parsed: result.data,
        comparisonValue: result.data,
        messageCount: result.data.messages.length,
        toolCount:
          result.data.tools !== undefined && result.data.tools.length > 0
            ? result.data.tools.length
            : null,
      };
    } catch {
      return emptyRequestAnalysis(rawBody);
    }
  },

  analyzeResponse(responseText) {
    if (responseText === null) return EMPTY_RESPONSE_ANALYSIS;
    try {
      let json: unknown = JSON.parse(responseText);
      if (typeof json === "string") json = JSON.parse(json);
      const result = AnthropicResponseSchema.safeParse(json);
      if (!result.success) return EMPTY_RESPONSE_ANALYSIS;
      const toolNames = result.data.content
        .filter((block) => block.type === "tool_use")
        .map((block) => block.name);
      return {
        parsed: result.data,
        toolNames: toolNames.length > 0 ? toolNames : null,
      };
    } catch {
      return EMPTY_RESPONSE_ANALYSIS;
    }
  },

  anatomySegments(parsed) {
    if (parsed === null || typeof parsed !== "object") return null;
    // We deliberately skip AnthropicRequestSchema validation here:
    // real Anthropic requests accept `system` as either a string or
    // an array, and we want the Anatomy view to render even when the
    // body shape is slightly off-schema (e.g. system: "string" vs
    // system: [{type:"text",text:"..."}]).
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    const body = parsed as {
      system?: unknown;
      messages?: unknown;
      tools?: unknown;
    };
    const segments: AnatomySegment[] = [];

    if (body.system !== undefined) {
      const text = systemToText(body.system);
      if (text.length > 0) {
        segments.push(segment("system", "system", text, "/system"));
      }
    }

    if (Array.isArray(body.messages)) {
      body.messages.forEach((message, index) => {
        if (message === null || typeof message !== "object") return;
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
        const m = message as { role?: unknown; content?: unknown };
        const role = m.role === "user" || m.role === "assistant" ? m.role : "user";
        const text = contentToText(m.content);
        segments.push(segment(role, `[${index}] ${role}`, text, `/messages/${index}`));
      });
    }

    if (Array.isArray(body.tools) && body.tools.length > 0) {
      const text = toolsToText(body.tools);
      segments.push(segment("tools", "tools", text, "/tools"));
    }

    return segments.length > 0 ? segments : null;
  },
};
