import { readFileSync } from "fs";
import { GraphAILogger } from "graphai";
import Replicate from "replicate";
import { apiKeyMissingError, agentGenerationError, agentInvalidResponseError, hasCause, imageAction, movieFileTarget, videoDurationTarget, unsupportedModelTarget, } from "../utils/error_cause.js";
import { provider2MovieAgent, getModelDuration, AUDIO_MODE_OPTIONAL, AUDIO_MODE_NEVER, AUDIO_MODE_ALWAYS } from "../types/provider2agent.js";
function replicate_get_videoUrl(output) {
    if (typeof output === "string")
        return output;
    if (output && typeof output === "object" && "url" in output)
        return output.url();
    return undefined;
}
async function generateMovie(model, apiKey, prompt, imagePath, lastFrameImagePath, referenceImages, aspectRatio, duration, generateAudio) {
    const replicate = new Replicate({
        auth: apiKey,
    });
    const input = {
        prompt,
        duration,
        image: undefined,
        start_image: undefined,
        first_frame_image: undefined,
        aspect_ratio: aspectRatio, // only for bytedance/seedance-1-lite
        // resolution: "720p", // only for bytedance/seedance-1-lite
        // fps: 24, // only for bytedance/seedance-1-lite
        // camera_fixed: false, // only for bytedance/seedance-1-lite
        // mode: "standard" // only for kwaivgi/kling-v2.1
        // negative_prompt: "" // only for kwaivgi/kling-v2.1
    };
    // Add image if provided (for image-to-video generation)
    if (imagePath) {
        const buffer = readFileSync(imagePath);
        const base64Image = `data:image/png;base64,${buffer.toString("base64")}`;
        const start_image = provider2MovieAgent.replicate.modelParams[model]?.start_image;
        if (start_image === "first_frame_image" || start_image === "image" || start_image === "start_image") {
            input[start_image] = base64Image;
        }
        else if (start_image === undefined) {
            throw new Error(`Model ${model} does not support image-to-video generation`, {
                cause: agentGenerationError("movieReplicateAgent", imageAction, unsupportedModelTarget),
            });
        }
        else {
            input.image = base64Image;
        }
    }
    // Add reference images if provided and model supports it
    const referenceImagesParam = provider2MovieAgent.replicate.modelParams[model]?.reference_images_param;
    if (referenceImages && referenceImages.length > 0) {
        if (!referenceImagesParam) {
            GraphAILogger.warn(`movieReplicateAgent: model ${model} does not support referenceImages — ignoring`);
        }
        else if (imagePath) {
            GraphAILogger.warn(`movieReplicateAgent: referenceImages cannot be combined with first frame image — ignoring referenceImages`);
        }
        else {
            input[referenceImagesParam] = referenceImages.map((ref) => {
                const buffer = readFileSync(ref.imagePath);
                return `data:image/png;base64,${buffer.toString("base64")}`;
            });
        }
    }
    // Add last frame image if provided and model supports it
    if (lastFrameImagePath) {
        const lastImageParam = provider2MovieAgent.replicate.modelParams[model]?.last_image;
        if (lastImageParam) {
            if (!imagePath) {
                GraphAILogger.warn(`movieReplicateAgent: model ${model} requires a first frame image to use lastFrame — ignoring lastFrameImageName`);
            }
            else {
                const buffer = readFileSync(lastFrameImagePath);
                const base64Image = `data:image/png;base64,${buffer.toString("base64")}`;
                input[lastImageParam] = base64Image;
            }
        }
        else {
            GraphAILogger.warn(`movieReplicateAgent: model ${model} does not support lastFrame — ignoring lastFrameImageName`);
        }
    }
    // Add generate_audio if the model supports it
    const audio = provider2MovieAgent.replicate.modelParams[model].audio;
    if (generateAudio !== undefined) {
        if (audio.mode === AUDIO_MODE_OPTIONAL) {
            input[audio.param] = generateAudio;
        }
        else if (audio.mode === AUDIO_MODE_NEVER && generateAudio === true) {
            throw new Error(`Model ${model} does not support audio generation`, {
                cause: agentGenerationError("movieReplicateAgent", imageAction, unsupportedModelTarget),
            });
        }
        else if (audio.mode === AUDIO_MODE_ALWAYS && generateAudio === false) {
            GraphAILogger.warn(`movieReplicateAgent: model ${model} always generates audio — ignoring generateAudio=false`);
        }
    }
    try {
        const output = await replicate.run(model, { input });
        // Download the generated video
        // Some models return a FileOutput object with a url() method; others return a plain string URL.
        const videoUrl = replicate_get_videoUrl(output);
        if (videoUrl) {
            const videoResponse = await fetch(videoUrl);
            if (!videoResponse.ok) {
                throw new Error(`Error downloading video: ${videoResponse.status} - ${videoResponse.statusText}`, {
                    cause: agentGenerationError("movieReplicateAgent", imageAction, movieFileTarget),
                });
            }
            const arrayBuffer = await videoResponse.arrayBuffer();
            return Buffer.from(arrayBuffer);
        }
        return undefined;
    }
    catch (error) {
        GraphAILogger.info("Replicate generation error:", error);
        throw error;
    }
}
export const getAspectRatio = (canvasSize) => {
    const ratio = canvasSize.width / canvasSize.height;
    const tolerance = 0.1;
    if (ratio > 4 / 3 + tolerance)
        return "16:9";
    if (ratio > 4 / 3 - tolerance)
        return "4:3";
    if (ratio > 3 / 4 + tolerance)
        return "1:1";
    if (ratio > 3 / 4 - tolerance)
        return "3:4";
    return "9:16";
};
export const movieReplicateAgent = async ({ namedInputs, params, config, }) => {
    const { prompt, imagePath, lastFrameImagePath, referenceImages } = namedInputs;
    const aspectRatio = getAspectRatio(params.canvasSize);
    const model = params.model ?? provider2MovieAgent.replicate.defaultModel;
    if (!provider2MovieAgent.replicate.modelParams[model]) {
        throw new Error(`Model ${model} is not supported`, {
            cause: agentGenerationError("movieReplicateAgent", imageAction, unsupportedModelTarget),
        });
    }
    if (provider2MovieAgent.replicate.modelParams[model].start_image_required && !imagePath) {
        throw new Error(`Model ${model} requires a start image (image-to-video only)`, {
            cause: agentGenerationError("movieReplicateAgent", imageAction, unsupportedModelTarget),
        });
    }
    const duration = getModelDuration("replicate", model, params.duration);
    if (duration === undefined || !provider2MovieAgent.replicate.modelParams[model].durations.includes(duration)) {
        throw new Error(`Duration ${duration} is not supported for model ${model}. Supported durations: ${provider2MovieAgent.replicate.modelParams[model].durations.join(", ")}`, {
            cause: agentGenerationError("movieReplicateAgent", imageAction, videoDurationTarget),
        });
    }
    const apiKey = config?.apiKey;
    if (!apiKey) {
        throw new Error("Replicate API key is required (REPLICATE_API_TOKEN)", {
            cause: apiKeyMissingError("movieReplicateAgent", imageAction, "REPLICATE_API_TOKEN"),
        });
    }
    try {
        const buffer = await generateMovie(model, apiKey, prompt, imagePath, lastFrameImagePath, referenceImages, aspectRatio, duration, params.generateAudio);
        if (buffer) {
            return { buffer };
        }
    }
    catch (error) {
        if (hasCause(error)) {
            throw error;
        }
        GraphAILogger.info("Failed to generate movie:", error.message);
    }
    throw new Error("ERROR: generateMovie returned undefined", {
        cause: agentInvalidResponseError("movieReplicateAgent", imageAction, movieFileTarget),
    });
};
const movieReplicateAgentInfo = {
    name: "movieReplicateAgent",
    agent: movieReplicateAgent,
    mock: movieReplicateAgent,
    samples: [],
    description: "Replicate Movie agent using seedance-1-lite",
    category: ["movie"],
    author: "Receptron Team",
    repository: "https://github.com/receptron/mulmocast-cli/",
    license: "MIT",
    environmentVariables: ["REPLICATE_API_TOKEN"],
};
export default movieReplicateAgentInfo;
