"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.setPlanDepsFactory = setPlanDepsFactory;
exports.getExecutor = getExecutor;
exports.setExecutor = setExecutor;
exports.removeExecutor = removeExecutor;
// backend/src/routes/plan-control.ts
const express_1 = require("express");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const config_1 = require("../config");
const executor_1 = require("../plan-control/executor");
const parser_1 = require("../plan-control/parser");
const templates_1 = require("../plan-control/templates");
const router = (0, express_1.Router)();
// In-memory executor instances per project
const executors = new Map();
let depsFactory = null;
/** Called once by index.ts at startup to inject real deps. */
function setPlanDepsFactory(factory) {
    depsFactory = factory;
}
function getExecutor(projectId) {
    return executors.get(projectId);
}
function setExecutor(projectId, executor) {
    executors.set(projectId, executor);
}
function removeExecutor(projectId) {
    executors.delete(projectId);
}
/** Check if the user can access this project's plan-control. */
function canAccessPlan(req, projectId) {
    const username = req.user?.username;
    if (!username)
        return false;
    if ((0, config_1.isAdminUser)(username))
        return true;
    const project = (0, config_1.getProject)(projectId);
    if (!project)
        return false;
    if ((0, config_1.isProjectOwner)(project, username))
        return true;
    const share = project.shares?.find(s => s.username === username);
    return share?.permission === 'edit';
}
// POST /init — Initialize .plan-control/ directory
router.post('/:id/plan/init', (req, res) => {
    if (!canAccessPlan(req, req.params.id))
        return res.status(403).json({ error: 'Access denied' });
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const pcDir = path.join(project.folderPath, '.plan-control');
    try {
        fs.mkdirSync(pcDir, { recursive: true });
        fs.mkdirSync(path.join(pcDir, 'nodes'), { recursive: true });
        fs.writeFileSync(path.join(pcDir, 'init.md'), templates_1.INIT_MD);
        fs.writeFileSync(path.join(pcDir, 'plan-code.md'), templates_1.PLAN_CODE_MD);
        fs.writeFileSync(path.join(pcDir, 'output-format.md'), templates_1.OUTPUT_FORMAT_MD);
        res.json({ success: true });
    }
    catch (err) {
        res.status(500).json({ error: `初始化失败: ${err.message}` });
    }
});
// GET /status — Get execution state
router.get('/:id/plan/status', (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const stateFile = path.join(project.folderPath, '.plan-control', 'state.json');
    const pcDir = path.join(project.folderPath, '.plan-control');
    const hasPcDir = fs.existsSync(pcDir);
    const hasMainPc = fs.existsSync(path.join(pcDir, 'main.pc'));
    if (fs.existsSync(stateFile)) {
        try {
            const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
            res.json({ initialized: hasPcDir, hasMainPc, state });
        }
        catch {
            res.json({ initialized: hasPcDir, hasMainPc, state: null });
        }
    }
    else {
        res.json({ initialized: hasPcDir, hasMainPc, state: null });
    }
});
// POST /check — Syntax check main.pc
router.post('/:id/plan/check', (req, res) => {
    if (!canAccessPlan(req, req.params.id))
        return res.status(403).json({ error: 'Access denied' });
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const executor = ensureExecutor(req.params.id, project.folderPath);
    const result = executor.checkSyntax();
    res.json({ errors: result.errors, valid: result.errors.length === 0 });
});
// POST /start — Start execution
router.post('/:id/plan/start', (req, res) => {
    if (!canAccessPlan(req, req.params.id))
        return res.status(403).json({ error: 'Access denied' });
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const executor = ensureExecutor(req.params.id, project.folderPath);
    try {
        executor.start();
        res.json({ success: true });
    }
    catch (err) {
        res.status(400).json({ error: err.message });
    }
});
// POST /pause
router.post('/:id/plan/pause', (req, res) => {
    if (!canAccessPlan(req, req.params.id))
        return res.status(403).json({ error: 'Access denied' });
    const executor = executors.get(req.params.id);
    if (!executor)
        return res.status(404).json({ error: 'No executor' });
    executor.pause();
    res.json({ success: true });
});
// POST /resume
router.post('/:id/plan/resume', (req, res) => {
    if (!canAccessPlan(req, req.params.id))
        return res.status(403).json({ error: 'Access denied' });
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const executor = ensureExecutor(req.params.id, project.folderPath);
    try {
        executor.resume();
        res.json({ success: true });
    }
    catch (err) {
        res.status(400).json({ error: err.message });
    }
});
// POST /stop
router.post('/:id/plan/stop', (req, res) => {
    if (!canAccessPlan(req, req.params.id))
        return res.status(403).json({ error: 'Access denied' });
    const executor = executors.get(req.params.id);
    if (!executor)
        return res.status(404).json({ error: 'No executor' });
    executor.stop();
    res.json({ success: true });
});
// GET /nodes — List all node records
router.get('/:id/plan/nodes', (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const executor = ensureExecutor(req.params.id, project.folderPath);
    res.json(executor.getNodes());
});
// GET /nodes/:nodeId — Single node detail
router.get('/:id/plan/nodes/:nodeId', (req, res) => {
    const { nodeId } = req.params;
    if (!/^\d{1,6}$/.test(nodeId))
        return res.status(400).json({ error: 'Invalid nodeId' });
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const executor = ensureExecutor(req.params.id, project.folderPath);
    const node = executor.getNode(nodeId);
    if (!node)
        return res.status(404).json({ error: 'Node not found' });
    res.json(node);
});
// GET /tree — Get AST topology tree for frontend rendering
router.get('/:id/plan/tree', (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project)
        return res.status(404).json({ error: 'Project not found' });
    const pcDir = path.join(project.folderPath, '.plan-control');
    const mainPcPath = path.join(pcDir, 'main.pc');
    if (!fs.existsSync(mainPcPath)) {
        return res.json({ tree: null });
    }
    const source = fs.readFileSync(mainPcPath, 'utf-8');
    const ast = (0, parser_1.parseProgram)(source);
    // Load state and node records to merge status
    let currentLine = null;
    let stateStatus = null;
    const stateFile = path.join(pcDir, 'state.json');
    if (fs.existsSync(stateFile)) {
        try {
            const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
            currentLine = state.current_line;
            stateStatus = state.status;
        }
        catch { /* ignore */ }
    }
    // Load node records for status mapping
    const nodeRecords = new Map();
    const nodesDir = path.join(pcDir, 'nodes');
    if (fs.existsSync(nodesDir)) {
        for (const f of fs.readdirSync(nodesDir)) {
            if (!f.startsWith('node-') || !f.endsWith('.json'))
                continue;
            try {
                const rec = JSON.parse(fs.readFileSync(path.join(nodesDir, f), 'utf-8'));
                nodeRecords.set(rec.line, { id: rec.id, status: rec.status });
            }
            catch { /* ignore */ }
        }
    }
    const tree = astToTree(ast, currentLine, stateStatus, nodeRecords);
    res.json({ tree });
});
function astToTree(nodes, currentLine, stateStatus, nodeRecords) {
    const result = [];
    for (const node of nodes) {
        if (node.type === 'blank' || node.type === 'comment')
            continue;
        const typeMap = {
            task: 'task', task_assign: 'task',
            if: 'if', elif: 'elif', else: 'else',
            for: 'for', loop: 'loop',
            func: 'func', call: 'call',
            var_assign: 'var',
        };
        const treeType = typeMap[node.type];
        if (!treeType)
            continue;
        const label = getNodeLabel(node);
        const rec = nodeRecords.get(node.line);
        let status;
        if (rec) {
            if (rec.status === 'success')
                status = 'success';
            else if (rec.status === 'failed')
                status = 'failed';
            else if (rec.status === 'blocked')
                status = 'blocked';
            else if (rec.status === null && currentLine === node.line &&
                (stateStatus === 'waiting' || stateStatus === 'running')) {
                status = 'running';
            }
            else if (rec.status === null)
                status = 'pending';
        }
        else if (currentLine !== null && node.line < currentLine) {
            status = undefined;
        }
        const treeNode = {
            id: `L${node.line}`,
            type: treeType,
            label,
            line: node.line,
            node_id: rec?.id,
            status,
            children: node.children.length > 0
                ? astToTree(node.children, currentLine, stateStatus, nodeRecords)
                : [],
        };
        result.push(treeNode);
    }
    return result;
}
function getNodeLabel(node) {
    switch (node.type) {
        case 'task':
        case 'task_assign': {
            const desc = node.description ?? '';
            const prefix = node.type === 'task_assign' ? `${node.varName} = ` : '';
            return prefix + (desc.length > 30 ? desc.slice(0, 30) + '...' : desc);
        }
        case 'if':
        case 'elif':
            return `${node.type} ${node.condition}`;
        case 'else':
            return 'else';
        case 'for':
            return `for ${node.iterVar} in \${${node.iterRef}}`;
        case 'loop':
            return node.loopCounter ? `loop ${node.loopCount} as ${node.loopCounter}` : `loop ${node.loopCount}`;
        case 'func':
            return `func ${node.funcName}(${node.params?.join(', ') ?? ''})`;
        case 'call':
            return `call ${node.funcName}(...)`;
        case 'var_assign':
            return `${node.varName} = [${node.listItems?.join(', ') ?? ''}]`;
        default:
            return node.raw;
    }
}
function ensureExecutor(projectId, folderPath) {
    let executor = executors.get(projectId);
    if (!executor) {
        if (!depsFactory)
            throw new Error('Plan deps factory not initialized — call setPlanDepsFactory() first');
        const deps = depsFactory(projectId, folderPath);
        executor = new executor_1.PlanExecutor(folderPath, deps);
        executors.set(projectId, executor);
    }
    return executor;
}
exports.default = router;
//# sourceMappingURL=plan-control.js.map