"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KIEClient = void 0;
exports.createKIEClient = createKIEClient;
const undici_1 = require("undici");
const logger_js_1 = require("../../utils/logger.js");
const config_js_1 = require("../../utils/config.js");
const logger = (0, logger_js_1.createModuleLogger)('kie-client');
const KIE_API_BASE = 'https://api.kie.ai/api/v1';
const MAX_RETRIES = 3;
const INITIAL_BACKOFF_MS = 1000;
const MAX_REFERENCE_IMAGES = 8;
const FLUX_KONTEXT_MODELS = [
    'flux-kontext-pro', 'flux-kontext-max',
];
const MARKET_MODELS = [
    'nano-banana-2', 'flux-2', 'flux-2/pro-image-to-image', 'seedream-4-5', 'seedream5-0-lite', 'z-image', '4o-image',
];
const VALID_MODELS = [...FLUX_KONTEXT_MODELS, ...MARKET_MODELS];
const VALID_ASPECT_RATIOS = [
    '1:1', '16:9', '9:16', '4:3', '3:4', '21:9',
];
function isFluxKontextModel(model) {
    return FLUX_KONTEXT_MODELS.includes(model);
}
class KIEClient {
    apiKey;
    initialBackoffMs;
    constructor(apiKey, options) {
        this.apiKey = apiKey || '';
        this.initialBackoffMs = options?.initialBackoffMs ?? INITIAL_BACKOFF_MS;
    }
    async createTask(request) {
        this.validateRequest(request);
        if (isFluxKontextModel(request.model)) {
            return this.createFluxKontextTask(request);
        }
        return this.createMarketTask(request);
    }
    async getTaskStatus(taskId) {
        if (!taskId) {
            throw new Error('taskId is required');
        }
        // Determine endpoint based on taskId prefix convention
        const isFluxKontext = taskId.startsWith('task_flux_');
        const url = isFluxKontext
            ? `${KIE_API_BASE}/flux/kontext/record-info?taskId=${encodeURIComponent(taskId)}`
            : `${KIE_API_BASE}/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`;
        const envelope = await this.sendRequest('GET', url);
        return this.unwrapEnvelope(envelope);
    }
    async createFluxKontextTask(request) {
        const body = {
            prompt: request.prompt,
            aspectRatio: request.aspectRatio,
            model: request.model,
            enableTranslation: request.enableTranslation ?? true,
            outputFormat: request.outputFormat ?? 'jpeg',
            safetyTolerance: request.safetyTolerance ?? 6,
        };
        if (request.webhookUrl) {
            body.callBackUrl = request.webhookUrl;
        }
        if (request.inputImage) {
            body.inputImage = request.inputImage;
        }
        if (request.negativePrompt) {
            body.negativePrompt = request.negativePrompt;
        }
        if (request.seed !== undefined) {
            body.seed = request.seed;
        }
        const url = `${KIE_API_BASE}/flux/kontext/generate`;
        const envelope = await this.sendRequest('POST', url, body);
        return this.unwrapEnvelope(envelope);
    }
    async createMarketTask(request) {
        const input = {
            prompt: request.prompt,
            aspect_ratio: request.aspectRatio,
        };
        if (request.negativePrompt) {
            input.negative_prompt = request.negativePrompt;
        }
        // flux-2/pro-image-to-image uses input_urls and resolution
        if (request.model === 'flux-2/pro-image-to-image') {
            const imgs = request.inputUrls ?? request.imageInput ?? request.referenceImages;
            if (imgs && imgs.length > 0) {
                input.input_urls = imgs.slice(0, 8);
            }
            input.resolution = request.resolution ?? '1K';
        }
        // nano-banana-2 uses image_input instead of reference_images
        else if (request.model === 'nano-banana-2') {
            const imgs = request.imageInput ?? request.referenceImages;
            if (imgs && imgs.length > 0) {
                input.image_input = imgs;
            }
            input.resolution = request.resolution ?? '1K';
            input.output_format = request.outputFormat ?? 'jpg';
            if (request.googleSearch !== undefined) {
                input.google_search = request.googleSearch;
            }
        }
        else {
            if (request.referenceImages) {
                input.reference_images = request.referenceImages;
            }
        }
        if (request.seed !== undefined) {
            input.seed = request.seed;
        }
        if (request.enableTranslation !== undefined) {
            input.enable_translation = request.enableTranslation;
        }
        const body = {
            model: request.model,
            input,
        };
        if (request.webhookUrl) {
            body.callBackUrl = request.webhookUrl;
        }
        const url = `${KIE_API_BASE}/jobs/createTask`;
        const envelope = await this.sendRequest('POST', url, body);
        return this.unwrapEnvelope(envelope);
    }
    unwrapEnvelope(envelope) {
        if (envelope.code !== 200) {
            throw new Error(`KIE API returned error code ${envelope.code}: ${envelope.msg ?? 'unknown error'}`);
        }
        return envelope.data;
    }
    validateRequest(request) {
        if (!request.prompt) {
            throw new Error('prompt is required');
        }
        if (!VALID_MODELS.includes(request.model)) {
            throw new Error(`Invalid model: ${request.model}. Valid models: ${VALID_MODELS.join(', ')}`);
        }
        if (!VALID_ASPECT_RATIOS.includes(request.aspectRatio)) {
            throw new Error(`Invalid aspectRatio: ${request.aspectRatio}. Valid ratios: ${VALID_ASPECT_RATIOS.join(', ')}`);
        }
        if (request.referenceImages && request.referenceImages.length > MAX_REFERENCE_IMAGES) {
            throw new Error(`referenceImages exceeds maximum of ${MAX_REFERENCE_IMAGES}`);
        }
    }
    async sendRequest(method, url, body) {
        let lastError;
        for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
            try {
                logger.debug({ attempt, method, url }, 'Sending KIE API request');
                const options = {
                    method,
                    headers: {
                        'Content-Type': 'application/json',
                        Authorization: `Bearer ${this.apiKey}`,
                    },
                };
                if (body !== undefined) {
                    options.body = JSON.stringify(body);
                }
                const response = await (0, undici_1.fetch)(url, options);
                if (!response.ok) {
                    const errorText = await response.text();
                    const err = new Error(`KIE API error: ${response.status} ${response.statusText} - ${errorText}`);
                    logger.warn({ status: response.status, attempt, errorText }, 'KIE API request failed');
                    if (response.status >= 400 && response.status < 500 && response.status !== 429) {
                        throw err;
                    }
                    lastError = err;
                }
                else {
                    const data = (await response.json());
                    logger.debug({ method, url }, 'KIE API request successful');
                    return data;
                }
            }
            catch (error) {
                if (lastError === undefined || lastError !== error) {
                    lastError = error instanceof Error ? error : new Error(String(error));
                }
                if (lastError.message.includes('KIE API error: 4') && !lastError.message.includes('429')) {
                    throw lastError;
                }
                logger.warn({ attempt, error: lastError.message }, 'KIE request attempt failed');
            }
            if (attempt < MAX_RETRIES) {
                const backoff = this.initialBackoffMs * Math.pow(2, attempt - 1);
                logger.debug({ backoff, attempt }, 'Waiting before retry');
                await sleep(backoff);
            }
        }
        logger.error({ error: lastError?.message }, 'All KIE API retry attempts exhausted');
        throw lastError ?? new Error('KIE API request failed after all retries');
    }
    /**
     * Creates a task and polls until completion, returning the image URL.
     * Supports both market models (nano-banana-2 etc.) and flux kontext models.
     * Polls every 5 seconds, max 60 attempts (5 minutes timeout).
     */
    async generateAndWait(request) {
        const taskResponse = await this.createTask(request);
        const taskId = taskResponse.taskId;
        logger.info({ taskId, model: request.model }, 'KIE task created, starting to poll for completion');
        const POLL_INTERVAL = 5000;
        const MAX_ATTEMPTS = 60;
        for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            await sleep(POLL_INTERVAL);
            logger.debug({ taskId, attempt, maxAttempts: MAX_ATTEMPTS }, 'Polling KIE task status');
            const status = await this.getTaskStatus(taskId);
            if (isFluxKontextModel(request.model)) {
                // Flux kontext: successFlag 1=success, 2=create_failed, 3=generate_failed
                const flag = status.successFlag;
                if (flag === 1) {
                    const imageUrl = status.response?.resultImageUrl;
                    if (!imageUrl) {
                        throw new Error(`KIE task ${taskId} succeeded but no resultImageUrl found`);
                    }
                    logger.info({ taskId, attempt }, 'KIE flux kontext task completed successfully');
                    return imageUrl;
                }
                else if (flag === 2 || flag === 3) {
                    throw new Error(`KIE task ${taskId} failed: ${status.errorMessage || `successFlag=${flag}`}`);
                }
            }
            else {
                // Market models (nano-banana-2): state === 'success' means done
                const state = status.state;
                logger.debug({ taskId, state, attempt }, 'Market model poll status');
                if (state === 'success') {
                    let imageUrl;
                    if (status.resultJson) {
                        try {
                            const parsed = JSON.parse(status.resultJson);
                            if (typeof parsed === 'object' && !Array.isArray(parsed) && Array.isArray(parsed.resultUrls) && parsed.resultUrls.length > 0) {
                                imageUrl = parsed.resultUrls[0];
                            }
                            else if (typeof parsed === 'string') {
                                imageUrl = parsed;
                            }
                            else if (Array.isArray(parsed)) {
                                imageUrl = parsed[0]?.url || parsed[0]?.image_url || parsed[0];
                            }
                            else if (typeof parsed === 'object') {
                                imageUrl = parsed.url || parsed.image_url || parsed.resultImageUrl || parsed.output;
                            }
                        }
                        catch {
                            imageUrl = status.resultJson;
                        }
                    }
                    if (!imageUrl) {
                        throw new Error(`KIE task ${taskId} succeeded but no image URL found in resultJson`);
                    }
                    logger.info({ taskId, attempt, imageUrl }, 'KIE market model task completed successfully');
                    return imageUrl;
                }
                else if (state === 'failed' || state === 'fail') {
                    throw new Error(`KIE task ${taskId} failed: ${status.failMsg || 'unknown error'}`);
                }
            }
            if (attempt % 12 === 0) {
                logger.info({ taskId, attempt, maxAttempts: MAX_ATTEMPTS }, 'Still waiting for KIE task completion...');
            }
        }
        throw new Error(`KIE task ${taskId} timed out after ${MAX_ATTEMPTS * POLL_INTERVAL / 1000} seconds`);
    }
}
exports.KIEClient = KIEClient;
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
function createKIEClient() {
    return new KIEClient(config_js_1.config.kieApiKey);
}
//# sourceMappingURL=kie-client.js.map