import { createHash } from "node:crypto";
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";

const MAX_GENERATED_IMAGE_BYTES = 15 * 1024 * 1024;
const IMAGE_TYPES = new Map([
  [".gif", "image/gif"],
  [".jpeg", "image/jpeg"],
  [".jpg", "image/jpeg"],
  [".png", "image/png"],
  [".webp", "image/webp"]
]);

export function projectGeneratedImages({ text, session, workspacePath, dataDir }) {
  if (typeof text !== "string" || !text.includes("![") || !session?.id || !workspacePath || !dataDir) return text;

  return text.replace(/!\[([^\]]*)\]\((?:<([^>]+)>|([^\s)]+))(?:\s+["'][^"']*["'])?\)/g, (markdown, alt, wrappedTarget, plainTarget) => {
    const source = generatedImageSource(wrappedTarget ?? plainTarget, workspacePath);
    if (!source) return markdown;

    const contents = readFileSync(source.path);
    const id = `generated-${createHash("sha256").update(contents).digest("hex").slice(0, 24)}`;
    const storedPath = join(dataDir, "attachments", session.id, `${id}${source.extension}`);
    if (!existsSync(storedPath)) {
      mkdirSync(join(dataDir, "attachments", session.id), { recursive: true });
      copyFileSync(source.path, storedPath);
    }
    if (!(session.attachments ?? []).some((item) => item.id === id)) {
      session.attachments = [...(session.attachments ?? []), {
        id,
        kind: "image",
        name: `Generated image${source.extension}`,
        mimeType: source.mimeType,
        path: storedPath,
        createdAt: new Date().toISOString()
      }];
    }
    return `![${alt}](/api/sessions/${encodeURIComponent(session.id)}/attachments/${id})`;
  });
}

export function projectGeneratedImageItem({ item, session, dataDir }) {
  if (!session?.id || !dataDir || !item || !["imageGeneration", "dynamicToolCall"].includes(item.type)) return [];
  const candidates = generatedItemCandidates(item);
  const projected = [];
  for (const candidate of candidates) {
    const source = generatedItemSource(candidate);
    if (!source) continue;
    const attachment = storeGeneratedImage(source, session, dataDir);
    if (!attachment || projected.some((image) => image.id === attachment.id)) continue;
    projected.push({
      id: attachment.id,
      kind: "image",
      name: attachment.name,
      mimeType: attachment.mimeType,
      url: `/api/sessions/${encodeURIComponent(session.id)}/attachments/${attachment.id}`
    });
  }
  return projected;
}

function generatedItemCandidates(item) {
  const values = [];
  if (typeof item.savedPath === "string") values.push(item.savedPath);
  if (typeof item.result === "string") values.push(item.result);
  for (const content of item.contentItems ?? []) {
    if (content?.type === "inputImage" && typeof content.imageUrl === "string") values.push(content.imageUrl);
  }
  return [...new Set(values)];
}

function generatedItemSource(candidate) {
  try {
    if (/^https?:/i.test(candidate) || candidate.includes("[redacted]")) return null;
    let contents;
    if (/^data:/i.test(candidate)) {
      const match = candidate.match(/^data:(image\/(?:png|jpeg|gif|webp));base64,([A-Za-z0-9+/=\r\n]+)$/i);
      if (!match || match[2].length > Math.ceil(MAX_GENERATED_IMAGE_BYTES * 4 / 3) + 8) return null;
      contents = Buffer.from(match[2], "base64");
    } else {
      const decoded = decodeURIComponent(String(candidate).replace(/^file:\/\//i, ""));
      if (!isAbsolute(decoded)) return null;
      const stats = statSync(decoded);
      if (!stats.isFile() || stats.size <= 0 || stats.size > MAX_GENERATED_IMAGE_BYTES) return null;
      contents = readFileSync(decoded);
    }
    if (!contents.length || contents.length > MAX_GENERATED_IMAGE_BYTES) return null;
    const detected = detectedImageType(contents);
    return detected ? { contents, ...detected } : null;
  } catch {
    return null;
  }
}

function detectedImageType(contents) {
  if (contents.length >= 8 && contents.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
    return { extension: ".png", mimeType: "image/png" };
  }
  if (contents.length >= 3 && contents[0] === 0xff && contents[1] === 0xd8 && contents[2] === 0xff) {
    return { extension: ".jpg", mimeType: "image/jpeg" };
  }
  if (contents.length >= 6 && ["GIF87a", "GIF89a"].includes(contents.subarray(0, 6).toString("ascii"))) {
    return { extension: ".gif", mimeType: "image/gif" };
  }
  if (contents.length >= 12 && contents.subarray(0, 4).toString("ascii") === "RIFF" && contents.subarray(8, 12).toString("ascii") === "WEBP") {
    return { extension: ".webp", mimeType: "image/webp" };
  }
  return null;
}

function storeGeneratedImage(source, session, dataDir) {
  const id = `generated-${createHash("sha256").update(source.contents).digest("hex").slice(0, 24)}`;
  const storedPath = join(dataDir, "attachments", session.id, `${id}${source.extension}`);
  if (!existsSync(storedPath)) {
    mkdirSync(join(dataDir, "attachments", session.id), { recursive: true });
    writeFileSync(storedPath, source.contents);
  }
  let attachment = (session.attachments ?? []).find((entry) => entry.id === id);
  if (!attachment) {
    attachment = {
      id,
      kind: "image",
      name: `Generated image${source.extension}`,
      mimeType: source.mimeType,
      path: storedPath,
      createdAt: new Date().toISOString()
    };
    session.attachments = [...(session.attachments ?? []), attachment];
  }
  return attachment;
}

function generatedImageSource(target, workspacePath) {
  try {
    const decoded = decodeURIComponent(String(target).replace(/^file:\/\//i, ""));
    if (!isAbsolute(decoded)) return null;
    const workspace = realpathSync(workspacePath);
    const source = realpathSync(resolve(decoded));
    const inside = relative(workspace, source);
    if (!inside || inside.startsWith("..") || isAbsolute(inside)) return null;

    const extension = extname(basename(source)).toLowerCase();
    const mimeType = IMAGE_TYPES.get(extension);
    const stats = statSync(source);
    if (!mimeType || !stats.isFile() || stats.size > MAX_GENERATED_IMAGE_BYTES) return null;
    return { path: source, extension, mimeType };
  } catch {
    return null;
  }
}
