import type { SessionSdkImage } from "./AgentSdkAdapter.ts";

/** Gemini `generateContent` user part — text or inline image bytes. */
export type GeminiContentPart =
  | { text: string }
  | { inlineData: { mimeType: string; data: string } };

const DEFAULT_MAX_IMAGE_BYTES = 10 * 1024 * 1024;

function normalizeBase64Payload(data: string): string {
  const trimmed = data.trim();
  const m = /^data:[^;]+;base64,(.+)$/is.exec(trimmed);
  return (m?.[1] ?? trimmed).replace(/\s/g, "");
}

function guessMimeFromUrl(url: string): string {
  const path = url.split("?")[0]!.toLowerCase();
  if (path.endsWith(".png")) return "image/png";
  if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
  if (path.endsWith(".gif")) return "image/gif";
  if (path.endsWith(".webp")) return "image/webp";
  if (path.endsWith(".svg")) return "image/svg+xml";
  return "application/octet-stream";
}

function parseMimeFromContentType(header: string | null): string | null {
  if (!header) return null;
  const primary = header.split(";")[0]?.trim().toLowerCase();
  return primary && primary.startsWith("image/") ? primary : null;
}

async function fetchUrlAsInlineData(
  url: string,
  opts: { fetchImpl: typeof fetch; maxImageBytes: number },
): Promise<{ mimeType: string; data: string } | null> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 30_000);
  try {
    const res = await opts.fetchImpl(url, {
      signal: controller.signal,
      redirect: "follow",
    });
    if (!res.ok) return null;
    const buf = Buffer.from(await res.arrayBuffer());
    if (buf.byteLength === 0 || buf.byteLength > opts.maxImageBytes) return null;
    const mimeType =
      parseMimeFromContentType(res.headers.get("content-type")) ??
      guessMimeFromUrl(url);
    if (!mimeType.startsWith("image/")) return null;
    return { mimeType, data: buf.toString("base64") };
  } catch {
    return null;
  } finally {
    clearTimeout(timer);
  }
}

async function sessionSdkImageToInlineData(
  image: SessionSdkImage,
  opts: { fetchImpl: typeof fetch; maxImageBytes: number },
): Promise<{ mimeType: string; data: string } | null> {
  if ("data" in image && image.data) {
    const data = normalizeBase64Payload(image.data);
    if (!data) return null;
    const mimeType = image.mimeType?.trim() || "image/png";
    return { mimeType, data };
  }
  if ("url" in image && image.url) {
    return fetchUrlAsInlineData(image.url, opts);
  }
  return null;
}

/**
 * Build Gemini user `parts` from session text + optional multimodal attachments.
 * Images are sent as `inlineData` per @google/genai generateContent API.
 */
export async function buildGeminiUserParts(
  userText: string,
  images?: SessionSdkImage[],
  opts?: { fetchImpl?: typeof fetch; maxImageBytes?: number },
): Promise<GeminiContentPart[]> {
  const fetchImpl = opts?.fetchImpl ?? fetch;
  const maxImageBytes = opts?.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
  const parts: GeminiContentPart[] = [];

  if (images && images.length > 0) {
    for (const image of images) {
      const inline = await sessionSdkImageToInlineData(image, {
        fetchImpl,
        maxImageBytes,
      });
      if (inline) {
        parts.push({ inlineData: inline });
      }
    }
  }

  const trimmed = userText.trim();
  if (trimmed) {
    parts.push({ text: trimmed });
  } else if (parts.length === 0) {
    parts.push({ text: "" });
  }

  return parts;
}
