"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveImageAttachment = resolveImageAttachment;
exports.generate = generate;
exports.browseGenerationJobs = browseGenerationJobs;
exports.showGenerationJob = showGenerationJob;
exports.generations = generations;
const types_1 = require("@playdrop/types");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const commandContext_1 = require("../commandContext");
const errors_1 = require("../errors");
const messages_1 = require("../messages");
const output_1 = require("../output");
const IMAGE_ASPECT_RATIO_SET = new Set(['1:1', '3:4', '9:16', '4:3', '16:9']);
const IMAGE_SIZE_SET = new Set(['1K', '4K']);
const VIDEO_ASPECT_RATIO_SET = new Set(['16:9', '9:16']);
const DEFAULT_SUBCATEGORY_BY_MODALITY = {
    IMAGE: 'generic',
    MUSIC: 'music',
    SFX: 'sfx',
    SPEECH: 'speech',
    VIDEO: 'generic',
    MODEL_3D: 'generic',
};
const DEFAULT_IMAGE_ASPECT_RATIO = '1:1';
const DEFAULT_IMAGE_SIZE = '1K';
const DEFAULT_VIDEO_ASPECT_RATIO = '16:9';
const DEFAULT_VIDEO_DURATION_SECONDS = 4;
const VIDEO_FIRST_LAST_FRAME_DURATION_SECONDS = 8;
const DEFAULT_MODEL3D_TOPOLOGY = 'triangle';
const DEFAULT_MODEL3D_TARGET_POLYCOUNT = 30000;
const DEFAULT_MODEL3D_SHOULD_REMESH = true;
const DEFAULT_MODEL3D_SHOULD_TEXTURE = true;
const GENERATE_DEFAULT_TIMEOUT_SECONDS = 900;
const GENERATE_DEFAULT_POLL_INTERVAL_MS = 1000;
const LOCAL_IMAGE_MIME_BY_EXTENSION = {
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.webp': 'image/webp',
};
function isPlaydropSpeechVoiceId(value) {
    return types_1.PLAYDROP_SPEECH_VOICE_IDS.includes(value);
}
function handleGenerateBuildRequestError(message) {
    const errorByCode = {
        missing_type: {
            title: 'A generation type is required.',
            details: ['Use one of: image, music, sfx, speech, video, model_3d.'],
        },
        missing_prompt: {
            title: 'A prompt is required.',
            details: ['Example: playdrop ai create image "A pixel art hero portrait"'],
        },
        invalid_type: {
            title: 'The generation type is invalid.',
            details: ['Use one of: image, music, sfx, speech, video, model_3d.'],
        },
        invalid_duration: {
            title: 'The --duration value is invalid.',
            details: ['Use a positive value like 20000, 20s, or 20000ms.'],
        },
        invalid_video_duration: {
            title: 'The --duration value for video is invalid.',
            details: ['Use 4s or 8s for video generation.'],
        },
        invalid_aspect_ratio: {
            title: 'The --ratio value is invalid.',
            details: ['Image: 1:1, 3:4, 9:16, 4:3, 16:9. Video: 16:9, 9:16.'],
        },
        invalid_image_size: {
            title: 'The --size value is invalid.',
            details: ['Image size must be 1K or 4K.'],
        },
        image_size_not_supported_for_modality: {
            title: 'The --size option is only supported for image generation.',
            details: ['Use --size 1K or --size 4K with "playdrop ai create image ...".'],
        },
        missing_model3d_images: {
            title: 'Model 3D IMAGE source mode requires at least one reference image.',
            details: ['Provide --image1 <url> or --image2 <url>, or switch to --source-mode TEXT.'],
        },
        model3d_text_mode_disallows_images: {
            title: 'Model 3D TEXT source mode cannot include reference images.',
            details: ['Remove --image1/--image2 or switch to --source-mode IMAGE.'],
        },
        invalid_asset_subcategory: {
            title: 'The --subcategory value is invalid.',
            details: ['Use a lowercase slug such as generic, music, sfx, or avatar.'],
        },
        speech_disallows_subcategory: {
            title: 'Speech does not accept --subcategory.',
            details: ['Speech jobs always create AUDIO assets with subcategory speech.'],
        },
        invalid_speech_voice_selection: {
            title: 'Speech voice options are invalid.',
            details: ['Use either --voice <alias> or --provider-voice-id <id>, but not both.'],
        },
        invalid_speech_voice: {
            title: 'The --voice value is invalid.',
            details: [`Use one of: ${types_1.PLAYDROP_SPEECH_VOICE_IDS.join(', ')}.`],
        },
        speech_voice_unavailable: {
            title: 'The provider voice is unavailable.',
            details: ['Use a different --provider-voice-id or a curated --voice alias.'],
        },
        speech_input_too_long: {
            title: 'The speech input is too long.',
            details: [`Use ${types_1.PLAYDROP_SPEECH_MAX_WORDS} words or fewer.`],
        },
    };
    const entry = errorByCode[message];
    if (!entry) {
        return false;
    }
    (0, messages_1.printErrorWithHelp)(entry.title, entry.details, { command: 'ai create' });
    process.exitCode = 1;
    return true;
}
function handleGenerateImageReferenceError(message) {
    const imageRefHandlers = [
        {
            prefix: 'image_ref_not_found:',
            title: (optionName) => `The ${optionName || 'image'} reference file was not found.`,
            details: (path) => [path ? `Missing file: ${path}` : 'Provide a valid local file path or HTTPS URL.'],
        },
        {
            prefix: 'image_ref_not_file:',
            title: (optionName) => `The ${optionName || 'image'} reference is not a file.`,
            details: (path) => [path ? `Path must point to a file: ${path}` : 'Provide a valid local file path.'],
        },
        {
            prefix: 'image_ref_invalid_scheme:',
            title: (optionName) => `The ${optionName || 'image'} reference URL uses an unsupported scheme.`,
            details: (scheme) => [`Use an HTTPS URL or a local file path. Received: ${scheme || 'unknown'}`],
        },
        {
            prefix: 'image_ref_signature_mismatch:',
            title: (optionName) => `The ${optionName || 'image'} reference file extension does not match its image signature.`,
            details: (path) => [path ? `Fix file type mismatch for: ${path}` : 'Ensure extension and file signature match (.png, .jpg, .jpeg, .webp).'],
        },
        {
            prefix: 'image_ref_unsupported_type:',
            title: (optionName) => `The ${optionName || 'image'} reference file type is unsupported.`,
            details: (path) => [
                path ? `Unsupported file: ${path}` : 'Provide a supported local file.',
                'Supported local image types: .png, .jpg, .jpeg, .webp.',
            ],
        },
    ];
    for (const handler of imageRefHandlers) {
        if (!message.startsWith(handler.prefix)) {
            continue;
        }
        const [, optionName, value] = message.split(':');
        (0, messages_1.printErrorWithHelp)(handler.title(optionName), handler.details(value), { command: 'ai create' });
        process.exitCode = 1;
        return true;
    }
    return false;
}
function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}
function normalizeModality(value) {
    const normalized = value.trim().toUpperCase().replace(/[-\s]+/g, '_');
    if (normalized === 'IMAGE')
        return 'IMAGE';
    if (normalized === 'MUSIC')
        return 'MUSIC';
    if (normalized === 'SFX')
        return 'SFX';
    if (normalized === 'SPEECH')
        return 'SPEECH';
    if (normalized === 'VIDEO')
        return 'VIDEO';
    if (normalized === 'MODEL3D' || normalized === 'MODEL_3D')
        return 'MODEL_3D';
    return null;
}
function normalizeVisibility(value) {
    if (typeof value !== 'string' || value.trim().length === 0) {
        return undefined;
    }
    const normalized = value.trim().toUpperCase();
    if (normalized === 'PRIVATE' || normalized === 'PUBLIC') {
        return normalized;
    }
    return undefined;
}
function parseAssetSubcategory(value) {
    if (typeof value !== 'string' || value.trim().length === 0) {
        return undefined;
    }
    const normalized = (0, types_1.normalizeAssetSubcategory)(value);
    if (!(0, types_1.isValidAssetSubcategorySlug)(normalized)) {
        throw new Error('invalid_asset_subcategory');
    }
    return normalized;
}
function parseOptionalPositiveInt(value, field) {
    if (value === undefined || value === null || value === '') {
        return undefined;
    }
    const raw = String(value).trim();
    if (!raw.length) {
        return undefined;
    }
    const parsed = Number.parseInt(raw, 10);
    if (!Number.isInteger(parsed) || parsed <= 0) {
        throw new Error(`invalid_${field}`);
    }
    return parsed;
}
function parseOptionalNonNegativeInt(value, field) {
    if (value === undefined || value === null || value === '') {
        return undefined;
    }
    const raw = String(value).trim();
    if (!raw.length) {
        return undefined;
    }
    const parsed = Number.parseInt(raw, 10);
    if (!Number.isInteger(parsed) || parsed < 0) {
        throw new Error(`invalid_${field}`);
    }
    return parsed;
}
function parseOptionalDuration(value) {
    if (value === undefined || value === null || value === '') {
        return undefined;
    }
    const raw = String(value).trim().toLowerCase();
    if (!raw.length) {
        return undefined;
    }
    if (raw.endsWith('ms')) {
        const parsed = Number(raw.slice(0, -2).trim());
        if (!Number.isFinite(parsed) || parsed <= 0) {
            throw new Error('invalid_duration');
        }
        return { value: parsed, unit: 'ms' };
    }
    if (raw.endsWith('s')) {
        const parsed = Number(raw.slice(0, -1).trim());
        if (!Number.isFinite(parsed) || parsed <= 0) {
            throw new Error('invalid_duration');
        }
        return { value: parsed, unit: 's' };
    }
    const parsed = Number(raw);
    if (!Number.isFinite(parsed) || parsed <= 0) {
        throw new Error('invalid_duration');
    }
    return { value: parsed, unit: 'plain' };
}
function parseMusicDurationMs(duration) {
    if (!duration) {
        return undefined;
    }
    const milliseconds = duration.unit === 's' ? duration.value * 1000 : duration.value;
    if (!Number.isInteger(milliseconds) || milliseconds <= 0) {
        throw new Error('invalid_duration');
    }
    return milliseconds;
}
function parseSecondsDuration(duration) {
    if (!duration) {
        return undefined;
    }
    const seconds = duration.unit === 'ms' ? duration.value / 1000 : duration.value;
    if (!Number.isFinite(seconds) || seconds <= 0) {
        throw new Error('invalid_duration');
    }
    return seconds;
}
function parseVideoDurationSeconds(duration) {
    const seconds = parseSecondsDuration(duration);
    if (seconds === undefined) {
        return undefined;
    }
    if (!Number.isInteger(seconds) || (seconds !== 4 && seconds !== 8)) {
        throw new Error('invalid_video_duration');
    }
    return seconds;
}
function parseListFormat(raw) {
    if (!raw || raw.trim().length === 0) {
        return 'text';
    }
    const normalized = raw.trim().toLowerCase();
    if (normalized === 'text' || normalized === 'json') {
        return normalized;
    }
    return null;
}
function parseListType(raw) {
    if (!raw || raw.trim().length === 0) {
        return undefined;
    }
    const normalized = raw.trim().toLowerCase().replace(/[-\s]+/g, '_');
    if (normalized === 'text'
        || normalized === 'json'
        || normalized === 'image'
        || normalized === 'music'
        || normalized === 'sfx'
        || normalized === 'speech'
        || normalized === 'video'
        || normalized === 'model_3d') {
        return normalized;
    }
    return null;
}
function isPngSignature(buffer) {
    if (buffer.length < 8) {
        return false;
    }
    return (buffer[0] === 0x89
        && buffer[1] === 0x50
        && buffer[2] === 0x4e
        && buffer[3] === 0x47
        && buffer[4] === 0x0d
        && buffer[5] === 0x0a
        && buffer[6] === 0x1a
        && buffer[7] === 0x0a);
}
function isJpegSignature(buffer) {
    if (buffer.length < 3) {
        return false;
    }
    return buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff;
}
function isWebpSignature(buffer) {
    if (buffer.length < 12) {
        return false;
    }
    return (buffer.toString('ascii', 0, 4) === 'RIFF'
        && buffer.toString('ascii', 8, 12) === 'WEBP');
}
function detectLocalImageMimeType(buffer) {
    if (isPngSignature(buffer)) {
        return 'image/png';
    }
    if (isJpegSignature(buffer)) {
        return 'image/jpeg';
    }
    if (isWebpSignature(buffer)) {
        return 'image/webp';
    }
    return null;
}
function resolveLocalImageAttachment(optionName, value) {
    const resolvedPath = (0, node_path_1.resolve)(process.cwd(), value);
    if (!(0, node_fs_1.existsSync)(resolvedPath)) {
        throw new Error(`image_ref_not_found:${optionName}:${resolvedPath}`);
    }
    if (!(0, node_fs_1.statSync)(resolvedPath).isFile()) {
        throw new Error(`image_ref_not_file:${optionName}:${resolvedPath}`);
    }
    const extension = (0, node_path_1.extname)(resolvedPath).toLowerCase();
    const expectedMime = LOCAL_IMAGE_MIME_BY_EXTENSION[extension];
    if (!expectedMime) {
        throw new Error(`image_ref_unsupported_type:${optionName}:${resolvedPath}`);
    }
    const imageBuffer = (0, node_fs_1.readFileSync)(resolvedPath);
    const detectedMime = detectLocalImageMimeType(imageBuffer);
    if (!detectedMime) {
        throw new Error(`image_ref_unsupported_type:${optionName}:${resolvedPath}`);
    }
    if (expectedMime !== detectedMime) {
        throw new Error(`image_ref_signature_mismatch:${optionName}:${resolvedPath}`);
    }
    return {
        data: imageBuffer.toString('base64'),
        mimeType: detectedMime,
    };
}
function resolveImageAttachment(value, optionName) {
    if (typeof value !== 'string' || value.trim().length === 0) {
        return undefined;
    }
    const trimmed = value.trim();
    try {
        const parsed = new URL(trimmed);
        if (parsed.protocol !== 'https:') {
            throw new Error(`image_ref_invalid_scheme:${optionName}:${parsed.protocol}`);
        }
        return { url: trimmed };
    }
    catch (error) {
        if (error instanceof Error && error.message.startsWith('image_ref_invalid_scheme:')) {
            throw error;
        }
        return resolveLocalImageAttachment(optionName, trimmed);
    }
}
function buildGenerateRequest(typeInput, promptInput, options) {
    const type = typeof typeInput === 'string' ? typeInput.trim() : '';
    if (!type.length) {
        throw new Error('missing_type');
    }
    const modality = normalizeModality(type);
    if (!modality) {
        throw new Error('invalid_type');
    }
    const prompt = modality === 'SPEECH'
        ? (0, types_1.normalizeSpeechText)(typeof promptInput === 'string' ? promptInput : '')
        : typeof promptInput === 'string'
            ? promptInput.trim()
            : '';
    if (!prompt.length) {
        throw new Error('missing_prompt');
    }
    if (modality === 'SPEECH' && (0, types_1.countSpeechWords)(prompt) > types_1.PLAYDROP_SPEECH_MAX_WORDS) {
        throw new Error('speech_input_too_long');
    }
    const visibility = normalizeVisibility(options.visibility);
    if (options.visibility && !visibility) {
        throw new Error('invalid_visibility');
    }
    const timeoutSeconds = parseOptionalPositiveInt(options.timeoutSeconds, 'timeout_seconds') ?? GENERATE_DEFAULT_TIMEOUT_SECONDS;
    const pollIntervalMs = parseOptionalPositiveInt(options.pollIntervalMs, 'poll_interval_ms') ?? GENERATE_DEFAULT_POLL_INTERVAL_MS;
    if (pollIntervalMs < 250) {
        throw new Error('invalid_poll_interval_ms');
    }
    const duration = parseOptionalDuration(options.duration);
    const request = {
        modality,
        input: prompt,
        assetName: typeof options.assetName === 'string' && options.assetName.trim().length > 0 ? options.assetName.trim() : undefined,
        assetDisplayName: typeof options.assetDisplayName === 'string' && options.assetDisplayName.trim().length > 0
            ? options.assetDisplayName.trim()
            : undefined,
        assetDescription: typeof options.assetDescription === 'string' && options.assetDescription.trim().length > 0
            ? options.assetDescription.trim()
            : undefined,
        assetVisibility: visibility ?? 'PUBLIC',
        attachAppVersionId: parseOptionalPositiveInt(options.attachAppVersionId, 'attach_app_version_id'),
    };
    if (modality !== 'SPEECH') {
        request.assetSubcategory = parseAssetSubcategory(options.subcategory) ?? DEFAULT_SUBCATEGORY_BY_MODALITY[modality];
    }
    else if (typeof options.subcategory === 'string' && options.subcategory.trim().length > 0) {
        throw new Error('speech_disallows_subcategory');
    }
    if (modality === 'IMAGE') {
        request.image1 = resolveImageAttachment(options.image1, 'image1');
        request.image2 = resolveImageAttachment(options.image2, 'image2');
        if (typeof options.ratio === 'string' && options.ratio.trim().length > 0) {
            const aspectRatio = options.ratio.trim();
            if (!IMAGE_ASPECT_RATIO_SET.has(aspectRatio)) {
                throw new Error('invalid_aspect_ratio');
            }
            request.aspectRatio = aspectRatio;
        }
        else {
            request.aspectRatio = DEFAULT_IMAGE_ASPECT_RATIO;
        }
        if (typeof options.size === 'string' && options.size.trim().length > 0) {
            const imageSize = options.size.trim().toUpperCase();
            if (!IMAGE_SIZE_SET.has(imageSize)) {
                throw new Error('invalid_image_size');
            }
            request.imageSize = imageSize;
        }
        else {
            request.imageSize = DEFAULT_IMAGE_SIZE;
        }
    }
    else if (typeof options.size === 'string' && options.size.trim().length > 0) {
        throw new Error('image_size_not_supported_for_modality');
    }
    if (modality === 'MUSIC') {
        request.durationMs = parseMusicDurationMs(duration);
        request.forceInstrumental = Boolean(options.instrumental);
        request.coverPrompt = typeof options.coverPrompt === 'string' && options.coverPrompt.trim().length > 0 ? options.coverPrompt.trim() : undefined;
    }
    if (modality === 'SFX') {
        request.durationSeconds = parseSecondsDuration(duration);
        request.loop = Boolean(options.loop);
        request.coverPrompt = typeof options.coverPrompt === 'string' && options.coverPrompt.trim().length > 0 ? options.coverPrompt.trim() : undefined;
    }
    if (modality === 'SPEECH') {
        const speechVoice = typeof options.voice === 'string' && options.voice.trim().length > 0 ? options.voice.trim() : undefined;
        const speechProviderVoiceId = typeof options.providerVoiceId === 'string' && options.providerVoiceId.trim().length > 0
            ? options.providerVoiceId.trim()
            : undefined;
        if (speechVoice && speechProviderVoiceId) {
            throw new Error('invalid_speech_voice_selection');
        }
        let validatedSpeechVoice;
        if (speechVoice) {
            if (!isPlaydropSpeechVoiceId(speechVoice)) {
                throw new Error('invalid_speech_voice');
            }
            validatedSpeechVoice = speechVoice;
        }
        request.speechVoice = validatedSpeechVoice;
        request.speechProviderVoiceId = speechProviderVoiceId;
    }
    if (modality === 'VIDEO') {
        request.image1 = resolveImageAttachment(options.image1, 'image1');
        request.image2 = resolveImageAttachment(options.image2, 'image2');
        const durationSeconds = parseVideoDurationSeconds(duration);
        const hasFirstAndLastFrame = Boolean(request.image1 && request.image2);
        request.durationSeconds = hasFirstAndLastFrame
            ? VIDEO_FIRST_LAST_FRAME_DURATION_SECONDS
            : (durationSeconds ?? DEFAULT_VIDEO_DURATION_SECONDS);
        if (typeof options.ratio === 'string' && options.ratio.trim().length > 0) {
            const aspectRatio = options.ratio.trim();
            if (!VIDEO_ASPECT_RATIO_SET.has(aspectRatio)) {
                throw new Error('invalid_aspect_ratio');
            }
            request.aspectRatio = aspectRatio;
        }
        else {
            request.aspectRatio = DEFAULT_VIDEO_ASPECT_RATIO;
        }
        if (options.resolution !== undefined && options.resolution !== '720p') {
            throw new Error('invalid_resolution');
        }
        request.resolution = options.resolution;
    }
    if (modality === 'MODEL_3D') {
        const sourceModeInput = typeof options.sourceMode === 'string' ? options.sourceMode.trim().toUpperCase() : '';
        if (sourceModeInput.length > 0 && sourceModeInput !== 'TEXT' && sourceModeInput !== 'IMAGE') {
            throw new Error('invalid_source_mode');
        }
        request.image1 = resolveImageAttachment(options.image1, 'image1');
        request.image2 = resolveImageAttachment(options.image2, 'image2');
        const hasInputImages = Boolean(request.image1 || request.image2);
        request.sourceMode = sourceModeInput.length > 0
            ? sourceModeInput
            : (hasInputImages ? 'IMAGE' : 'TEXT');
        if (request.sourceMode === 'IMAGE' && !hasInputImages) {
            throw new Error('missing_model3d_images');
        }
        if (request.sourceMode === 'TEXT' && hasInputImages) {
            throw new Error('model3d_text_mode_disallows_images');
        }
        const topology = typeof options.topology === 'string' ? options.topology.trim().toLowerCase() : '';
        if (topology.length > 0 && topology !== 'triangle' && topology !== 'quad') {
            throw new Error('invalid_topology');
        }
        request.topology = topology.length > 0 ? topology : DEFAULT_MODEL3D_TOPOLOGY;
        request.targetPolycount = parseOptionalPositiveInt(options.polycount, 'polycount') ?? DEFAULT_MODEL3D_TARGET_POLYCOUNT;
        request.shouldRemesh = typeof options.remesh === 'boolean' ? options.remesh : DEFAULT_MODEL3D_SHOULD_REMESH;
        request.shouldTexture = typeof options.texture === 'boolean' ? options.texture : DEFAULT_MODEL3D_SHOULD_TEXTURE;
    }
    return { modality, request, timeoutSeconds, pollIntervalMs };
}
function generationPrompt(record) {
    if ('inputPrompt' in record && typeof record.inputPrompt === 'string') {
        return record.inputPrompt;
    }
    return '';
}
function generationAssetRef(record) {
    const candidate = record.generatedAsset;
    if (candidate && typeof candidate === 'object' && typeof candidate.ref === 'string' && candidate.ref.trim().length > 0) {
        return candidate.ref.trim();
    }
    return null;
}
function generationAssetSubcategory(record) {
    const candidate = record.generatedAsset;
    if (candidate && typeof candidate === 'object' && typeof candidate.subcategory === 'string' && candidate.subcategory.trim().length > 0) {
        return candidate.subcategory.trim();
    }
    return null;
}
function printGenerateResult(modality, payload) {
    console.log(`Modality: ${modality}`);
    if (payload?.billing?.generationId) {
        console.log(`Generation ID: ${payload.billing.generationId}`);
    }
    if (typeof payload?.cost === 'number') {
        console.log(`Cost: ${payload.cost}`);
    }
    if (typeof payload?.createdAt === 'string') {
        console.log(`Created At: ${payload.createdAt}`);
    }
    const ref = typeof payload?.generatedAsset?.ref === 'string' ? payload.generatedAsset.ref : null;
    const subcategory = typeof payload?.generatedAsset?.subcategory === 'string' ? payload.generatedAsset.subcategory : null;
    if (ref) {
        console.log(`Generated Asset: ${ref}`);
        if (subcategory) {
            console.log(`Generated Subcategory: ${subcategory}`);
        }
        console.log(`Next: run "playdrop detail ${ref}" to inspect the generated asset.`);
    }
    else {
        console.log('Generated Asset: none');
    }
}
function formatJobStatus(job) {
    const progress = typeof job.progressPercent === 'number'
        ? ` progress=${Math.max(0, Math.min(100, Math.round(job.progressPercent)))}%`
        : '';
    const eta = typeof job.etaSecondsRemaining === 'number'
        ? ` eta=${Math.max(0, Math.round(job.etaSecondsRemaining))}s`
        : '';
    return `${job.status}${progress}${eta}`;
}
async function generate(typeInput, promptInput, options = {}) {
    let parsed;
    try {
        parsed = buildGenerateRequest(typeInput, promptInput, options);
    }
    catch (error) {
        const message = typeof error?.message === 'string' ? error.message : 'invalid_input';
        if (handleGenerateBuildRequestError(message)) {
            return;
        }
        if (handleGenerateImageReferenceError(message)) {
            return;
        }
        (0, messages_1.printErrorWithHelp)(`Invalid AI create input: ${message}.`, ['Run "playdrop help ai create" for valid options.'], {
            command: 'ai create',
        });
        process.exitCode = 1;
        return;
    }
    await (0, commandContext_1.withEnvironment)('ai create', 'Generating AI assets', async ({ aiClient }) => {
        try {
            const created = await aiClient.createGenerationJob(parsed.request);
            let job = created.job;
            const jobId = String(job.id || '').trim();
            if (!jobId) {
                throw new Error('ai_generation_job_missing_id');
            }
            if (!options.json) {
                console.log(`Job ID: ${jobId}`);
                console.log(`Status: ${formatJobStatus(job)}`);
            }
            const deadlineMs = Date.now() + parsed.timeoutSeconds * 1000;
            while (job.status === 'PENDING' || job.status === 'RUNNING') {
                if (Date.now() >= deadlineMs) {
                    throw new Error(`ai_generation_job_timeout: Timed out waiting for job ${jobId}`);
                }
                await sleep(parsed.pollIntervalMs);
                const latest = await aiClient.getGenerationJob(jobId);
                job = latest.job;
                if (!options.json) {
                    console.log(`Status: ${formatJobStatus(job)}`);
                }
            }
            if (job.status !== 'SUCCESS') {
                const errorCode = typeof job.errorCode === 'string' && job.errorCode.length > 0 ? job.errorCode : 'ai_generation_failed';
                const errorMessage = typeof job.errorMessage === 'string' && job.errorMessage.length > 0
                    ? job.errorMessage
                    : 'Generation failed.';
                throw new Error(`${errorCode}: ${errorMessage}`);
            }
            const payload = (job.resultData && typeof job.resultData === 'object') ? job.resultData : {};
            if (options.json) {
                (0, output_1.printJson)({ job, result: payload });
                return;
            }
            printGenerateResult(parsed.modality, payload);
        }
        catch (error) {
            if (!handleAiFailure(error, 'ai create', 'AI create')) {
                throw error;
            }
        }
    });
}
function parseJobModality(raw) {
    if (!raw || raw.trim().length === 0 || raw.trim().toLowerCase() === 'all') {
        return undefined;
    }
    return normalizeModality(raw);
}
function handleAiFailure(error, command, context) {
    const handled = (0, errors_1.handleCommandFailure)(error, command, context, {
        networkMessage: 'Could not reach the Playdrop AI API.',
        apiMessage: (apiError) => {
            if (apiError.status === 401 || apiError.status === 403) {
                return {
                    problem: `${context} requires you to be logged in.`,
                    suggestions: ['Run "playdrop auth login" and retry.'],
                };
            }
            return {
                problem: `${context} failed with status ${apiError.status}.`,
                suggestions: ['Retry in a moment.'],
            };
        },
    });
    if (handled) {
        return true;
    }
    const status = typeof error?.status === 'number' ? Number(error.status) : null;
    if (status !== null) {
        if (status === 401 || status === 403) {
            (0, messages_1.printErrorWithHelp)(`${context} requires you to be logged in.`, ['Run "playdrop auth login" and retry.'], { command });
        }
        else {
            (0, messages_1.printErrorWithHelp)(`${context} failed with status ${status}.`, ['Retry in a moment.'], { command });
        }
        process.exitCode = 1;
        return true;
    }
    const message = error instanceof Error ? error.message : String(error);
    if (message === 'ai_generation_job_missing_id') {
        (0, messages_1.printErrorWithHelp)('AI create failed because the server did not return a job id.', ['Retry the command. If the problem persists, contact Playdrop support.'], { command });
        process.exitCode = 1;
        return true;
    }
    if (message.startsWith('ai_generation_job_timeout:')) {
        const detail = message.split(':').slice(1).join(':').trim();
        (0, messages_1.printErrorWithHelp)(detail || 'AI create timed out while waiting for the job to finish.', ['Retry the command with a higher --timeout value if the job normally takes longer.'], { command });
        process.exitCode = 1;
        return true;
    }
    if (message.includes(':')) {
        const [errorCode, ...rest] = message.split(':');
        const detail = rest.join(':').trim();
        if (errorCode && detail) {
            (0, messages_1.printErrorWithHelp)(`AI create failed: ${detail}`, [`Error code: ${errorCode.trim()}`], { command });
            process.exitCode = 1;
            return true;
        }
    }
    return false;
}
async function browseGenerationJobs(options = {}) {
    const modality = parseJobModality(options.type);
    if (options.type !== undefined && modality === null) {
        (0, messages_1.printErrorWithHelp)('The --type value is invalid.', ['Use one of: image, music, sfx, video, model_3d, all.'], { command: 'ai jobs browse' });
        process.exitCode = 1;
        return;
    }
    let limit;
    let offset;
    try {
        limit = parseOptionalPositiveInt(options.limit, 'limit');
        offset = parseOptionalNonNegativeInt(options.offset, 'offset');
    }
    catch (error) {
        const message = typeof error?.message === 'string' ? error.message : 'invalid_pagination';
        (0, messages_1.printErrorWithHelp)(`Invalid ai jobs input: ${message}.`, ['Use positive integers for --limit and --offset.'], {
            command: 'ai jobs browse',
        });
        process.exitCode = 1;
        return;
    }
    await (0, commandContext_1.withEnvironment)('ai jobs browse', 'Browsing AI jobs', async ({ aiClient }) => {
        try {
            const response = await aiClient.listGenerationJobs({
                modality: modality ?? undefined,
                limit,
                offset,
            });
            if (options.json) {
                (0, output_1.printJson)({ items: response.jobs, pagination: response.pagination });
                return;
            }
            if (!Array.isArray(response.jobs) || response.jobs.length === 0) {
                console.log('No AI jobs found.');
                console.log('Next: run "playdrop ai create <type> <prompt>" to start a new job.');
                return;
            }
            console.log('AI jobs:\n');
            for (const job of response.jobs) {
                console.log(`${job.id} | ${job.modality.toLowerCase()} | ${formatJobStatus(job)} | ${job.createdAt}`);
            }
            console.log('\nNext: run "playdrop ai jobs detail <id>" to inspect one job.');
        }
        catch (error) {
            if (!handleAiFailure(error, 'ai jobs browse', 'AI job lookup')) {
                throw error;
            }
        }
    });
}
async function showGenerationJob(id, options = {}) {
    const jobId = typeof id === 'string' ? id.trim() : '';
    if (!jobId) {
        (0, messages_1.printErrorWithHelp)('A job id is required.', ['Example: playdrop ai jobs detail job_123'], { command: 'ai jobs detail' });
        process.exitCode = 1;
        return;
    }
    await (0, commandContext_1.withEnvironment)('ai jobs detail', 'Inspecting an AI job', async ({ aiClient }) => {
        try {
            const response = await aiClient.getGenerationJob(jobId);
            if (options.json) {
                (0, output_1.printJson)({ job: response.job });
                return;
            }
            const job = response.job;
            console.log(`Job: ${job.id}`);
            console.log(`Type: ${job.modality.toLowerCase()}`);
            console.log(`Status: ${formatJobStatus(job)}`);
            console.log(`Created: ${job.createdAt}`);
            console.log(`Updated: ${job.updatedAt}`);
            if (job.errorMessage) {
                console.log(`Error: ${job.errorMessage}`);
            }
            if (job.linkedGenerationId) {
                console.log(`Linked generation: ${job.linkedGenerationId}`);
            }
            console.log('\nNext: run "playdrop ai jobs browse" to review other jobs.');
        }
        catch (error) {
            if (!handleAiFailure(error, 'ai jobs detail', 'AI job detail lookup')) {
                throw error;
            }
        }
    });
}
async function generations(options = {}) {
    const format = parseListFormat(options.format);
    if (!format) {
        (0, messages_1.printErrorWithHelp)('The --format value is invalid.', ['Use --format text or --format json.'], { command: 'generations' });
        process.exitCode = 1;
        return;
    }
    const type = parseListType(options.type);
    if (type === null) {
        (0, messages_1.printErrorWithHelp)('The --type value is invalid.', ['Use one of: text, json, image, music, sfx, video, model_3d.'], { command: 'generations' });
        process.exitCode = 1;
        return;
    }
    let limit;
    let offset;
    try {
        limit = parseOptionalPositiveInt(options.limit, 'limit');
        offset = parseOptionalNonNegativeInt(options.offset, 'offset');
    }
    catch (error) {
        const message = typeof error?.message === 'string' ? error.message : 'invalid_pagination';
        (0, messages_1.printErrorWithHelp)(`Invalid generations input: ${message}.`, ['Use positive integers for --limit and --offset.'], {
            command: 'generations',
        });
        process.exitCode = 1;
        return;
    }
    await (0, commandContext_1.withEnvironment)('generations', 'Listing AI generations', async ({ aiClient }) => {
        const response = await aiClient.listGenerations({ type: type ?? undefined, limit, offset });
        if (format === 'json') {
            console.log(JSON.stringify(response, null, 2));
            return;
        }
        if (!Array.isArray(response.generations) || response.generations.length === 0) {
            console.log('No AI generations found.');
            return;
        }
        for (const generation of response.generations) {
            const prompt = generationPrompt(generation);
            const ref = generationAssetRef(generation);
            const subcategory = generationAssetSubcategory(generation);
            const parts = [
                generation.id,
                generation.type,
                generation.createdAt,
                `cost=${generation.cost}`,
            ];
            if (prompt.length > 0) {
                parts.push(`prompt=${prompt}`);
            }
            if (ref) {
                parts.push(`asset=${ref}`);
            }
            if (subcategory) {
                parts.push(`subcategory=${subcategory}`);
            }
            console.log(parts.join(' | '));
        }
    });
}
