export function registerMediaRoutes(app, ctx) {
    const { db } = ctx;
    const { sendApiError, requireLocalDaemonRequest, isLocalSameOrigin, resolvedPortRef } = ctx.http;
    const { PROJECT_ROOT, PROJECTS_DIR, RUNTIME_DATA_DIR } = ctx.paths;
    const { randomUUID } = ctx.ids;
    const { MEDIA_PROVIDERS, IMAGE_MODELS, VIDEO_MODELS, AUDIO_MODELS_BY_KIND, MEDIA_ASPECTS, VIDEO_LENGTHS_SEC, AUDIO_DURATIONS_SEC, readMaskedConfig, writeConfig, generateMedia, createMediaTask, persistMediaTask, appendTaskProgress, notifyTaskWaiters, getLiveMediaTask, mediaTaskSnapshot, listMediaTasksByProject, listElevenLabsVoiceOptions } = ctx.media;
    const { readAppConfig, writeAppConfig } = ctx.appConfig;
    const { orbitService } = ctx.orbit;
    const { openNativeFolderDialog } = ctx.nativeDialogs;
    const { getProject } = ctx.projectStore;
    const { writeProjectFile } = ctx.projectFiles;
    const { insertConversation, upsertMessage } = ctx.conversations;
    const { searchResearch, ResearchError } = ctx.research;
    const getResolvedPort = () => resolvedPortRef.current;
    app.get('/api/media/models', (_req, res) => {
        res.json({
            providers: MEDIA_PROVIDERS,
            image: IMAGE_MODELS,
            video: VIDEO_MODELS,
            audio: AUDIO_MODELS_BY_KIND,
            aspects: MEDIA_ASPECTS,
            videoLengthsSec: VIDEO_LENGTHS_SEC,
            audioDurationsSec: AUDIO_DURATIONS_SEC,
        });
    });
    app.get('/api/media/config', async (_req, res) => {
        try {
            const cfg = await readMaskedConfig(PROJECT_ROOT);
            res.json(cfg);
        }
        catch (err) {
            res
                .status(500)
                .json({ error: String(err && err.message ? err.message : err) });
        }
    });
    app.put('/api/media/config', async (req, res) => {
        try {
            const cfg = await writeConfig(PROJECT_ROOT, req.body);
            res.json(cfg);
        }
        catch (err) {
            const status = typeof err?.status === 'number' ? err.status : 400;
            res
                .status(status)
                .json({ error: String(err && err.message ? err.message : err) });
        }
    });
    app.get('/api/media/providers/elevenlabs/voices', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        try {
            const rawLimit = Number(req.query.limit);
            const limit = Number.isFinite(rawLimit) ? rawLimit : undefined;
            const voices = await listElevenLabsVoiceOptions(PROJECT_ROOT, { limit });
            res.json({ voices });
        }
        catch (err) {
            const message = String(err && err.message ? err.message : err);
            const status = message.includes('no ElevenLabs API key') ? 400 : 502;
            res.status(status).json({ error: message });
        }
    });
    app.get('/api/app-config', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        try {
            const config = await readAppConfig(RUNTIME_DATA_DIR);
            res.json({ config });
        }
        catch (err) {
            res
                .status(500)
                .json({ error: String(err && err.message ? err.message : err) });
        }
    });
    app.put('/api/app-config', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        try {
            const config = await writeAppConfig(RUNTIME_DATA_DIR, req.body);
            orbitService.configure(config.orbit);
            res.json({ config });
        }
        catch (err) {
            res
                .status(500)
                .json({ error: String(err && err.message ? err.message : err) });
        }
    });
    app.get('/api/orbit/status', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        try {
            res.json(await orbitService.status());
        }
        catch (err) {
            res
                .status(500)
                .json({ error: String(err && err.message ? err.message : err) });
        }
    });
    app.post('/api/orbit/run', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        try {
            res.json(await orbitService.start('manual'));
        }
        catch (err) {
            res
                .status(500)
                .json({ error: String(err && err.message ? err.message : err) });
        }
    });
    // Native OS folder picker dialog. Returns { path: string | null }.
    app.post('/api/dialog/open-folder', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        try {
            const selected = await openNativeFolderDialog();
            res.json({ path: selected });
        }
        catch (err) {
            res
                .status(500)
                .json({ error: String(err && err.message ? err.message : err) });
        }
    });
    app.post('/api/projects/:id/media/generate', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({
                error: 'cross-origin request rejected: media generation is restricted to the local UI / CLI',
            });
        }
        try {
            const projectId = req.params.id;
            const project = getProject(db, projectId);
            if (!project)
                return res.status(404).json({ error: 'project not found' });
            const taskId = randomUUID();
            const task = createMediaTask(taskId, projectId, {
                surface: req.body?.surface,
                model: req.body?.model,
            });
            console.error(`[task ${taskId.slice(0, 8)}] queued model=${req.body?.model} ` +
                `surface=${req.body?.surface} ` +
                `image=${req.body?.image ? 'yes' : 'no'} ` +
                `compositionDir=${req.body?.compositionDir ? 'yes' : 'no'}`);
            task.status = 'running';
            persistMediaTask(task);
            generateMedia({
                projectRoot: PROJECT_ROOT,
                projectsRoot: PROJECTS_DIR,
                projectId,
                surface: req.body?.surface,
                model: req.body?.model,
                prompt: req.body?.prompt,
                output: req.body?.output,
                aspect: req.body?.aspect,
                length: typeof req.body?.length === 'number' ? req.body.length : undefined,
                duration: typeof req.body?.duration === 'number'
                    ? req.body.duration
                    : undefined,
                voice: req.body?.voice,
                audioKind: req.body?.audioKind,
                language: typeof req.body?.language === 'string' ? req.body.language : undefined,
                loop: typeof req.body?.loop === 'boolean' ? req.body.loop : undefined,
                promptInfluence: typeof req.body?.promptInfluence === 'number'
                    ? req.body.promptInfluence
                    : undefined,
                compositionDir: req.body?.compositionDir,
                image: req.body?.image,
                onProgress: (line) => appendTaskProgress(task, line),
            })
                .then((meta) => {
                task.status = 'done';
                task.file = meta;
                task.endedAt = Date.now();
                persistMediaTask(task);
                notifyTaskWaiters(task);
                console.error(`[task ${taskId.slice(0, 8)}] done size=${meta?.size} mime=${meta?.mime} ` +
                    `elapsed=${Math.round((task.endedAt - task.startedAt) / 1000)}s`);
            })
                .catch((err) => {
                task.status = 'failed';
                task.error = {
                    message: String(err && err.message ? err.message : err),
                    status: typeof err?.status === 'number' ? err.status : 400,
                    code: err?.code,
                };
                task.endedAt = Date.now();
                persistMediaTask(task);
                notifyTaskWaiters(task);
                console.error(`[task ${taskId.slice(0, 8)}] failed status=${task.error.status} ` +
                    `message=${(task.error.message || '').slice(0, 240)}`);
            });
            res.status(202).json({
                taskId,
                status: task.status,
                startedAt: task.startedAt,
            });
        }
        catch (err) {
            const status = typeof err?.status === 'number' ? err.status : 400;
            const code = err?.code;
            const body = { error: String(err && err.message ? err.message : err) };
            if (code)
                body.code = code;
            res.status(status).json(body);
        }
    });
    app.post('/api/research/search', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({
                error: 'cross-origin request rejected: research search is restricted to the local UI / CLI',
            });
        }
        try {
            const result = await searchResearch({
                projectRoot: PROJECT_ROOT,
                query: req.body?.query,
                maxSources: typeof req.body?.maxSources === 'number'
                    ? req.body.maxSources
                    : undefined,
                providers: Array.isArray(req.body?.providers)
                    ? req.body.providers
                    : undefined,
            });
            res.json(result);
        }
        catch (err) {
            if (err instanceof ResearchError) {
                return res.status(err.status).json({
                    error: { code: err.code, message: err.message },
                });
            }
            res.status(500).json({
                error: {
                    code: 'RESEARCH_FAILED',
                    message: String(err && err.message ? err.message : err),
                },
            });
        }
    });
    app.post('/api/media/tasks/:id/wait', async (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        const taskId = req.params.id;
        const task = getLiveMediaTask(taskId);
        if (!task)
            return res.status(404).json({ error: 'task not found' });
        const since = Number.isFinite(req.body?.since) ? Number(req.body.since) : 0;
        const requestedTimeout = Number.isFinite(req.body?.timeoutMs)
            ? Number(req.body.timeoutMs)
            : 25_000;
        const timeoutMs = Math.min(Math.max(requestedTimeout, 0), 25_000);
        const respond = () => {
            if (res.writableEnded)
                return;
            res.json(mediaTaskSnapshot(task, since));
        };
        if (task.status === 'done' ||
            task.status === 'failed' ||
            task.status === 'interrupted' ||
            task.progress.length > since) {
            return respond();
        }
        let resolved = false;
        const wake = () => {
            if (resolved)
                return;
            resolved = true;
            task.waiters.delete(wake);
            clearTimeout(timer);
            respond();
        };
        task.waiters.add(wake);
        const timer = setTimeout(wake, timeoutMs);
        res.on('close', wake);
    });
    app.get('/api/projects/:id/media/tasks', (req, res) => {
        if (!isLocalSameOrigin(req, getResolvedPort())) {
            return res.status(403).json({ error: 'cross-origin request rejected' });
        }
        const projectId = req.params.id;
        const includeDone = req.query.includeDone === '1' || req.query.includeDone === 'true';
        const tasks = listMediaTasksByProject(db, projectId, {
            includeTerminal: includeDone,
        }).map((t) => ({
            taskId: t.id,
            status: t.status,
            startedAt: t.startedAt,
            endedAt: t.endedAt,
            elapsed: Math.round(((t.endedAt ?? Date.now()) - t.startedAt) / 1000),
            surface: t.surface,
            model: t.model,
            progress: t.progress.slice(-3),
            progressCount: t.progress.length,
            ...(t.status === 'done' ? { file: t.file } : {}),
            ...(t.status === 'failed' || t.status === 'interrupted' ? { error: t.error } : {}),
        }));
        res.json({ tasks });
    });
    // Multi-file upload that the chat composer uses for paste/drop/picker.
    // Files land flat in the project folder; the response carries the same
    // metadata as listFiles so the client can stage them as ChatAttachments
    // without a separate refetch.
}
//# sourceMappingURL=media-routes.js.map