/**
 * Anthropic Claude Messages API support.
 *
 * Translates incoming /v1/messages requests into the ChatCompletionRequest
 * format used by the fixture router, and converts fixture responses back into
 * the Claude Messages API streaming (or non-streaming) format.
 */

import type * as http from "node:http";
import type {
  ChatCompletionRequest,
  ChatMessage,
  Fixture,
  HandlerDefaults,
  RecordedTimings,
  ResponseOverrides,
  StreamingProfile,
  ToolCall,
  ToolDefinition,
} from "./types.js";
import {
  generateMessageId,
  generateToolUseId,
  extractOverrides,
  isTextResponse,
  isToolCallResponse,
  isContentWithToolCallsResponse,
  isErrorResponse,
  flattenHeaders,
  getTestId,
  resolveResponse,
  resolveStrictMode,
  strictOverrideField,
  getContext,
} from "./helpers.js";
import { matchFixture } from "./router.js";
import { writeErrorResponse, delay, calculateDelay } from "./sse-writer.js";
import { createInterruptionSignal } from "./interruption.js";
import type { Journal } from "./journal.js";
import type { Logger } from "./logger.js";
import { applyChaos } from "./chaos.js";
import { proxyAndRecord } from "./recorder.js";

// ─── Claude Messages API request types ──────────────────────────────────────

interface ClaudeContentBlock {
  type: "text" | "tool_use" | "tool_result" | "image" | "document";
  text?: string;
  id?: string;
  name?: string;
  input?: unknown;
  tool_use_id?: string;
  content?: string | ClaudeContentBlock[];
  is_error?: boolean;
}

interface ClaudeMessage {
  role: "user" | "assistant";
  content: string | ClaudeContentBlock[];
}

interface ClaudeToolDef {
  name: string;
  description?: string;
  input_schema?: object;
}

interface ClaudeRequest {
  model: string;
  messages: ClaudeMessage[];
  system?: string | ClaudeContentBlock[];
  tools?: ClaudeToolDef[];
  tool_choice?: unknown;
  stream?: boolean;
  max_tokens: number;
  temperature?: number;
  [key: string]: unknown;
}

// ─── Input conversion: Claude → ChatCompletions messages ────────────────────

function extractClaudeTextContent(content: string | ClaudeContentBlock[]): string {
  if (typeof content === "string") return content;
  return content
    .filter((b) => b.type === "text")
    .map((b) => b.text ?? "")
    .join("");
}

export function claudeToCompletionRequest(req: ClaudeRequest): ChatCompletionRequest {
  const messages: ChatMessage[] = [];

  // system field → system message
  if (req.system) {
    const systemText =
      typeof req.system === "string"
        ? req.system
        : req.system
            .filter((b) => b.type === "text")
            .map((b) => b.text ?? "")
            .join("");
    if (systemText) {
      messages.push({ role: "system", content: systemText });
    }
  }

  for (const msg of req.messages) {
    if (msg.role === "user") {
      // Check for tool_result blocks
      if (typeof msg.content !== "string" && Array.isArray(msg.content)) {
        const toolResults = msg.content.filter((b) => b.type === "tool_result");
        const textBlocks = msg.content.filter((b) => b.type === "text");

        if (toolResults.length > 0) {
          // Each tool_result → tool message
          for (const tr of toolResults) {
            const resultContent =
              typeof tr.content === "string"
                ? tr.content
                : Array.isArray(tr.content)
                  ? tr.content
                      .filter((b) => b.type === "text")
                      .map((b) => b.text ?? "")
                      .join("")
                  : "";
            messages.push({
              role: "tool",
              content: resultContent,
              tool_call_id: tr.tool_use_id,
            });
          }
          // Any accompanying text blocks → user message
          if (textBlocks.length > 0) {
            messages.push({
              role: "user",
              content: textBlocks.map((b) => b.text ?? "").join(""),
            });
          }
          continue;
        }
      }
      // Regular user message
      messages.push({
        role: "user",
        content: extractClaudeTextContent(msg.content),
      });
    } else if (msg.role === "assistant") {
      if (typeof msg.content === "string") {
        messages.push({ role: "assistant", content: msg.content });
      } else if (Array.isArray(msg.content)) {
        const toolUseBlocks = msg.content.filter((b) => b.type === "tool_use");
        const textContent = extractClaudeTextContent(msg.content);

        if (toolUseBlocks.length > 0) {
          messages.push({
            role: "assistant",
            content: textContent || null,
            tool_calls: toolUseBlocks.map((b) => ({
              id: b.id ?? generateToolUseId(),
              type: "function" as const,
              function: {
                name: b.name ?? "",
                arguments: typeof b.input === "string" ? b.input : JSON.stringify(b.input ?? {}),
              },
            })),
          });
        } else {
          messages.push({ role: "assistant", content: textContent || null });
        }
      } else {
        // null/undefined content — tool-only assistant turn
        messages.push({ role: "assistant", content: null });
      }
    }
  }

  // Convert tools
  let tools: ToolDefinition[] | undefined;
  if (req.tools && req.tools.length > 0) {
    tools = req.tools.map((t) => ({
      type: "function" as const,
      function: {
        name: t.name,
        description: t.description,
        parameters: t.input_schema,
      },
    }));
  }

  return {
    model: req.model,
    messages,
    stream: req.stream,
    temperature: req.temperature,
    max_tokens: req.max_tokens,
    tools,
    _endpointType: "chat",
  };
}

// ─── Response building: fixture → Claude Messages API format ────────────────

function claudeStopReason(finishReason: string | undefined, defaultReason: string): string {
  if (!finishReason) return defaultReason;
  if (finishReason === "stop") return "end_turn";
  if (finishReason === "tool_calls") return "tool_use";
  if (finishReason === "length") return "max_tokens";
  return finishReason;
}

function claudeUsage(overrides?: ResponseOverrides): {
  input_tokens: number;
  output_tokens: number;
} {
  if (!overrides?.usage) return { input_tokens: 0, output_tokens: 0 };
  return {
    input_tokens: overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0,
    output_tokens: overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0,
  };
}

interface ClaudeSSEEvent {
  type: string;
  [key: string]: unknown;
}

function buildClaudeTextStreamEvents(
  content: string,
  model: string,
  chunkSize: number,
  reasoning?: string,
  overrides?: ResponseOverrides,
): ClaudeSSEEvent[] {
  const msgId = overrides?.id ?? generateMessageId();
  const effectiveModel = overrides?.model ?? model;
  const events: ClaudeSSEEvent[] = [];

  // message_start
  events.push({
    type: "message_start",
    message: {
      id: msgId,
      type: "message",
      role: overrides?.role ?? "assistant",
      content: [],
      model: effectiveModel,
      stop_reason: null,
      stop_sequence: null,
      usage: claudeUsage(overrides),
    },
  });

  let blockIndex = 0;

  // Thinking block (emitted before text when reasoning is present)
  if (reasoning) {
    events.push({
      type: "content_block_start",
      index: blockIndex,
      content_block: { type: "thinking", thinking: "", signature: "" },
    });

    for (let i = 0; i < reasoning.length; i += chunkSize) {
      const slice = reasoning.slice(i, i + chunkSize);
      events.push({
        type: "content_block_delta",
        index: blockIndex,
        delta: { type: "thinking_delta", thinking: slice },
      });
    }

    events.push({
      type: "content_block_delta",
      index: blockIndex,
      delta: { type: "signature_delta", signature: "" },
    });

    events.push({
      type: "content_block_stop",
      index: blockIndex,
    });

    blockIndex++;
  }

  // content_block_start (text)
  events.push({
    type: "content_block_start",
    index: blockIndex,
    content_block: { type: "text", text: "" },
  });

  // content_block_delta — text chunks
  for (let i = 0; i < content.length; i += chunkSize) {
    const slice = content.slice(i, i + chunkSize);
    events.push({
      type: "content_block_delta",
      index: blockIndex,
      delta: { type: "text_delta", text: slice },
    });
  }

  // content_block_stop
  events.push({
    type: "content_block_stop",
    index: blockIndex,
  });

  // message_delta
  events.push({
    type: "message_delta",
    delta: {
      stop_reason: claudeStopReason(overrides?.finishReason, "end_turn"),
      stop_sequence: null,
    },
    usage: { output_tokens: claudeUsage(overrides).output_tokens },
  });

  // message_stop
  events.push({ type: "message_stop" });

  return events;
}

function buildClaudeToolCallStreamEvents(
  toolCalls: ToolCall[],
  model: string,
  chunkSize: number,
  logger: Logger,
  overrides?: ResponseOverrides,
): ClaudeSSEEvent[] {
  const msgId = overrides?.id ?? generateMessageId();
  const effectiveModel = overrides?.model ?? model;
  const events: ClaudeSSEEvent[] = [];

  // message_start
  events.push({
    type: "message_start",
    message: {
      id: msgId,
      type: "message",
      role: overrides?.role ?? "assistant",
      content: [],
      model: effectiveModel,
      stop_reason: null,
      stop_sequence: null,
      usage: claudeUsage(overrides),
    },
  });

  for (let idx = 0; idx < toolCalls.length; idx++) {
    const tc = toolCalls[idx];
    const toolUseId = tc.id || generateToolUseId();

    // Parse arguments to JSON object (Claude uses objects, not strings)
    let argsObj: unknown;
    try {
      argsObj = JSON.parse(tc.arguments || "{}");
    } catch {
      logger.warn(
        `Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`,
      );
      argsObj = {};
    }
    const argsJson = JSON.stringify(argsObj);

    // content_block_start
    events.push({
      type: "content_block_start",
      index: idx,
      content_block: {
        type: "tool_use",
        id: toolUseId,
        name: tc.name,
        input: {},
      },
    });

    // content_block_delta — input_json_delta chunks
    for (let i = 0; i < argsJson.length; i += chunkSize) {
      const slice = argsJson.slice(i, i + chunkSize);
      events.push({
        type: "content_block_delta",
        index: idx,
        delta: { type: "input_json_delta", partial_json: slice },
      });
    }

    // content_block_stop
    events.push({
      type: "content_block_stop",
      index: idx,
    });
  }

  // message_delta
  events.push({
    type: "message_delta",
    delta: {
      stop_reason: claudeStopReason(overrides?.finishReason, "tool_use"),
      stop_sequence: null,
    },
    usage: { output_tokens: claudeUsage(overrides).output_tokens },
  });

  // message_stop
  events.push({ type: "message_stop" });

  return events;
}

// Non-streaming response builders

function buildClaudeTextResponse(
  content: string,
  model: string,
  reasoning?: string,
  overrides?: ResponseOverrides,
): object {
  const contentBlocks: object[] = [];

  if (reasoning) {
    contentBlocks.push({ type: "thinking", thinking: reasoning, signature: "" });
  }

  contentBlocks.push({ type: "text", text: content });

  return {
    id: overrides?.id ?? generateMessageId(),
    type: "message",
    role: overrides?.role ?? "assistant",
    content: contentBlocks,
    model: overrides?.model ?? model,
    stop_reason: claudeStopReason(overrides?.finishReason, "end_turn"),
    stop_sequence: null,
    usage: claudeUsage(overrides),
  };
}

function buildClaudeToolCallResponse(
  toolCalls: ToolCall[],
  model: string,
  logger: Logger,
  overrides?: ResponseOverrides,
): object {
  return {
    id: overrides?.id ?? generateMessageId(),
    type: "message",
    role: overrides?.role ?? "assistant",
    content: toolCalls.map((tc) => {
      let argsObj: unknown;
      try {
        argsObj = JSON.parse(tc.arguments || "{}");
      } catch {
        logger.warn(
          `Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`,
        );
        argsObj = {};
      }
      return {
        type: "tool_use",
        id: tc.id || generateToolUseId(),
        name: tc.name,
        input: argsObj,
      };
    }),
    model: overrides?.model ?? model,
    stop_reason: claudeStopReason(overrides?.finishReason, "tool_use"),
    stop_sequence: null,
    usage: claudeUsage(overrides),
  };
}

function buildClaudeContentWithToolCallsStreamEvents(
  content: string,
  toolCalls: ToolCall[],
  model: string,
  chunkSize: number,
  logger: Logger,
  reasoning?: string,
  overrides?: ResponseOverrides,
): ClaudeSSEEvent[] {
  const msgId = overrides?.id ?? generateMessageId();
  const effectiveModel = overrides?.model ?? model;
  const events: ClaudeSSEEvent[] = [];

  // message_start
  events.push({
    type: "message_start",
    message: {
      id: msgId,
      type: "message",
      role: overrides?.role ?? "assistant",
      content: [],
      model: effectiveModel,
      stop_reason: null,
      stop_sequence: null,
      usage: claudeUsage(overrides),
    },
  });

  let blockIndex = 0;

  // Optional thinking block
  if (reasoning) {
    events.push({
      type: "content_block_start",
      index: blockIndex,
      content_block: { type: "thinking", thinking: "", signature: "" },
    });

    for (let i = 0; i < reasoning.length; i += chunkSize) {
      const slice = reasoning.slice(i, i + chunkSize);
      events.push({
        type: "content_block_delta",
        index: blockIndex,
        delta: { type: "thinking_delta", thinking: slice },
      });
    }

    events.push({
      type: "content_block_delta",
      index: blockIndex,
      delta: { type: "signature_delta", signature: "" },
    });

    events.push({
      type: "content_block_stop",
      index: blockIndex,
    });

    blockIndex++;
  }

  // Text content block
  events.push({
    type: "content_block_start",
    index: blockIndex,
    content_block: { type: "text", text: "" },
  });

  for (let i = 0; i < content.length; i += chunkSize) {
    const slice = content.slice(i, i + chunkSize);
    events.push({
      type: "content_block_delta",
      index: blockIndex,
      delta: { type: "text_delta", text: slice },
    });
  }

  events.push({
    type: "content_block_stop",
    index: blockIndex,
  });

  blockIndex++;

  // Tool use blocks
  for (const tc of toolCalls) {
    const toolUseId = tc.id || generateToolUseId();

    let argsObj: unknown;
    try {
      argsObj = JSON.parse(tc.arguments || "{}");
    } catch {
      logger.warn(
        `Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`,
      );
      argsObj = {};
    }
    const argsJson = JSON.stringify(argsObj);

    events.push({
      type: "content_block_start",
      index: blockIndex,
      content_block: {
        type: "tool_use",
        id: toolUseId,
        name: tc.name,
        input: {},
      },
    });

    for (let i = 0; i < argsJson.length; i += chunkSize) {
      const slice = argsJson.slice(i, i + chunkSize);
      events.push({
        type: "content_block_delta",
        index: blockIndex,
        delta: { type: "input_json_delta", partial_json: slice },
      });
    }

    events.push({
      type: "content_block_stop",
      index: blockIndex,
    });

    blockIndex++;
  }

  // message_delta
  events.push({
    type: "message_delta",
    delta: {
      stop_reason: claudeStopReason(overrides?.finishReason, "tool_use"),
      stop_sequence: null,
    },
    usage: { output_tokens: claudeUsage(overrides).output_tokens },
  });

  // message_stop
  events.push({ type: "message_stop" });

  return events;
}

function buildClaudeContentWithToolCallsResponse(
  content: string,
  toolCalls: ToolCall[],
  model: string,
  logger: Logger,
  reasoning?: string,
  overrides?: ResponseOverrides,
): object {
  const contentBlocks: object[] = [];

  if (reasoning) {
    contentBlocks.push({ type: "thinking", thinking: reasoning, signature: "" });
  }

  contentBlocks.push({ type: "text", text: content });

  for (const tc of toolCalls) {
    let argsObj: unknown;
    try {
      argsObj = JSON.parse(tc.arguments || "{}");
    } catch {
      logger.warn(
        `Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`,
      );
      argsObj = {};
    }
    contentBlocks.push({
      type: "tool_use",
      id: tc.id || generateToolUseId(),
      name: tc.name,
      input: argsObj,
    });
  }

  return {
    id: overrides?.id ?? generateMessageId(),
    type: "message",
    role: overrides?.role ?? "assistant",
    content: contentBlocks,
    model: overrides?.model ?? model,
    stop_reason: claudeStopReason(overrides?.finishReason, "tool_use"),
    stop_sequence: null,
    usage: claudeUsage(overrides),
  };
}

// ─── SSE writer for Claude Messages API ─────────────────────────────────────

interface ClaudeStreamOptions {
  latency?: number;
  streamingProfile?: StreamingProfile;
  recordedTimings?: RecordedTimings;
  replaySpeed?: number;
  signal?: AbortSignal;
  onChunkSent?: () => void;
}

async function writeClaudeSSEStream(
  res: http.ServerResponse,
  events: ClaudeSSEEvent[],
  optionsOrLatency?: number | ClaudeStreamOptions,
): Promise<boolean> {
  const opts: ClaudeStreamOptions =
    typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});
  const latency = opts.latency ?? 0;
  const profile = opts.streamingProfile;
  const { recordedTimings, replaySpeed } = opts;
  const signal = opts.signal;
  const onChunkSent = opts.onChunkSent;

  if (res.writableEnded) return true;
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  let chunkIndex = 0;
  for (const event of events) {
    const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
    if (chunkDelay > 0) await delay(chunkDelay, signal);
    if (signal?.aborted) return false;
    if (res.writableEnded) return true;
    res.write(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
    onChunkSent?.();
    if (signal?.aborted) return false;
    chunkIndex++;
  }

  if (!res.writableEnded) {
    res.end();
  }
  return true;
}

// ─── Request handler ────────────────────────────────────────────────────────

export async function handleMessages(
  req: http.IncomingMessage,
  res: http.ServerResponse,
  raw: string,
  fixtures: Fixture[],
  journal: Journal,
  defaults: HandlerDefaults,
  setCorsHeaders: (res: http.ServerResponse) => void,
): Promise<void> {
  const { logger } = defaults;
  setCorsHeaders(res);

  let claudeReq: ClaudeRequest;
  try {
    claudeReq = JSON.parse(raw) as ClaudeRequest;
  } catch (parseErr) {
    const detail = parseErr instanceof Error ? parseErr.message : "unknown";
    journal.add({
      method: req.method ?? "POST",
      path: req.url ?? "/v1/messages",
      headers: flattenHeaders(req.headers),
      body: null,
      response: { status: 400, fixture: null },
    });
    writeErrorResponse(
      res,
      400,
      JSON.stringify({
        error: {
          message: `Malformed JSON: ${detail}`,
          type: "invalid_request_error",
        },
      }),
    );
    return;
  }

  // Convert to ChatCompletionRequest for fixture matching
  const completionReq = claudeToCompletionRequest(claudeReq);
  completionReq._context = getContext(req);

  const testId = getTestId(req);
  const fixture = matchFixture(
    fixtures,
    completionReq,
    journal.getFixtureMatchCountsForTest(testId),
    defaults.requestTransform,
  );

  if (fixture) {
    journal.incrementFixtureMatchCount(fixture, fixtures, testId);
    logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
  } else {
    const lastUserMsg = completionReq.messages.filter((m) => m.role === "user").pop();
    const snippet =
      typeof lastUserMsg?.content === "string" ? lastUserMsg.content.slice(0, 80) : "";
    logger.debug(
      `No fixture matched for request (model=${completionReq.model ?? "?"}, msg="${snippet}")`,
    );
  }

  if (
    applyChaos(
      res,
      fixture,
      defaults.chaos,
      req.headers,
      journal,
      {
        method: req.method ?? "POST",
        path: req.url ?? "/v1/messages",
        headers: flattenHeaders(req.headers),
        body: completionReq,
      },
      fixture ? "fixture" : "proxy",
      defaults.registry,
      defaults.logger,
    )
  )
    return;

  if (!fixture) {
    const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);
    if (effectiveStrict) {
      const strictStatus = 503;
      const strictMessage = "Strict mode: no fixture matched";
      logger.error(
        `STRICT: No fixture matched for ${req.method ?? "POST"} ${req.url ?? "/v1/messages"}`,
      );
      journal.add({
        method: req.method ?? "POST",
        path: req.url ?? "/v1/messages",
        headers: flattenHeaders(req.headers),
        body: completionReq,
        response: {
          status: strictStatus,
          fixture: null,
          ...strictOverrideField(defaults.strict, req.headers),
        },
      });
      writeErrorResponse(
        res,
        strictStatus,
        JSON.stringify({
          error: {
            message: strictMessage,
            type: "invalid_request_error",
          },
        }),
      );
      return;
    }
    if (defaults.record) {
      const outcome = await proxyAndRecord(
        req,
        res,
        completionReq,
        "anthropic",
        req.url ?? "/v1/messages",
        fixtures,
        defaults,
        raw,
      );
      if (outcome === "handled_by_hook") return;
      if (outcome !== "not_configured") {
        journal.add({
          method: req.method ?? "POST",
          path: req.url ?? "/v1/messages",
          headers: flattenHeaders(req.headers),
          body: completionReq,
          response: { status: res.statusCode ?? 200, fixture: null, source: "proxy" },
        });
        return;
      }
    }
    journal.add({
      method: req.method ?? "POST",
      path: req.url ?? "/v1/messages",
      headers: flattenHeaders(req.headers),
      body: completionReq,
      response: {
        status: 404,
        fixture: null,
        ...strictOverrideField(defaults.strict, req.headers),
      },
    });
    writeErrorResponse(
      res,
      404,
      JSON.stringify({
        error: {
          message: "No fixture matched",
          type: "invalid_request_error",
        },
      }),
    );
    return;
  }

  const response = await resolveResponse(fixture, completionReq);
  const latency = fixture.latency ?? defaults.latency;
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);

  // Error response
  if (isErrorResponse(response)) {
    const status = response.status ?? 500;
    journal.add({
      method: req.method ?? "POST",
      path: req.url ?? "/v1/messages",
      headers: flattenHeaders(req.headers),
      body: completionReq,
      response: { status, fixture },
    });
    // Anthropic-style error format: { type: "error", error: { type, message } }
    const anthropicError = {
      type: "error",
      error: {
        type: response.error.type ?? "api_error",
        message: response.error.message,
      },
    };
    writeErrorResponse(res, status, JSON.stringify(anthropicError), {
      retryAfter: response.retryAfter,
    });
    return;
  }

  // Content + tool calls response (must be checked before text/tool-only branches)
  if (isContentWithToolCallsResponse(response)) {
    if (response.webSearches?.length) {
      logger.warn(
        "webSearches in fixture response are not supported for Claude Messages API — ignoring",
      );
    }
    const overrides = extractOverrides(response);
    const journalEntry = journal.add({
      method: req.method ?? "POST",
      path: req.url ?? "/v1/messages",
      headers: flattenHeaders(req.headers),
      body: completionReq,
      response: { status: 200, fixture },
    });
    if (claudeReq.stream !== true) {
      const body = buildClaudeContentWithToolCallsResponse(
        response.content,
        response.toolCalls,
        completionReq.model,
        logger,
        response.reasoning,
        overrides,
      );
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(body));
    } else {
      const events = buildClaudeContentWithToolCallsStreamEvents(
        response.content,
        response.toolCalls,
        completionReq.model,
        chunkSize,
        logger,
        response.reasoning,
        overrides,
      );
      const interruption = createInterruptionSignal(fixture);
      const completed = await writeClaudeSSEStream(res, events, {
        latency,
        streamingProfile: fixture.streamingProfile,
        recordedTimings: fixture.recordedTimings,
        replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
        signal: interruption?.signal,
        onChunkSent: interruption?.tick,
      });
      if (!completed) {
        if (!res.writableEnded) res.destroy();
        journalEntry.response.interrupted = true;
        journalEntry.response.interruptReason = interruption?.reason();
      }
      interruption?.cleanup();
    }
    return;
  }

  // Text response
  if (isTextResponse(response)) {
    if (response.webSearches?.length) {
      logger.warn(
        "webSearches in fixture response are not supported for Claude Messages API — ignoring",
      );
    }
    const overrides = extractOverrides(response);
    const journalEntry = journal.add({
      method: req.method ?? "POST",
      path: req.url ?? "/v1/messages",
      headers: flattenHeaders(req.headers),
      body: completionReq,
      response: { status: 200, fixture },
    });
    if (claudeReq.stream !== true) {
      const body = buildClaudeTextResponse(
        response.content,
        completionReq.model,
        response.reasoning,
        overrides,
      );
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(body));
    } else {
      const events = buildClaudeTextStreamEvents(
        response.content,
        completionReq.model,
        chunkSize,
        response.reasoning,
        overrides,
      );
      const interruption = createInterruptionSignal(fixture);
      const completed = await writeClaudeSSEStream(res, events, {
        latency,
        streamingProfile: fixture.streamingProfile,
        recordedTimings: fixture.recordedTimings,
        replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
        signal: interruption?.signal,
        onChunkSent: interruption?.tick,
      });
      if (!completed) {
        if (!res.writableEnded) res.destroy();
        journalEntry.response.interrupted = true;
        journalEntry.response.interruptReason = interruption?.reason();
      }
      interruption?.cleanup();
    }
    return;
  }

  // Tool call response
  if (isToolCallResponse(response)) {
    if (response.webSearches?.length) {
      logger.warn(
        "webSearches in fixture response are not supported for Claude Messages API — ignoring",
      );
    }
    const overrides = extractOverrides(response);
    const journalEntry = journal.add({
      method: req.method ?? "POST",
      path: req.url ?? "/v1/messages",
      headers: flattenHeaders(req.headers),
      body: completionReq,
      response: { status: 200, fixture },
    });
    if (claudeReq.stream !== true) {
      const body = buildClaudeToolCallResponse(
        response.toolCalls,
        completionReq.model,
        logger,
        overrides,
      );
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify(body));
    } else {
      const events = buildClaudeToolCallStreamEvents(
        response.toolCalls,
        completionReq.model,
        chunkSize,
        logger,
        overrides,
      );
      const interruption = createInterruptionSignal(fixture);
      const completed = await writeClaudeSSEStream(res, events, {
        latency,
        streamingProfile: fixture.streamingProfile,
        recordedTimings: fixture.recordedTimings,
        replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
        signal: interruption?.signal,
        onChunkSent: interruption?.tick,
      });
      if (!completed) {
        if (!res.writableEnded) res.destroy();
        journalEntry.response.interrupted = true;
        journalEntry.response.interruptReason = interruption?.reason();
      }
      interruption?.cleanup();
    }
    return;
  }

  // Unknown response type
  journal.add({
    method: req.method ?? "POST",
    path: req.url ?? "/v1/messages",
    headers: flattenHeaders(req.headers),
    body: completionReq,
    response: { status: 500, fixture },
  });
  writeErrorResponse(
    res,
    500,
    JSON.stringify({
      error: {
        message: "Fixture response did not match any known type",
        type: "server_error",
      },
    }),
  );
}
