"use strict";
/**
 * /api/projects/:projectId/track-flows/* routes — track-flow CRUD (M1).
 *
 * Endpoints:
 *   GET  /:projectId/track-flows              — list
 *   GET  /:projectId/track-flows/file/:filename — load flow + trainJson
 *   PUT  /:projectId/track-flows/file/:filename — save flow (+ optional trainJson)
 *   DELETE /:projectId/track-flows/file/:filename — delete flow + sidecar
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildTrackFlowsRouter = buildTrackFlowsRouter;
const express_1 = require("express");
const config_1 = require("../config");
const authz_1 = require("../middleware/authz");
const store_1 = require("../track-flow/store");
const track_flow_1 = require("../track-flow");
const _flow_injector_1 = require("./_flow-injector");
const run_id_1 = require("../track-flow/run-id");
const logger_1 = require("../logger");
const log = (0, logger_1.modLogger)('track-flows-route');
function buildTrackFlowsRouter() {
    const router = (0, express_1.Router)();
    // GET /api/projects/:projectId/track-flows
    router.get('/:projectId/track-flows', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const project = (0, config_1.getProject)(req.params.projectId);
        if (!project) {
            res.status(404).json({ error: 'Project not found' });
            return;
        }
        res.json({ files: (0, store_1.listFlows)(project.folderPath) });
    });
    // GET /api/projects/:projectId/track-flows/file/:filename
    router.get('/:projectId/track-flows/file/:filename', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const project = (0, config_1.getProject)(req.params.projectId);
        if (!project) {
            res.status(404).json({ error: 'Project not found' });
            return;
        }
        const basename = (0, store_1.sanitizeFlowFilename)(req.params.filename);
        if (!basename) {
            res.status(400).json({ error: 'invalid filename' });
            return;
        }
        const flow = (0, store_1.loadFlow)(project.folderPath, basename);
        if (flow === null) {
            res.status(404).json({ error: 'flow not found' });
            return;
        }
        const trainJson = (0, store_1.loadTrainJson)(project.folderPath, basename);
        res.json({ filename: `${basename}.flow`, flow, trainJson });
    });
    // PUT /api/projects/:projectId/track-flows/file/:filename
    // body: { flow: object, trainJson?: object }
    router.put('/:projectId/track-flows/file/:filename', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const project = (0, config_1.getProject)(req.params.projectId);
        if (!project) {
            res.status(404).json({ error: 'Project not found' });
            return;
        }
        const basename = (0, store_1.sanitizeFlowFilename)(req.params.filename);
        if (!basename) {
            res.status(400).json({ error: 'invalid filename' });
            return;
        }
        const { flow, trainJson } = req.body ?? {};
        // flow validation
        if (typeof flow !== 'object' || flow === null || Array.isArray(flow)) {
            res.status(400).json({ error: 'body.flow must be a non-null object' });
            return;
        }
        const flowStr = JSON.stringify(flow);
        if (flowStr.length > 1048576) {
            res.status(413).json({ error: 'flow too large (>1MB)' });
            return;
        }
        // trainJson validation (only when key is present)
        const hasTrainJson = Object.prototype.hasOwnProperty.call(req.body ?? {}, 'trainJson');
        if (hasTrainJson && trainJson != null) {
            if (typeof trainJson !== 'object' || Array.isArray(trainJson)) {
                res.status(400).json({ error: 'body.trainJson must be an object' });
                return;
            }
            if (JSON.stringify(trainJson).length > 524288) {
                res.status(413).json({ error: 'trainJson too large (>512KB)' });
                return;
            }
        }
        // v-h: body.createOnly=true 时拒绝覆盖已存在的 flow（避免 "新建工作轨"
        // UI 在 filename 撞名时静默覆盖用户数据）。upsert 是默认。
        const createOnly = req.body?.createOnly === true;
        const saveResult = (0, store_1.saveFlow)(project.folderPath, basename, flow, createOnly ? 'create' : 'upsert');
        if (!saveResult.ok) {
            if (saveResult.reason === 'exists') {
                res.status(409).json({ code: 'FLOW_FILE_EXISTS', error: `flow "${basename}" already exists` });
            }
            else {
                res.status(500).json({ error: 'failed to save flow' });
            }
            return;
        }
        if (hasTrainJson && trainJson != null) {
            const okT = (0, store_1.saveTrainJson)(project.folderPath, basename, trainJson);
            if (!okT) {
                res.status(500).json({ error: 'failed to save trainJson' });
                return;
            }
        }
        log.info({ projectId: project.id, basename, flowBytes: flowStr.length }, 'track-flow saved');
        res.json({ ok: true });
    });
    // DELETE /api/projects/:projectId/track-flows/file/:filename
    router.delete('/:projectId/track-flows/file/:filename', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const project = (0, config_1.getProject)(req.params.projectId);
        if (!project) {
            res.status(404).json({ error: 'Project not found' });
            return;
        }
        const basename = (0, store_1.sanitizeFlowFilename)(req.params.filename);
        if (!basename) {
            res.status(400).json({ error: 'invalid filename' });
            return;
        }
        // v-h: 拒绝删除运行中的 flow（否则文件被删但后台 run 仍跑，audit log 沦
        // 为孤儿，且 list 拿不到给用户）。要求用户先 cancel 再删。
        const active = track_flow_1.flowRunRegistry.findActive(project.id, basename);
        if (active) {
            res.status(409).json({
                code: 'FLOW_RUN_ACTIVE',
                runId: active.runId,
                error: `flow "${basename}" is running, cancel run first`,
            });
            return;
        }
        const ok = (0, store_1.deleteFlow)(project.folderPath, basename);
        res.json({ ok });
    });
    // POST /api/projects/:projectId/track-flows/file/:filename/run — body { quotaOverride? }
    router.post('/:projectId/track-flows/file/:filename/run', (0, authz_1.requireProjectOwner)('projectId'), async (req, res) => {
        const project = (0, config_1.getProject)(req.params.projectId);
        if (!project) {
            res.status(404).json({ error: 'Project not found' });
            return;
        }
        const basename = (0, store_1.sanitizeFlowFilename)(req.params.filename);
        if (!basename) {
            res.status(400).json({ error: 'invalid filename' });
            return;
        }
        const flowRaw = (0, store_1.loadFlow)(project.folderPath, basename);
        if (flowRaw === null || typeof flowRaw !== 'object') {
            res.status(404).json({ error: 'flow not found' });
            return;
        }
        const flow = flowRaw;
        const trainJson = (0, store_1.loadTrainJson)(project.folderPath, basename) ?? {};
        const runId = (0, run_id_1.newRunId)();
        try {
            track_flow_1.flowRunRegistry.start({
                runId,
                projectId: project.id,
                basename,
                quotaOverride: req.body?.quotaOverride,
            });
        }
        catch (e) {
            const err = e;
            if (err.message === 'FLOW_ALREADY_RUNNING') {
                res.status(409).json({
                    code: 'FLOW_ALREADY_RUNNING',
                    runId: err.existingRunId,
                    error: '该工作轨已有运行中的实例',
                });
                return;
            }
            throw e;
        }
        res.json({ runId });
        const injector = (0, _flow_injector_1.deriveInjector)(project.id);
        const broadcast = (0, _flow_injector_1.deriveBroadcast)(project.id);
        const getTerminalRef = (0, _flow_injector_1.deriveTerminalRefGetter)(project.id);
        void (0, track_flow_1.runFlow)(flow, trainJson, {
            projectFolder: project.folderPath,
            basename,
            runId,
            injector,
            broadcast,
            getTerminalRef,
        }).catch((e) => {
            log.error({ runId, err: e.message }, 'runFlow threw');
        });
    });
    // POST /api/projects/:projectId/track-flows/file/:filename/cancel — body { runId? }
    router.post('/:projectId/track-flows/file/:filename/cancel', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const project = (0, config_1.getProject)(req.params.projectId);
        if (!project) {
            res.status(404).json({ error: 'Project not found' });
            return;
        }
        const basename = (0, store_1.sanitizeFlowFilename)(req.params.filename);
        if (!basename) {
            res.status(400).json({ error: 'invalid filename' });
            return;
        }
        const runId = req.body?.runId;
        if (typeof runId === 'string') {
            const ok = track_flow_1.flowRunRegistry.cancel(runId);
            res.json({ ok });
            return;
        }
        const active = track_flow_1.flowRunRegistry.findActive(project.id, basename);
        if (!active) {
            res.json({ ok: false, message: 'no active run' });
            return;
        }
        track_flow_1.flowRunRegistry.cancel(active.runId);
        res.json({ ok: true, runId: active.runId });
    });
    // POST /api/projects/:projectId/track-flows/file/:filename/user_input — body { runId, values }
    router.post('/:projectId/track-flows/file/:filename/user_input', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const body = req.body;
        const runId = body?.runId;
        const values = body?.values;
        if (typeof runId !== 'string' || typeof values !== 'object' || values === null) {
            res.status(400).json({ error: 'runId/values required' });
            return;
        }
        const ok = (0, track_flow_1.submitUserInputForRun)(runId, values);
        res.json({ ok });
    });
    // GET /api/projects/:projectId/track-flows/runs/active
    router.get('/:projectId/track-flows/runs/active', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const list = track_flow_1.flowRunRegistry.listActive(req.params.projectId);
        res.json({
            runs: list.map((r) => ({
                runId: r.runId,
                basename: r.basename,
                status: r.status,
                startedAt: r.startedAt,
                pendingUserInput: r.pendingUserInput,
            })),
        });
    });
    // GET /api/projects/:projectId/track-flows/runs/:runId/state
    // 用于 WS 重连 / 浏览器刷新后前端 attach existing run → 拉回 snapshot +
    // nodeStates + currentNodeId，否则 useFlowRun 是空白，用户看不到进度。
    router.get('/:projectId/track-flows/runs/:runId/state', (0, authz_1.requireProjectOwner)('projectId'), (req, res) => {
        const info = track_flow_1.flowRunRegistry.get(req.params.runId);
        if (!info || info.projectId !== req.params.projectId) {
            res.status(404).json({ error: 'run not found' });
            return;
        }
        res.json({
            runId: info.runId,
            basename: info.basename,
            status: info.status,
            startedAt: info.startedAt,
            snapshot: info.snapshot,
            currentNodeId: info.currentNodeId,
            nodeStates: Object.fromEntries(info.nodeStates),
            pendingUserInput: info.pendingUserInput,
            error: info.error,
            quota: track_flow_1.flowRunRegistry.remainingQuota(info.runId, info.currentNodeId ?? undefined),
        });
    });
    return router;
}
//# sourceMappingURL=track-flows.js.map