import { VideoBufferType, VideoFrame } from "@livekit/rtc-node";
import sharp from "sharp";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { z } from "zod";
import {
  AgentHandoffItem,
  ChatContext,
  ChatMessage,
  FunctionCall,
  FunctionCallOutput
} from "./chat_context.js";
import { tool } from "./tool_context.js";
import {
  computeChatCtxDiff,
  executeToolCall,
  formatChatHistory,
  parseFunctionArguments,
  serializeImage,
  validateChatContextStructure
} from "./utils.js";
function createChatMessage(id, content, role = "user") {
  return ChatMessage.create({
    id,
    content,
    role
  });
}
function createChatContext(messages) {
  const ctx = new ChatContext();
  for (const message of messages) {
    ctx.items.push(message);
  }
  return ctx;
}
function createImageContent(image, inferenceDetail = "auto", options) {
  return {
    id: "test-id",
    type: "image_content",
    image,
    inferenceDetail,
    mimeType: options == null ? void 0 : options.mimeType,
    inferenceWidth: options == null ? void 0 : options.inferenceWidth,
    inferenceHeight: options == null ? void 0 : options.inferenceHeight,
    _cache: {}
  };
}
async function decodeImageToRaw(base64Data) {
  const imageBuffer = Buffer.from(base64Data, "base64");
  const decodedImage = await sharp(imageBuffer).raw().toBuffer({ resolveWithObject: true });
  return { imageBuffer, decodedImage };
}
function createSolidColorFrame(width, height, color, bufferType = VideoBufferType.RGBA) {
  const channels = bufferType === VideoBufferType.RGB24 ? 3 : 4;
  const frameData = new Uint8Array(width * height * channels);
  for (let i = 0; i < frameData.length; i += channels) {
    frameData[i] = color.r;
    frameData[i + 1] = color.g;
    frameData[i + 2] = color.b;
    if (channels === 4 && color.a !== void 0) {
      frameData[i + 3] = color.a;
    }
  }
  return new VideoFrame(frameData, width, height, bufferType);
}
function createGradientFrame(width, height) {
  const channels = 4;
  const frameData = new Uint8Array(width * height * channels);
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const idx = (y * width + x) * channels;
      frameData[idx] = Math.floor(x / (width - 1) * 255);
      frameData[idx + 1] = Math.floor(y / (height - 1) * 255);
      frameData[idx + 2] = 128;
      frameData[idx + 3] = 255;
    }
  }
  return new VideoFrame(frameData, width, height, VideoBufferType.RGBA);
}
function createPatternFrame(width, height, patterns) {
  const channels = 4;
  const frameData = new Uint8Array(width * height * channels);
  for (let i = 0; i < patterns.length; i++) {
    const offset = i * 4;
    const pattern = patterns[i];
    frameData[offset] = pattern[0];
    frameData[offset + 1] = pattern[1];
    frameData[offset + 2] = pattern[2];
    frameData[offset + 3] = pattern[3];
  }
  return new VideoFrame(frameData, width, height, VideoBufferType.RGBA);
}
function verifyPngHeader(imageBuffer) {
  expect(imageBuffer[0]).toBe(137);
  expect(imageBuffer[1]).toBe(80);
  expect(imageBuffer[2]).toBe(78);
  expect(imageBuffer[3]).toBe(71);
}
function expectPixel(data, index, expected) {
  expect(data[index]).toBe(expected.r);
  expect(data[index + 1]).toBe(expected.g);
  expect(data[index + 2]).toBe(expected.b);
  expect(data[index + 3]).toBe(expected.a);
}
describe("parseFunctionArguments", () => {
  it("should parse strict JSON arguments", () => {
    expect(parseFunctionArguments('{"a": 1, "b": [2, 3]}')).toEqual({ a: 1, b: [2, 3] });
    expect(parseFunctionArguments("{}")).toEqual({});
    expect(parseFunctionArguments("null")).toEqual({});
  });
  it("should repair malformed JSON arguments", () => {
    expect(parseFunctionArguments('{"arg1": "hi", "optArg2": "yo"')).toEqual({
      arg1: "hi",
      optArg2: "yo"
    });
    expect(parseFunctionArguments("{arg1: 'hi'}")).toEqual({ arg1: "hi" });
  });
  it("should strip leaked chat-template tokens after repair", () => {
    const leaked = '{"orderId": ["<|\\"|\\"O_WAAB70<|\\"|\\"]}';
    expect(parseFunctionArguments(leaked)).toEqual({ orderId: ["O_WAAB70"] });
  });
  it("should leave clean chat-template-like strings unchanged", () => {
    expect(parseFunctionArguments('{"arg1": "<|safe|>"}')).toEqual({ arg1: "<|safe|>" });
  });
  it("should accept pre-parsed argument objects", () => {
    const args = { arg1: "hi", optArg2: "yo" };
    expect(parseFunctionArguments(args)).toEqual(args);
  });
  it("should reject non-object argument shapes", () => {
    expect(() => parseFunctionArguments("[1, 2, 3]")).toThrow(/expected object/);
    expect(() => parseFunctionArguments('"just a string"')).toThrow(/non-JSON string/);
  });
});
describe("executeToolCall", () => {
  it("should canonicalize repaired arguments before returning", async () => {
    const removeOrderItem = tool({
      description: "remove order item",
      parameters: z.object({ orderId: z.array(z.string()) }),
      execute: async ({ orderId }) => orderId.join(",")
    });
    const rawArgs = '{"orderId": ["<|\\"|\\"O_WAAB70<|\\"|\\"]}';
    const toolCall = FunctionCall.create({
      callId: "call-canon-1",
      name: "removeOrderItem",
      args: rawArgs
    });
    const result = await executeToolCall(toolCall, { removeOrderItem });
    expect(result.isError).toBe(false);
    expect(JSON.parse(result.output)).toBe("O_WAAB70");
    expect(toolCall.args).not.toBe(rawArgs);
    expect(JSON.parse(toolCall.args)).toEqual({ orderId: ["O_WAAB70"] });
  });
  it("should preserve valid argument structure during execution", async () => {
    const echo = tool({
      description: "echo",
      parameters: z.object({ arg1: z.string(), optArg2: z.string().optional() }),
      execute: async ({ arg1, optArg2 }) => ({ arg1, optArg2 })
    });
    const originalArgs = '{"arg1": "hello", "optArg2": "<|safe|>"}';
    const toolCall = FunctionCall.create({
      callId: "call-valid-1",
      name: "echo",
      args: originalArgs
    });
    const result = await executeToolCall(toolCall, { echo });
    expect(result.isError).toBe(false);
    expect(JSON.parse(result.output)).toEqual({ arg1: "hello", optArg2: "<|safe|>" });
    expect(JSON.parse(toolCall.args)).toEqual({ arg1: "hello", optArg2: "<|safe|>" });
  });
});
describe("computeChatCtxDiff", () => {
  it("should return empty operations for identical contexts", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const oldCtx = createChatContext([msg1, msg2]);
    const newCtx = createChatContext([msg1, msg2]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual([]);
    expect(result.toCreate).toEqual([]);
  });
  it("should handle empty old context", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const oldCtx = createChatContext([]);
    const newCtx = createChatContext([msg1, msg2]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual([]);
    expect(result.toCreate).toEqual([
      [null, "1"],
      // first item goes to root
      ["1", "2"]
    ]);
  });
  it("should handle empty new context", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const oldCtx = createChatContext([msg1, msg2]);
    const newCtx = createChatContext([]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual(["1", "2"]);
    expect(result.toCreate).toEqual([]);
  });
  it("should handle adding items to the end", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const oldCtx = createChatContext([msg1, msg2]);
    const newCtx = createChatContext([msg1, msg2, msg3]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual([]);
    expect(result.toCreate).toEqual([["2", "3"]]);
  });
  it("should handle removing items from the end", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const oldCtx = createChatContext([msg1, msg2, msg3]);
    const newCtx = createChatContext([msg1, msg2]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual(["3"]);
    expect(result.toCreate).toEqual([]);
  });
  it("should handle adding items to the beginning", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const oldCtx = createChatContext([msg2, msg3]);
    const newCtx = createChatContext([msg1, msg2, msg3]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual([]);
    expect(result.toCreate).toEqual([[null, "1"]]);
  });
  it("should handle removing items from the beginning", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const oldCtx = createChatContext([msg1, msg2, msg3]);
    const newCtx = createChatContext([msg2, msg3]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual(["1"]);
    expect(result.toCreate).toEqual([]);
  });
  it("should handle adding items in the middle", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const msg4 = createChatMessage("4", "Fine thanks", "assistant");
    const oldCtx = createChatContext([msg1, msg3, msg4]);
    const newCtx = createChatContext([msg1, msg2, msg3, msg4]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual([]);
    expect(result.toCreate).toEqual([["1", "2"]]);
  });
  it("should handle removing items from the middle", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const msg4 = createChatMessage("4", "Fine thanks", "assistant");
    const oldCtx = createChatContext([msg1, msg2, msg3, msg4]);
    const newCtx = createChatContext([msg1, msg3, msg4]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual(["2"]);
    expect(result.toCreate).toEqual([]);
  });
  it("should handle complex mixed operations", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const msg4 = createChatMessage("4", "Fine thanks", "assistant");
    const msg5 = createChatMessage("5", "Good to hear", "user");
    const msg6 = createChatMessage("6", "Anything else?", "assistant");
    const oldCtx = createChatContext([msg1, msg2, msg3, msg4]);
    const newCtx = createChatContext([msg1, msg5, msg3, msg6]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual(["2", "4"]);
    expect(result.toCreate).toEqual([
      ["1", "5"],
      ["3", "6"]
    ]);
  });
  it("should handle reordering items", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const oldCtx = createChatContext([msg1, msg2, msg3]);
    const newCtx = createChatContext([msg3, msg1, msg2]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove.length + result.toCreate.length).toBeGreaterThan(0);
  });
  it("should handle identical single item contexts", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const oldCtx = createChatContext([msg1]);
    const newCtx = createChatContext([msg1]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual([]);
    expect(result.toCreate).toEqual([]);
  });
  it("should handle longest common subsequence correctly", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const msg4 = createChatMessage("4", "Fine thanks", "assistant");
    const msg5 = createChatMessage("5", "Good to hear", "user");
    const oldCtx = createChatContext([msg1, msg2, msg3, msg4, msg5]);
    const newCtx = createChatContext([msg1, msg3, msg5]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual(["2", "4"]);
    expect(result.toCreate).toEqual([]);
  });
  it("should handle interleaved additions and common subsequence", () => {
    const msg1 = createChatMessage("1", "Hello", "user");
    const msg2 = createChatMessage("2", "Hi there", "assistant");
    const msg3 = createChatMessage("3", "How are you?", "user");
    const msg4 = createChatMessage("4", "Fine thanks", "assistant");
    const msg5 = createChatMessage("5", "Good to hear", "user");
    const msg6 = createChatMessage("6", "Anything else?", "assistant");
    const oldCtx = createChatContext([msg1, msg3, msg5]);
    const newCtx = createChatContext([msg1, msg2, msg3, msg4, msg5, msg6]);
    const result = computeChatCtxDiff(oldCtx, newCtx);
    expect(result.toRemove).toEqual([]);
    expect(result.toCreate).toEqual([
      ["1", "2"],
      ["3", "4"],
      ["5", "6"]
    ]);
  });
});
describe("formatChatHistory", () => {
  it("should format mixed chat history items for logging", () => {
    const ctx = new ChatContext([
      ChatMessage.create({
        id: "msg_system",
        role: "system",
        content: "You are helpful.",
        createdAt: 1
      }),
      ChatMessage.create({
        id: "msg_user",
        role: "user",
        content: [
          "Show me my order",
          createImageContent("https://example.com/receipt.png"),
          { type: "audio_content", frame: [], transcript: "order 123" }
        ],
        createdAt: 2
      }),
      FunctionCall.create({
        id: "fn_call",
        callId: "call_1",
        name: "lookup_order",
        args: '{"orderId":"123"}',
        createdAt: 3
      }),
      FunctionCallOutput.create({
        id: "fn_output",
        callId: "call_1",
        name: "lookup_order",
        output: '{"status":"shipped","ok":true}',
        isError: false,
        createdAt: 4
      }),
      AgentHandoffItem.create({
        id: "handoff",
        oldAgentId: "support",
        newAgentId: "returns",
        createdAt: 5
      })
    ]);
    expect(formatChatHistory(ctx)).toBe(
      [
        "Chat history (5 items)",
        "",
        "[0] message system",
        "  You are helpful.",
        "",
        "[1] message user",
        "  Show me my order",
        "  [image url=https://example.com/receipt.png]",
        '  [audio transcript="order 123"]',
        "",
        "[2] function_call lookup_order call_id=call_1",
        "  {",
        '    "orderId": "123"',
        "  }",
        "",
        "[3] function_call_output lookup_order call_id=call_1",
        "  {",
        '    "status": "shipped",',
        '    "ok": true',
        "  }",
        "",
        "[4] agent_handoff",
        "  support -> returns"
      ].join("\n")
    );
  });
  it("should optionally include ids and timestamps", () => {
    const ctx = new ChatContext([
      FunctionCallOutput.create({
        id: "fn_output",
        callId: "call_1",
        name: "lookup_order",
        output: "plain text error",
        isError: true,
        createdAt: 123.456
      })
    ]);
    expect(formatChatHistory(ctx, { includeIds: true, includeTimestamps: true })).toBe(
      [
        "Chat history (1 items)",
        "",
        "[0] function_call_output lookup_order call_id=call_1 error=true id=fn_output created_at=123.456",
        "  plain text error"
      ].join("\n")
    );
  });
});
describe("validateChatContextStructure", () => {
  it("returns valid=true for well-formed chat context", () => {
    const ctx = new ChatContext([
      ChatMessage.create({
        id: "msg_user",
        role: "user",
        content: ["hello"],
        createdAt: 1
      }),
      FunctionCall.create({
        id: "fn_call",
        callId: "call_1",
        name: "lookup_order",
        args: '{"orderId":"123"}',
        createdAt: 2
      }),
      FunctionCallOutput.create({
        id: "fn_output",
        callId: "call_1",
        name: "lookup_order",
        output: '{"ok":true}',
        isError: false,
        createdAt: 3
      })
    ]);
    const result = validateChatContextStructure(ctx);
    expect(result.valid).toBe(true);
    expect(result.errors).toBe(0);
    expect(result.warnings).toBe(0);
    expect(result.issues).toEqual([]);
  });
  it("detects duplicate ids and timestamp ordering issues", () => {
    const m1 = ChatMessage.create({
      id: "dup_id",
      role: "user",
      content: ["hello"],
      createdAt: 10
    });
    const m2 = ChatMessage.create({
      id: "dup_id",
      role: "assistant",
      content: ["world"],
      createdAt: 5
    });
    const ctx = new ChatContext([m1, m2]);
    const result = validateChatContextStructure(ctx);
    expect(result.valid).toBe(false);
    expect(result.errors).toBeGreaterThanOrEqual(2);
    expect(result.issues.some((i) => i.code === "duplicate_id")).toBe(true);
    expect(result.issues.some((i) => i.code === "timestamp_order")).toBe(true);
  });
  it("detects malformed terms and orphan function outputs", () => {
    const msg = ChatMessage.create({
      id: "msg_1",
      role: "user",
      content: ["   "],
      createdAt: 1
    });
    const output = FunctionCallOutput.create({
      id: "fn_out_1",
      callId: "call_missing",
      name: "lookup_order",
      output: "ok",
      isError: false,
      createdAt: 2
    });
    const ctx = new ChatContext([msg, output]);
    const result = validateChatContextStructure(ctx);
    expect(result.valid).toBe(true);
    expect(result.errors).toBe(0);
    expect(result.warnings).toBeGreaterThanOrEqual(2);
    expect(result.issues.some((i) => i.code === "empty_text_term")).toBe(true);
    expect(result.issues.some((i) => i.code === "orphan_function_call_output")).toBe(true);
  });
});
describe("serializeImage", () => {
  let consoleWarnSpy;
  beforeEach(() => {
    consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {
    });
    vi.clearAllMocks();
  });
  describe("Data URL handling", () => {
    it("should serialize a valid JPEG data URL", async () => {
      const originalBase64 = "/9j/4AAQSkZJRg==";
      const imageContent = createImageContent(`data:image/jpeg;base64,${originalBase64}`, "high");
      const result = await serializeImage(imageContent);
      expect(result).toEqual({
        base64Data: originalBase64,
        mimeType: "image/jpeg",
        inferenceDetail: "high"
      });
      expect(result.base64Data).toBe(originalBase64);
    });
    it("should serialize a valid PNG data URL", async () => {
      const imageContent = createImageContent(
        "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB",
        "low"
      );
      const result = await serializeImage(imageContent);
      expect(result).toEqual({
        base64Data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB",
        mimeType: "image/png",
        inferenceDetail: "low"
      });
    });
    it("should serialize a valid WebP data URL", async () => {
      const imageContent = createImageContent(
        "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAA",
        "auto"
      );
      const result = await serializeImage(imageContent);
      expect(result).toEqual({
        base64Data: "UklGRiQAAABXRUJQVlA4IBgAAAA",
        mimeType: "image/webp",
        inferenceDetail: "auto"
      });
    });
    it("should serialize a valid GIF data URL", async () => {
      const imageContent = createImageContent(
        "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==",
        "auto"
      );
      const result = await serializeImage(imageContent);
      expect(result).toEqual({
        base64Data: "R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==",
        mimeType: "image/gif",
        inferenceDetail: "auto"
      });
    });
    it("should warn and use provided mimeType when it differs from data URL mime type", async () => {
      const imageContent = createImageContent("data:image/jpeg;base64,/9j/4AAQSkZJRg==", "auto", {
        mimeType: "image/png"
      });
      const result = await serializeImage(imageContent);
      expect(consoleWarnSpy).toHaveBeenCalledWith(
        "Provided mimeType 'image/png' does not match data URL mime type 'image/jpeg'. Using provided mimeType."
      );
      expect(result).toEqual({
        base64Data: "/9j/4AAQSkZJRg==",
        mimeType: "image/png",
        inferenceDetail: "auto"
      });
    });
    it("should throw error for invalid data URL format", async () => {
      const imageContent = createImageContent("data:;base64,/9j/4AAQSkZJRg==", "auto");
      await expect(serializeImage(imageContent)).rejects.toThrow("Invalid data URL format");
    });
    it("should throw error for unsupported mime type", async () => {
      const imageContent = createImageContent(
        "data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAA",
        "auto"
      );
      await expect(serializeImage(imageContent)).rejects.toThrow(
        "Unsupported mimeType image/bmp. Must be jpeg, png, webp, or gif"
      );
    });
  });
  describe("External URL handling", () => {
    it("should serialize an external URL without mimeType", async () => {
      const imageContent = createImageContent("https://example.com/image.jpg", "high");
      const result = await serializeImage(imageContent);
      expect(result).toEqual({
        mimeType: void 0,
        inferenceDetail: "high",
        externalUrl: "https://example.com/image.jpg"
      });
    });
    it("should serialize an external URL with mimeType", async () => {
      const imageContent = createImageContent("https://example.com/image.jpg", "low", {
        mimeType: "image/jpeg"
      });
      const result = await serializeImage(imageContent);
      expect(result).toEqual({
        mimeType: "image/jpeg",
        inferenceDetail: "low",
        externalUrl: "https://example.com/image.jpg"
      });
    });
  });
  describe("VideoFrame handling", () => {
    it("should serialize a VideoFrame without resize parameters", async () => {
      const width = 4;
      const height = 4;
      const videoFrame = createSolidColorFrame(width, height, { r: 255, g: 0, b: 0, a: 255 });
      const imageContent = createImageContent(videoFrame, "auto");
      const result = await serializeImage(imageContent);
      expect(result).toMatchObject({
        mimeType: "image/png",
        inferenceDetail: "auto"
      });
      expect(result.base64Data).toBeDefined();
      expect(result.base64Data).toMatch(/^[A-Za-z0-9+/]+=*$/);
      expect(result.externalUrl).toBeUndefined();
      const { imageBuffer, decodedImage } = await decodeImageToRaw(result.base64Data);
      verifyPngHeader(imageBuffer);
      expect(decodedImage.info.width).toBe(width);
      expect(decodedImage.info.height).toBe(height);
      expect(decodedImage.info.channels).toBe(4);
      const decodedData = decodedImage.data;
      for (let i = 0; i < decodedData.length; i += 4) {
        expectPixel(decodedData, i, { r: 255, g: 0, b: 0, a: 255 });
      }
    });
    it("should serialize a VideoFrame with a gradient pattern", async () => {
      const width = 8;
      const height = 8;
      const videoFrame = createGradientFrame(width, height);
      const imageContent = createImageContent(videoFrame, "high");
      const result = await serializeImage(imageContent);
      expect(result).toMatchObject({
        mimeType: "image/png",
        inferenceDetail: "high"
      });
      const { decodedImage } = await decodeImageToRaw(result.base64Data);
      expect(decodedImage.info.width).toBe(width);
      expect(decodedImage.info.height).toBe(height);
      const decodedData = decodedImage.data;
      for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
          const idx = (y * width + x) * 4;
          const expectedR = Math.floor(x / (width - 1) * 255);
          const expectedG = Math.floor(y / (height - 1) * 255);
          expectPixel(decodedData, idx, { r: expectedR, g: expectedG, b: 128, a: 255 });
        }
      }
    });
    it("should preserve exact pixel data through serialization", async () => {
      const width = 2;
      const height = 2;
      const patterns = [
        [255, 0, 0, 255],
        [0, 255, 0, 255],
        [0, 0, 255, 255],
        [255, 255, 255, 255]
      ];
      const videoFrame = createPatternFrame(width, height, patterns);
      const imageContent = createImageContent(videoFrame, "low");
      const result = await serializeImage(imageContent);
      const { decodedImage } = await decodeImageToRaw(result.base64Data);
      const decoded = decodedImage.data;
      expectPixel(decoded, 0, { r: 255, g: 0, b: 0, a: 255 });
      expectPixel(decoded, 4, { r: 0, g: 255, b: 0, a: 255 });
      expectPixel(decoded, width * 4, { r: 0, g: 0, b: 255, a: 255 });
      expectPixel(decoded, (width + 1) * 4, { r: 255, g: 255, b: 255, a: 255 });
    });
    it("should handle resize parameters correctly", async () => {
      const width = 2;
      const height = 2;
      const videoFrame = createSolidColorFrame(width, height, { r: 100, g: 100, b: 100, a: 255 });
      const imageContent = createImageContent(videoFrame, "auto", {
        inferenceWidth: 4,
        inferenceHeight: 4
      });
      const result = await serializeImage(imageContent);
      const { decodedImage } = await decodeImageToRaw(result.base64Data);
      expect(decodedImage.info.width).toBe(4);
      expect(decodedImage.info.height).toBe(4);
      const decodedData = decodedImage.data;
      for (let i = 0; i < decodedData.length; i += 4) {
        expect(decodedData[i]).toBeCloseTo(100, -1);
        expect(decodedData[i + 1]).toBeCloseTo(100, -1);
        expect(decodedData[i + 2]).toBeCloseTo(100, -1);
        expect(decodedData[i + 3]).toBe(255);
      }
    });
    it("should handle RGB24 VideoBufferType correctly", async () => {
      const width = 2;
      const height = 2;
      const channels = 3;
      const frameData = new Uint8Array(width * height * channels);
      for (let i = 0; i < frameData.length; i += channels) {
        frameData[i] = 255;
        frameData[i + 1] = 128;
        frameData[i + 2] = 64;
      }
      const videoFrame = new VideoFrame(frameData, width, height, VideoBufferType.RGB24);
      const imageContent = createImageContent(videoFrame, "auto");
      const result = await serializeImage(imageContent);
      expect(result.mimeType).toBe("image/png");
      const { decodedImage } = await decodeImageToRaw(result.base64Data);
      expect(decodedImage.info.channels).toBeGreaterThanOrEqual(3);
      const decodedData = decodedImage.data;
      const decodedChannels = decodedImage.info.channels;
      for (let i = 0; i < decodedData.length; i += decodedChannels) {
        expect(decodedData[i]).toBe(255);
        expect(decodedData[i + 1]).toBe(128);
        expect(decodedData[i + 2]).toBe(64);
        if (decodedChannels === 4) {
          expect(decodedData[i + 3]).toBe(255);
        }
      }
    });
    it("should handle different RGBA-like formats correctly", async () => {
      const width = 1;
      const height = 1;
      const testFormats = [
        VideoBufferType.RGBA,
        VideoBufferType.BGRA,
        VideoBufferType.ARGB,
        VideoBufferType.ABGR
      ];
      for (const format of testFormats) {
        const frameData = new Uint8Array([100, 150, 200, 250]);
        const videoFrame = new VideoFrame(frameData, width, height, format);
        const imageContent = createImageContent(videoFrame, "auto");
        const result = await serializeImage(imageContent);
        expect(result.mimeType).toBe("image/png");
        expect(result.base64Data).toBeDefined();
      }
    });
  });
  describe("Error handling", () => {
    it("should throw error for unsupported image type", async () => {
      const imageContent = createImageContent(123, "auto");
      await expect(serializeImage(imageContent)).rejects.toThrow("Unsupported image type");
    });
  });
});
//# sourceMappingURL=utils.test.js.map