import { spawn } from "node:child_process";
import { readFile, rm, stat, writeFile, mkdir } from "node:fs/promises";
import { extname, join, resolve } from "node:path";
import { homedir, tmpdir } from "node:os";
import { randomBytes } from "node:crypto";
import { logEvent } from "./logger.js";
import { detectImageMimeFromB64 } from "./refs.js";
const AGY_TIMEOUT_MS = 360_000;
const AGY_OUTPUT_RESOLUTION = "1024x1024";
const AGY_MAX_OUTPUT_BYTES = 1024 * 1024;
function agyError(message, status, code) {
    const err = new Error(message);
    err.status = status;
    err.code = code;
    return err;
}
function buildAgyPrompt(userPrompt, referencePaths) {
    const imagePathsJson = referencePaths.length > 0
        ? JSON.stringify(referencePaths)
        : "[]";
    return [
        "Please generate one image by calling the tool default_api:generate_image once.",
        "After the tool finishes, print one machine-readable result line so ima2-gen can copy the artifact.",
        "",
        "Tool parameters:",
        `  Prompt: ${JSON.stringify(userPrompt)}`,
        '  ImageName: "ima2_generated"',
        `  ImagePaths: ${imagePathsJson}`,
        '  toolSummary: "ima2 pipeline generation"',
        '  toolAction: "Generating ima2 image"',
        "",
        `Reference count: ${referencePaths.length}. The output resolution is fixed at ${AGY_OUTPUT_RESOLUTION}.`,
        "If generation succeeds, print: RESULT|<absolute_artifact_path>|<file_extension>",
        "If generation fails, print: ERROR|<concise error message>",
    ].join("\n");
}
function parseAgyOutput(stdout) {
    const lines = stdout.trim().split("\n").filter((l) => l.trim().length > 0);
    const resultLine = lines.find((l) => l.startsWith("RESULT|"));
    if (resultLine) {
        const parts = resultLine.split("|");
        if (parts.length >= 3) {
            return { artifactPath: parts[1], ext: parts[2] };
        }
        throw agyError(`Malformed RESULT line: ${resultLine}`, 502, "AGY_MALFORMED_RESULT");
    }
    const errorLine = lines.find((l) => l.startsWith("ERROR|"));
    if (errorLine) {
        const msg = errorLine.slice("ERROR|".length).trim() || "Unknown agy error";
        const lower = msg.toLowerCase();
        if (lower.includes("resource exhausted") || lower.includes("exhausted your capacity") || lower.includes("quota will reset")) {
            throw agyError(`Agy generation failed: ${msg}`, 429, "AGY_QUOTA_EXHAUSTED");
        }
        throw agyError(`Agy generation failed: ${msg}`, 502, "AGY_GENERATION_FAILED");
    }
    const fullLower = stdout.toLowerCase();
    if (fullLower.includes("resource exhausted") || fullLower.includes("exhausted your capacity")) {
        throw agyError(`Agy quota exhausted: ${stdout.trim().slice(0, 200)}`, 429, "AGY_QUOTA_EXHAUSTED");
    }
    const savedPathLine = lines.find((l) => l.startsWith("SAVED_PATH="));
    if (savedPathLine) {
        const p = savedPathLine.slice("SAVED_PATH=".length).trim();
        const ext = p.split(".").pop() || "png";
        return { artifactPath: p, ext };
    }
    const normalizedStdout = stdout.replace(/\\/g, "/");
    const pathMatch = normalizedStdout.match(/\/[^\s"']+\/(brain|artifacts)\/[^\s"']+\.(png|jpg|jpeg|webp)/i);
    if (pathMatch) {
        const artifactPath = process.platform === "win32" ? pathMatch[0].replace(/\//g, "\\") : pathMatch[0];
        const ext = extname(artifactPath).slice(1) || "png";
        return { artifactPath, ext };
    }
    throw agyError(`Could not parse artifact path from agy output (${stdout.length} chars): ${stdout.slice(0, 200)}`, 502, "AGY_PARSE_FAILED");
}
function spawnAgy(prompt, signal) {
    return new Promise((resolve, reject) => {
        const child = spawn("agy", ["-p", "-"], {
            stdio: ["pipe", "pipe", "pipe"],
            env: {
                PATH: process.env.PATH,
                HOME: process.env.HOME,
                USERPROFILE: process.env.USERPROFILE,
                TMPDIR: process.env.TMPDIR,
                TEMP: process.env.TEMP,
                LANG: process.env.LANG,
                GEMINI_API_KEY: process.env.GEMINI_API_KEY,
            },
        });
        let stdout = "";
        let stderr = "";
        let settled = false;
        const timer = setTimeout(() => {
            if (!settled) {
                settled = true;
                child.kill("SIGTERM");
                reject(agyError("Agy generation timed out", 504, "AGY_TIMEOUT"));
            }
        }, AGY_TIMEOUT_MS);
        child.stdout.on("data", (chunk) => { if (stdout.length < AGY_MAX_OUTPUT_BYTES)
            stdout += chunk.toString(); });
        child.stderr.on("data", (chunk) => { if (stderr.length < AGY_MAX_OUTPUT_BYTES)
            stderr += chunk.toString(); });
        child.on("error", (err) => {
            if (settled)
                return;
            settled = true;
            clearTimeout(timer);
            reject(agyError(`Agy process error: ${err.message}`, 502, "AGY_PROCESS_ERROR"));
        });
        child.on("close", (code) => {
            if (settled)
                return;
            settled = true;
            clearTimeout(timer);
            if (code !== 0 && !stdout.trim()) {
                reject(agyError(`Agy exited with code ${code}: ${stderr.slice(0, 200)}`, 502, "AGY_PROCESS_ERROR"));
                return;
            }
            resolve({ stdout, stderr });
        });
        if (signal) {
            const onAbort = () => {
                if (!settled) {
                    settled = true;
                    clearTimeout(timer);
                    child.kill("SIGTERM");
                    reject(agyError("Generation canceled", 499, "GENERATION_CANCELED"));
                }
            };
            signal.addEventListener("abort", onAbort, { once: true });
            child.on("close", () => signal.removeEventListener("abort", onAbort));
        }
        if (signal?.aborted) {
            settled = true;
            clearTimeout(timer);
            child.kill("SIGTERM");
            return reject(agyError("Generation canceled", 499, "GENERATION_CANCELED"));
        }
        child.stdin.on("error", () => { });
        child.stdin.write(prompt);
        child.stdin.end();
    });
}
const MIME_TO_EXT = {
    "image/png": "png",
    "image/jpeg": "jpg",
    "image/webp": "webp",
};
async function writeRefsToTempFiles(refs) {
    if (refs.length === 0)
        return { paths: [], cleanup: async () => { } };
    const dir = join(tmpdir(), `ima2-agy-refs-${randomBytes(6).toString("hex")}`);
    await mkdir(dir, { recursive: true });
    const paths = [];
    for (let i = 0; i < refs.length; i++) {
        const ref = refs[i];
        const mime = ref.detectedMime || ref.declaredMime || detectImageMimeFromB64(ref.b64) || "image/png";
        const ext = MIME_TO_EXT[mime] || "png";
        const p = join(dir, `ref_${i}.${ext}`);
        await writeFile(p, Buffer.from(ref.b64, "base64"));
        paths.push(p);
    }
    return {
        paths,
        cleanup: async () => {
            await rm(dir, { recursive: true, force: true }).catch(() => { });
        },
    };
}
export async function generateViaAgy(prompt, options = {}) {
    const refDetails = (options.references || []).slice(0, 3);
    const { paths: refPaths, cleanup } = await writeRefsToTempFiles(refDetails);
    const agyPrompt = buildAgyPrompt(prompt, refPaths);
    logEvent("agy", "generate:start", {
        requestId: options.requestId,
        promptChars: prompt.length,
        agyPromptChars: agyPrompt.length,
        refs: refPaths.length,
    });
    try {
        const { stdout, stderr } = await spawnAgy(agyPrompt, options.signal);
        if (stderr && stderr.trim().length > 0) {
            logEvent("agy", "generate:stderr", {
                requestId: options.requestId,
                stderrChars: stderr.length,
                stderrPreview: stderr.slice(0, 200),
            });
        }
        const { artifactPath } = parseAgyOutput(stdout);
        // Validate artifact path is within allowed directories
        const resolvedPath = resolve(artifactPath);
        const allowedPrefixes = [
            join(homedir(), ".gemini"),
            join(homedir(), ".cache"),
            tmpdir(),
        ];
        const normalizedResolved = resolvedPath.replace(/\\/g, "/");
        const isSafePath = allowedPrefixes.some((prefix) => {
            const normalizedPrefix = prefix.replace(/\\/g, "/");
            return normalizedResolved.startsWith(normalizedPrefix + "/") || normalizedResolved === normalizedPrefix;
        });
        if (!isSafePath) {
            throw agyError(`Agy artifact path outside allowed directories: ${resolvedPath}`, 502, "AGY_PATH_REJECTED");
        }
        try {
            await stat(resolvedPath);
        }
        catch {
            throw agyError(`Agy artifact not found at parsed path: ${resolvedPath}`, 502, "AGY_ARTIFACT_NOT_FOUND");
        }
        const buffer = await readFile(resolvedPath);
        const b64 = buffer.toString("base64");
        const mime = detectImageMimeFromB64(b64) || "image/png";
        logEvent("agy", "generate:done", {
            requestId: options.requestId,
            artifactPath,
            b64Len: b64.length,
            mime,
            fileBytes: buffer.length,
        });
        return {
            b64,
            revisedPrompt: prompt,
            usage: { agy_artifact_bytes: buffer.length },
            webSearchCalls: 0,
            mime,
        };
    }
    finally {
        await cleanup();
    }
}
