/**
 * Loop V2 Routes Module
 * CCW Loop System - Simplified HTTP API endpoints for Dashboard
 * Provides simplified loop CRUD operations independent of task files
 *
 * Loop Endpoints:
 * - GET    /api/loops/v2                   - List all loops with pagination
 * - POST   /api/loops/v2                   - Create loop with {title, description, max_iterations}
 * - GET    /api/loops/v2/:loopId           - Get loop details
 * - PUT    /api/loops/v2/:loopId           - Update loop metadata (title, description, max_iterations, tags, priority, notes)
 * - PATCH  /api/loops/v2/:loopId/status    - Quick status update with {status}
 * - DELETE /api/loops/v2/:loopId           - Delete loop
 * - POST   /api/loops/v2/:loopId/start     - Start loop execution
 * - POST   /api/loops/v2/:loopId/pause     - Pause loop
 * - POST   /api/loops/v2/:loopId/resume    - Resume loop
 * - POST   /api/loops/v2/:loopId/stop      - Stop loop
 *
 * Task Management Endpoints:
 * - POST   /api/loops/v2/:loopId/tasks           - Add task to loop
 * - GET    /api/loops/v2/:loopId/tasks           - List all tasks for loop
 * - PUT    /api/loops/v2/tasks/:taskId           - Update task (requires loop_id in body)
 * - DELETE /api/loops/v2/tasks/:taskId           - Delete task (requires loop_id query param)
 * - PUT    /api/loops/v2/:loopId/tasks/reorder   - Reorder tasks with {ordered_task_ids: string[]}
 *
 * Advanced Task Features:
 * - POST   /api/loops/v2/:loopId/import   - Import tasks from issue with {issue_id}
 * - POST   /api/loops/v2/:loopId/generate - Generate tasks via Gemini with {tool?, count?}
 */
import { join } from 'path';
import { randomBytes } from 'crypto';
import * as os from 'os';
import { LoopStatus } from '../../types/loop.js';
import { TaskStorageManager } from '../../tools/loop-task-manager.js';
import { executeCliTool } from '../../tools/cli-executor.js';
import { loadClaudeCliTools } from '../../tools/claude-cli-tools.js';
/**
 * Module-level cache for CLI tools configuration
 * Loaded once at server startup to avoid repeated file I/O
 */
let cachedEnabledTools = null;
/**
 * Initialize CLI tools cache at server startup
 * Should be called once when the server starts
 */
export function initializeCliToolsCache() {
    try {
        const cliToolsConfig = loadClaudeCliTools(os.homedir());
        const enabledTools = Object.entries(cliToolsConfig.tools || {})
            .filter(([_, config]) => config.enabled === true)
            .map(([name]) => name);
        cachedEnabledTools = ['bash', ...enabledTools];
        console.log('[Loop V2] CLI tools cache initialized:', cachedEnabledTools);
    }
    catch (err) {
        console.error('[Loop V2] Failed to initialize CLI tools cache:', err);
        // Fallback to basic tools if config loading fails
        cachedEnabledTools = ['bash', 'gemini', 'qwen', 'codex', 'claude'];
    }
}
/**
 * Clear CLI tools cache (for testing or config reload)
 */
export function clearCliToolsCache() {
    cachedEnabledTools = null;
}
/**
 * Handle V2 loop routes
 * @returns true if route was handled, false otherwise
 */
export async function handleLoopV2Routes(ctx) {
    const { pathname, req, res, initialPath, handlePostRequest, url, broadcastToClients } = ctx;
    // Get workflow directory from initialPath
    const workflowDir = initialPath || process.cwd();
    const loopDir = join(workflowDir, '.workflow', '.loop');
    // Helper to broadcast loop state updates
    const broadcastStateUpdate = (loopId, status) => {
        try {
            broadcastToClients({
                type: 'LOOP_STATE_UPDATE',
                loop_id: loopId,
                status: status,
                updated_at: new Date().toISOString()
            });
        }
        catch (error) {
            // Silently ignore broadcast errors
        }
    };
    // Helper to generate loop ID
    const generateLoopId = () => {
        const timestamp = new Date().toISOString().replace(/[-:]/g, '').split('.')[0];
        const random = randomBytes(4).toString('hex');
        return `loop-v2-${timestamp}-${random}`;
    };
    // Helper to read loop storage
    const readLoopStorage = async (loopId) => {
        const { readFile } = await import('fs/promises');
        const { existsSync } = await import('fs');
        const filePath = join(loopDir, `${loopId}.json`);
        if (!existsSync(filePath)) {
            return null;
        }
        try {
            const content = await readFile(filePath, 'utf-8');
            return JSON.parse(content);
        }
        catch {
            return null;
        }
    };
    // Helper to write loop storage
    const writeLoopStorage = async (loop) => {
        const { writeFile, mkdir } = await import('fs/promises');
        const { existsSync } = await import('fs');
        if (!existsSync(loopDir)) {
            await mkdir(loopDir, { recursive: true });
        }
        const filePath = join(loopDir, `${loop.loop_id}.json`);
        await writeFile(filePath, JSON.stringify(loop, null, 2), 'utf-8');
    };
    // Helper to delete loop storage
    const deleteLoopStorage = async (loopId) => {
        const { unlink } = await import('fs/promises');
        const { existsSync } = await import('fs');
        const filePath = join(loopDir, `${loopId}.json`);
        if (existsSync(filePath)) {
            await unlink(filePath);
        }
        // Also delete tasks.jsonl if exists
        const tasksPath = join(loopDir, `${loopId}.tasks.jsonl`);
        if (existsSync(tasksPath)) {
            await unlink(tasksPath).catch(() => { });
        }
    };
    // Helper to list all loops
    const listLoops = async () => {
        const { readdir } = await import('fs/promises');
        const { existsSync } = await import('fs');
        if (!existsSync(loopDir)) {
            return [];
        }
        const files = await readdir(loopDir);
        const loopFiles = files.filter(f => f.startsWith('loop-v2-') && f.endsWith('.json'));
        const loops = [];
        for (const file of loopFiles) {
            const loopId = file.replace('.json', '');
            const loop = await readLoopStorage(loopId);
            if (loop) {
                loops.push(loop);
            }
        }
        return loops;
    };
    // ==== EXACT PATH ROUTES ====
    // POST /api/loops/v2 - Create loop with simplified fields
    if (pathname === '/api/loops/v2' && req.method === 'POST') {
        handlePostRequest(req, res, async (body) => {
            const { title, description, max_iterations } = body;
            // Validation
            if (!title || typeof title !== 'string' || title.trim().length === 0) {
                return { success: false, error: 'title is required and must be non-empty', status: 400 };
            }
            if (description !== undefined && typeof description !== 'string') {
                return { success: false, error: 'description must be a string', status: 400 };
            }
            if (max_iterations !== undefined && (typeof max_iterations !== 'number' || max_iterations < 1)) {
                return { success: false, error: 'max_iterations must be a positive number', status: 400 };
            }
            try {
                const loopId = generateLoopId();
                const now = new Date().toISOString();
                const loop = {
                    loop_id: loopId,
                    title: title.trim(),
                    description: description?.trim() || '',
                    max_iterations: max_iterations || 10,
                    status: LoopStatus.CREATED,
                    current_iteration: 0,
                    created_at: now,
                    updated_at: now
                };
                await writeLoopStorage(loop);
                // Broadcast creation
                broadcastStateUpdate(loopId, LoopStatus.CREATED);
                return { success: true, data: loop };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    // GET /api/loops/v2 - List all loops with pagination
    if (pathname === '/api/loops/v2' && req.method === 'GET') {
        try {
            const loops = await listLoops();
            // Parse query params for pagination and filtering
            const searchParams = url?.searchParams;
            let filteredLoops = loops;
            // Filter by status
            const statusFilter = searchParams?.get('status');
            if (statusFilter && statusFilter !== 'all') {
                filteredLoops = filteredLoops.filter(l => l.status === statusFilter);
            }
            // Sort by updated_at (most recent first)
            filteredLoops.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime());
            // Parse pagination params
            const limit = parseInt(searchParams?.get('limit') || '50', 10);
            const offset = parseInt(searchParams?.get('offset') || '0', 10);
            // Apply pagination
            const paginatedLoops = filteredLoops.slice(offset, offset + limit);
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({
                success: true,
                data: paginatedLoops,
                total: filteredLoops.length,
                limit,
                offset,
                hasMore: offset + limit < filteredLoops.length,
                timestamp: new Date().toISOString()
            }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // ==== NESTED PATH ROUTES (more specific patterns first) ====
    // POST /api/loops/v2/:loopId/start - Start loop execution
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/start$/) && req.method === 'POST') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        try {
            const loop = await readLoopStorage(loopId);
            if (!loop) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Loop not found' }));
                return true;
            }
            // Can only start created or paused loops
            if (!['created', 'paused'].includes(loop.status.toLowerCase())) {
                res.writeHead(400, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    success: false,
                    error: `Cannot start loop with status: ${loop.status}`
                }));
                return true;
            }
            // Update loop status
            loop.status = LoopStatus.RUNNING;
            loop.updated_at = new Date().toISOString();
            await writeLoopStorage(loop);
            // Broadcast state update
            broadcastStateUpdate(loopId, LoopStatus.RUNNING);
            // Trigger ccw-loop skill execution (non-blocking)
            // The skill will check status before each action and exit gracefully on pause/stop
            executeCliTool({
                tool: 'claude',
                prompt: `/ccw-loop --loop-id ${loopId} --auto`,
                mode: 'write',
                workingDir: workflowDir
            }).catch((error) => {
                // Log error but don't fail the start request
                console.error(`Failed to trigger ccw-loop skill for ${loopId}:`, error);
                // Update loop status to failed
                readLoopStorage(loopId).then(async (failedLoop) => {
                    if (failedLoop) {
                        failedLoop.status = LoopStatus.FAILED;
                        failedLoop.failure_reason = `Skill execution failed: ${error.message}`;
                        failedLoop.completed_at = new Date().toISOString();
                        await writeLoopStorage(failedLoop);
                        broadcastStateUpdate(loopId, LoopStatus.FAILED);
                    }
                });
            });
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: true, data: loop, message: 'Loop started' }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // POST /api/loops/v2/:loopId/pause - Pause loop
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/pause$/) && req.method === 'POST') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        try {
            const loop = await readLoopStorage(loopId);
            if (!loop) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Loop not found' }));
                return true;
            }
            // Can only pause running loops
            if (loop.status !== LoopStatus.RUNNING) {
                res.writeHead(400, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    success: false,
                    error: `Cannot pause loop with status: ${loop.status}`
                }));
                return true;
            }
            loop.status = LoopStatus.PAUSED;
            loop.updated_at = new Date().toISOString();
            await writeLoopStorage(loop);
            broadcastStateUpdate(loopId, LoopStatus.PAUSED);
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: true, data: loop, message: 'Loop paused' }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // POST /api/loops/v2/:loopId/resume - Resume loop
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/resume$/) && req.method === 'POST') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        try {
            const loop = await readLoopStorage(loopId);
            if (!loop) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Loop not found' }));
                return true;
            }
            // Can only resume paused loops
            if (loop.status !== LoopStatus.PAUSED) {
                res.writeHead(400, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    success: false,
                    error: `Cannot resume loop with status: ${loop.status}`
                }));
                return true;
            }
            loop.status = LoopStatus.RUNNING;
            loop.updated_at = new Date().toISOString();
            await writeLoopStorage(loop);
            broadcastStateUpdate(loopId, LoopStatus.RUNNING);
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: true, data: loop, message: 'Loop resumed' }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // POST /api/loops/v2/:loopId/stop - Stop loop
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/stop$/) && req.method === 'POST') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        try {
            const loop = await readLoopStorage(loopId);
            if (!loop) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Loop not found' }));
                return true;
            }
            // Can only stop running or paused loops
            if (![LoopStatus.RUNNING, LoopStatus.PAUSED, LoopStatus.CREATED].includes(loop.status)) {
                res.writeHead(400, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    success: false,
                    error: `Cannot stop loop with status: ${loop.status}`
                }));
                return true;
            }
            loop.status = LoopStatus.FAILED;
            loop.failure_reason = 'Manually stopped by user';
            loop.completed_at = new Date().toISOString();
            loop.updated_at = loop.completed_at;
            await writeLoopStorage(loop);
            broadcastStateUpdate(loopId, LoopStatus.FAILED);
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: true, data: loop, message: 'Loop stopped' }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // ==== SINGLE PARAM ROUTES (must come after nested routes) ====
    // GET /api/loops/v2/:loopId - Get loop details
    if (pathname.match(/^\/api\/loops\/v2\/[^/]+$/) && req.method === 'GET') {
        const loopId = pathname.split('/').pop();
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        try {
            const loop = await readLoopStorage(loopId);
            if (!loop) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Loop not found' }));
                return true;
            }
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: true, data: loop }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // PUT /api/loops/v2/:loopId - Update loop metadata
    if (pathname.match(/^\/api\/loops\/v2\/[^/]+$/) && req.method === 'PUT') {
        const loopId = pathname.split('/').pop();
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        handlePostRequest(req, res, async (body) => {
            const { title, description, max_iterations, tags, priority, notes } = body;
            try {
                const loop = await readLoopStorage(loopId);
                if (!loop) {
                    return { success: false, error: 'Loop not found', status: 404 };
                }
                // Can only update created or paused loops
                if (![LoopStatus.CREATED, LoopStatus.PAUSED, LoopStatus.FAILED, LoopStatus.COMPLETED].includes(loop.status)) {
                    return { success: false, error: `Cannot update loop with status: ${loop.status}`, status: 400 };
                }
                // Validate and apply updates
                if (title !== undefined) {
                    if (typeof title !== 'string' || title.trim().length === 0) {
                        return { success: false, error: 'title must be a non-empty string', status: 400 };
                    }
                    loop.title = title.trim();
                }
                if (description !== undefined) {
                    if (typeof description !== 'string') {
                        return { success: false, error: 'description must be a string', status: 400 };
                    }
                    loop.description = description.trim();
                }
                if (max_iterations !== undefined) {
                    if (typeof max_iterations !== 'number' || max_iterations < 1) {
                        return { success: false, error: 'max_iterations must be a positive number', status: 400 };
                    }
                    loop.max_iterations = max_iterations;
                }
                // Extended metadata fields
                if (tags !== undefined) {
                    if (!Array.isArray(tags) || !tags.every(t => typeof t === 'string')) {
                        return { success: false, error: 'tags must be an array of strings', status: 400 };
                    }
                    loop.tags = tags;
                }
                if (priority !== undefined) {
                    if (!['low', 'medium', 'high'].includes(priority)) {
                        return { success: false, error: 'priority must be one of: low, medium, high', status: 400 };
                    }
                    loop.priority = priority;
                }
                if (notes !== undefined) {
                    if (typeof notes !== 'string') {
                        return { success: false, error: 'notes must be a string', status: 400 };
                    }
                    loop.notes = notes.trim();
                }
                loop.updated_at = new Date().toISOString();
                await writeLoopStorage(loop);
                broadcastStateUpdate(loopId, loop.status);
                return { success: true, data: loop };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    // PATCH /api/loops/v2/:loopId/status - Quick status update
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/status$/) && req.method === 'PATCH') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        handlePostRequest(req, res, async (body) => {
            const { status } = body;
            if (!status || typeof status !== 'string') {
                return { success: false, error: 'status is required', status: 400 };
            }
            if (!Object.values(LoopStatus).includes(status)) {
                return { success: false, error: `Invalid status: ${status}`, status: 400 };
            }
            try {
                const loop = await readLoopStorage(loopId);
                if (!loop) {
                    return { success: false, error: 'Loop not found', status: 404 };
                }
                loop.status = status;
                loop.updated_at = new Date().toISOString();
                if (status === LoopStatus.COMPLETED && !loop.completed_at) {
                    loop.completed_at = new Date().toISOString();
                }
                await writeLoopStorage(loop);
                broadcastStateUpdate(loopId, loop.status);
                return { success: true, data: loop };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    // DELETE /api/loops/v2/:loopId - Delete loop
    if (pathname.match(/^\/api\/loops\/v2\/[^/]+$/) && req.method === 'DELETE') {
        const loopId = pathname.split('/').pop();
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        try {
            const loop = await readLoopStorage(loopId);
            if (!loop) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Loop not found' }));
                return true;
            }
            // Cannot delete running loops
            if (loop.status === LoopStatus.RUNNING) {
                res.writeHead(400, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    success: false,
                    error: 'Cannot delete running loop. Stop it first.'
                }));
                return true;
            }
            await deleteLoopStorage(loopId);
            // Broadcast deletion
            try {
                broadcastToClients({
                    type: 'LOOP_DELETED',
                    loop_id: loopId
                });
            }
            catch {
                // Ignore broadcast errors
            }
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: true, message: 'Loop deleted' }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // ==== TASK MANAGEMENT ENDPOINTS ====
    // Helper to create TaskStorageManager instance
    const createTaskManager = () => {
        return new TaskStorageManager(workflowDir);
    };
    // POST /api/loops/v2/:loopId/tasks - Add task to loop
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/tasks$/) && req.method === 'POST') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        handlePostRequest(req, res, async (body) => {
            const { description, tool, mode, prompt_template, command, on_error } = body;
            // Validation
            if (!description || typeof description !== 'string' || description.trim().length === 0) {
                return { success: false, error: 'description is required', status: 400 };
            }
            if (!tool || typeof tool !== 'string') {
                return { success: false, error: 'tool is required', status: 400 };
            }
            // Get enabled tools from cli-tools.json dynamically
            const cliToolsConfig = loadClaudeCliTools(os.homedir());
            const enabledTools = Object.entries(cliToolsConfig.tools || {})
                .filter(([_, config]) => config.enabled === true)
                .map(([name]) => name);
            // Also allow 'bash' as a special case (built-in tool)
            const validTools = ['bash', ...enabledTools];
            if (!validTools.includes(tool)) {
                return { success: false, error: `tool must be one of enabled tools: ${validTools.join(', ')}`, status: 400 };
            }
            if (!mode || typeof mode !== 'string') {
                return { success: false, error: 'mode is required', status: 400 };
            }
            const validModes = ['analysis', 'write', 'review'];
            if (!validModes.includes(mode)) {
                return { success: false, error: `mode must be one of: ${validModes.join(', ')}`, status: 400 };
            }
            if (!prompt_template || typeof prompt_template !== 'string' || prompt_template.trim().length === 0) {
                return { success: false, error: 'prompt_template is required', status: 400 };
            }
            try {
                const taskManager = createTaskManager();
                const task = await taskManager.addTask(loopId, {
                    description: description.trim(),
                    tool,
                    mode,
                    prompt_template: prompt_template.trim(),
                    command,
                    on_error
                });
                // Broadcast task added
                try {
                    broadcastToClients({
                        type: 'TASK_ADDED',
                        loop_id: loopId,
                        task_id: task.task_id,
                        task: task
                    });
                }
                catch {
                    // Ignore broadcast errors
                }
                return { success: true, data: task };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    // GET /api/loops/v2/:loopId/tasks - List all tasks for loop
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/tasks$/) && req.method === 'GET') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        try {
            const taskManager = createTaskManager();
            const tasks = await taskManager.getTasks(loopId);
            // Sort by order
            tasks.sort((a, b) => a.order - b.order);
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({
                success: true,
                data: tasks,
                total: tasks.length,
                loop_id: loopId,
                timestamp: new Date().toISOString()
            }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // GET /api/loops/v2/tasks/:taskId - Get single task (taskId lookup)
    if (pathname.match(/^\/api\/loops\/v2\/tasks\/[^/]+$/) && req.method === 'GET') {
        const taskId = pathname.split('/').pop();
        if (!taskId || !isValidId(taskId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid task ID format' }));
            return true;
        }
        try {
            const taskManager = createTaskManager();
            // Get all loops and search for the task
            const loops = await listLoops();
            let foundTask = null;
            let foundLoopId = null;
            for (const loop of loops) {
                const loopId = loop.loop_id;
                try {
                    const tasks = await taskManager.getTasks(loopId);
                    const task = tasks.find(t => t.task_id === taskId);
                    if (task) {
                        foundTask = task;
                        foundLoopId = loopId;
                        break;
                    }
                }
                catch {
                    continue;
                }
            }
            if (!foundTask) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Task not found' }));
                return true;
            }
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({
                success: true,
                data: { ...foundTask, loop_id: foundLoopId },
                timestamp: new Date().toISOString()
            }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // PUT /api/loops/v2/tasks/:taskId - Update task (taskId lookup)
    if (pathname.match(/^\/api\/loops\/v2\/tasks\/[^/]+$/) && req.method === 'PUT') {
        const taskId = pathname.split('/').pop();
        if (!taskId || !isValidId(taskId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid task ID format' }));
            return true;
        }
        handlePostRequest(req, res, async (body) => {
            const { loop_id, description, tool, mode, prompt_template, command, on_error } = body;
            if (!loop_id || typeof loop_id !== 'string') {
                return { success: false, error: 'loop_id is required', status: 400 };
            }
            if (!isValidId(loop_id)) {
                return { success: false, error: 'Invalid loop_id format', status: 400 };
            }
            try {
                const taskManager = createTaskManager();
                const updatedTask = await taskManager.updateTask(loop_id, taskId, {
                    description,
                    tool,
                    mode,
                    prompt_template,
                    command,
                    on_error
                });
                if (!updatedTask) {
                    return { success: false, error: 'Task not found', status: 404 };
                }
                // Broadcast task updated
                try {
                    broadcastToClients({
                        type: 'TASK_UPDATED',
                        loop_id: loop_id,
                        task_id: taskId,
                        task: updatedTask
                    });
                }
                catch {
                    // Ignore broadcast errors
                }
                return { success: true, data: updatedTask };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    // DELETE /api/loops/v2/tasks/:taskId - Delete task (taskId lookup)
    if (pathname.match(/^\/api\/loops\/v2\/tasks\/[^/]+$/) && req.method === 'DELETE') {
        const taskId = pathname.split('/').pop();
        if (!taskId || !isValidId(taskId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid task ID format' }));
            return true;
        }
        // Get loop_id from query parameter
        const urlObj = new URL(req.url || '', `http://localhost`);
        const loopId = urlObj.searchParams.get('loop_id');
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'loop_id query parameter is required' }));
            return true;
        }
        try {
            const taskManager = createTaskManager();
            const deleted = await taskManager.deleteTask(loopId, taskId);
            if (!deleted) {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: false, error: 'Task not found' }));
                return true;
            }
            // Broadcast task deleted
            try {
                broadcastToClients({
                    type: 'TASK_DELETED',
                    loop_id: loopId,
                    task_id: taskId
                });
            }
            catch {
                // Ignore broadcast errors
            }
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: true, message: 'Task deleted' }));
            return true;
        }
        catch (error) {
            res.writeHead(500, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: error.message }));
            return true;
        }
    }
    // PUT /api/loops/v2/:loopId/tasks/reorder - Reorder tasks
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/tasks\/reorder$/) && req.method === 'PUT') {
        const loopId = pathname.split('/').slice(-3)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        handlePostRequest(req, res, async (body) => {
            const { ordered_task_ids } = body;
            if (!ordered_task_ids || !Array.isArray(ordered_task_ids)) {
                return { success: false, error: 'ordered_task_ids must be an array', status: 400 };
            }
            if (ordered_task_ids.length === 0) {
                return { success: false, error: 'ordered_task_ids cannot be empty', status: 400 };
            }
            try {
                const taskManager = createTaskManager();
                const reorderedTasks = await taskManager.reorderTasks(loopId, { ordered_task_ids });
                // Broadcast tasks reordered
                try {
                    broadcastToClients({
                        type: 'TASK_REORDERED',
                        loop_id: loopId,
                        ordered_task_ids: ordered_task_ids,
                        tasks: reorderedTasks
                    });
                }
                catch {
                    // Ignore broadcast errors
                }
                return { success: true, data: reorderedTasks };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    // ==== ADVANCED TASK FEATURES ====
    // POST /api/loops/v2/:loopId/import - Import tasks from issue
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/import$/) && req.method === 'POST') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        handlePostRequest(req, res, async (body) => {
            const { issue_id } = body;
            if (!issue_id || typeof issue_id !== 'string') {
                return { success: false, error: 'issue_id is required', status: 400 };
            }
            try {
                // Fetch issue data from issue-manager
                const { readFile } = await import('fs/promises');
                const { existsSync } = await import('fs');
                const issuesDir = join(workflowDir, '.workflow', 'issues');
                const issuesPath = join(issuesDir, 'issues.jsonl');
                let issueData = null;
                // Try reading from active issues
                if (existsSync(issuesPath)) {
                    const content = await readFile(issuesPath, 'utf-8');
                    const issues = content.split('\n').filter(line => line.trim()).map(line => JSON.parse(line));
                    issueData = issues.find((i) => i.id === issue_id);
                }
                // Try reading from history if not found
                if (!issueData) {
                    const historyPath = join(issuesDir, 'issue-history.jsonl');
                    if (existsSync(historyPath)) {
                        const content = await readFile(historyPath, 'utf-8');
                        const historyIssues = content.split('\n').filter(line => line.trim()).map(line => JSON.parse(line));
                        issueData = historyIssues.find((i) => i.id === issue_id);
                    }
                }
                if (!issueData) {
                    return { success: false, error: `Issue ${issue_id} not found`, status: 404 };
                }
                // Load solutions to get bound solution tasks
                const solutionsPath = join(issuesDir, 'solutions', `${issue_id}.jsonl`);
                let tasksToImport = [];
                if (existsSync(solutionsPath)) {
                    const solutionsContent = await readFile(solutionsPath, 'utf-8');
                    const solutions = solutionsContent.split('\n').filter(line => line.trim()).map(line => JSON.parse(line));
                    // Get tasks from bound solution
                    const boundSolution = solutions.find((s) => s.id === issueData.bound_solution_id) ||
                        solutions.find((s) => s.is_bound) ||
                        solutions[0];
                    if (boundSolution?.tasks) {
                        tasksToImport = boundSolution.tasks;
                    }
                }
                if (tasksToImport.length === 0) {
                    return { success: false, error: 'No tasks found in issue. Bind a solution with tasks first.', status: 400 };
                }
                // Broadcast import start
                broadcastToClients({
                    type: 'LOOP_TASK_IMPORT_PROGRESS',
                    loop_id: loopId,
                    payload: {
                        stage: 'starting',
                        total: tasksToImport.length,
                        imported: 0
                    }
                });
                const taskManager = createTaskManager();
                const createdTasks = [];
                // Convert issue tasks to loop tasks
                for (let i = 0; i < tasksToImport.length; i++) {
                    const issueTask = tasksToImport[i];
                    // Map issue task fields to loop task fields
                    const taskRequest = {
                        description: issueTask.description || issueTask.title || `Task ${i + 1}`,
                        tool: mapIssueToolToLoopTool(issueTask.tool) || 'gemini',
                        mode: mapIssueModeToLoopMode(issueTask.mode) || 'write',
                        prompt_template: issueTask.prompt_template || issueTask.prompt || `Execute: ${issueTask.description || issueTask.title}`,
                        command: issueTask.command,
                        on_error: mapIssueOnError(issueTask.on_error)
                    };
                    const task = await taskManager.addTask(loopId, taskRequest);
                    createdTasks.push(task);
                    // Broadcast progress
                    broadcastToClients({
                        type: 'LOOP_TASK_IMPORT_PROGRESS',
                        loop_id: loopId,
                        payload: {
                            stage: 'importing',
                            total: tasksToImport.length,
                            imported: i + 1,
                            current_task: task
                        }
                    });
                }
                // Broadcast completion
                broadcastToClients({
                    type: 'LOOP_TASK_IMPORT_COMPLETE',
                    loop_id: loopId,
                    payload: {
                        total: tasksToImport.length,
                        imported: createdTasks.length,
                        tasks: createdTasks
                    }
                });
                return {
                    success: true,
                    data: createdTasks,
                    message: `Imported ${createdTasks.length} tasks from issue ${issue_id}`
                };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    // POST /api/loops/v2/:loopId/generate - Generate tasks via Gemini
    if (pathname.match(/\/api\/loops\/v2\/[^/]+\/generate$/) && req.method === 'POST') {
        const loopId = pathname.split('/').slice(-2)[0];
        if (!loopId || !isValidId(loopId)) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ success: false, error: 'Invalid loop ID format' }));
            return true;
        }
        handlePostRequest(req, res, async (body) => {
            const { tool = 'gemini', count } = body;
            try {
                // Get loop details for context
                const loop = await readLoopStorage(loopId);
                if (!loop) {
                    return { success: false, error: 'Loop not found', status: 404 };
                }
                // Broadcast generation start
                broadcastToClients({
                    type: 'LOOP_TASK_GENERATION_PROGRESS',
                    loop_id: loopId,
                    payload: {
                        stage: 'analyzing',
                        message: 'Analyzing loop description...'
                    }
                });
                // Build generation prompt
                const generatePrompt = `PURPOSE: Generate ${count || 5} specific tasks for loop execution
TASK: Analyze the loop description and generate a list of actionable tasks that can be executed via CLI tools. Each task should have clear description, tool selection, mode, and prompt template.
MODE: analysis
CONTEXT: Loop title: ${loop.title}
Loop description: ${loop.description || 'No description provided'}
Max iterations: ${loop.max_iterations}
EXPECTED: Return a JSON array of tasks with this exact structure:
[
  {
    "description": "Clear task description",
    "tool": "gemini|codex|qwen|bash",
    "mode": "analysis|write|review",
    "prompt_template": "PURPOSE: ... TASK: ... MODE: analysis CONTEXT: @**/* EXPECTED: ...",
    "on_error": "continue|pause|fail_fast"
  }
]
CONSTRAINTS: Generate ${count || 5} tasks | Use gemini for AI tasks | Use bash for CLI commands | Include error handling strategy`;
                // Call CLI with gemini to generate tasks
                let generatedTasks = [];
                let outputBuffer = '';
                const result = await executeCliTool({
                    tool: tool === 'codex' || tool === 'qwen' || tool === 'gemini' ? tool : 'gemini',
                    prompt: generatePrompt,
                    mode: 'analysis',
                    format: 'plain',
                    cd: workflowDir,
                    timeout: 120000, // 2 minutes timeout
                    stream: true
                }, (unit) => {
                    // Collect output
                    outputBuffer += unit.content;
                    // Broadcast partial output for progress
                    broadcastToClients({
                        type: 'LOOP_TASK_GENERATION_PROGRESS',
                        loop_id: loopId,
                        payload: {
                            stage: 'generating',
                            message: 'Generating tasks...',
                            output: unit.content
                        }
                    });
                });
                if (!result.success) {
                    return { success: false, error: 'Failed to generate tasks via CLI', status: 500 };
                }
                // Parse generated tasks from CLI output
                generatedTasks = parseGeneratedTasks(outputBuffer);
                if (generatedTasks.length === 0) {
                    return {
                        success: false,
                        error: 'No valid tasks generated. Check CLI output for details.',
                        status: 500,
                        output: outputBuffer
                    };
                }
                // Broadcast import start
                broadcastToClients({
                    type: 'LOOP_TASK_GENERATION_PROGRESS',
                    loop_id: loopId,
                    payload: {
                        stage: 'importing',
                        message: `Importing ${generatedTasks.length} generated tasks...`,
                        total: generatedTasks.length,
                        imported: 0
                    }
                });
                const taskManager = createTaskManager();
                const createdTasks = [];
                // Add generated tasks to loop
                for (let i = 0; i < generatedTasks.length; i++) {
                    const genTask = generatedTasks[i];
                    const taskRequest = {
                        description: genTask.description || `Generated Task ${i + 1}`,
                        tool: validateTool(genTask.tool) ? genTask.tool : 'gemini',
                        mode: validateMode(genTask.mode) ? genTask.mode : 'write',
                        prompt_template: genTask.prompt_template || `Execute task: ${genTask.description}`,
                        command: genTask.command,
                        on_error: validateOnError(genTask.on_error) ? genTask.on_error : 'continue'
                    };
                    const task = await taskManager.addTask(loopId, taskRequest);
                    createdTasks.push(task);
                    // Broadcast progress
                    broadcastToClients({
                        type: 'LOOP_TASK_GENERATION_PROGRESS',
                        loop_id: loopId,
                        payload: {
                            stage: 'importing',
                            message: `Importing task ${i + 1}/${generatedTasks.length}...`,
                            total: generatedTasks.length,
                            imported: i + 1,
                            current_task: task
                        }
                    });
                }
                // Broadcast completion
                broadcastToClients({
                    type: 'LOOP_TASK_GENERATION_COMPLETE',
                    loop_id: loopId,
                    payload: {
                        total: generatedTasks.length,
                        imported: createdTasks.length,
                        tasks: createdTasks
                    }
                });
                return {
                    success: true,
                    data: createdTasks,
                    message: `Generated and imported ${createdTasks.length} tasks`
                };
            }
            catch (error) {
                return { success: false, error: error.message, status: 500 };
            }
        });
        return true;
    }
    return false;
}
/**
 * Sanitize ID parameter to prevent path traversal attacks
 * @returns true if valid, false if invalid
 */
function isValidId(id) {
    if (!id)
        return false;
    // Block path traversal attempts and null bytes
    if (id.includes('/') || id.includes('\\') || id === '..' || id === '.')
        return false;
    if (id.includes('\0'))
        return false;
    return true;
}
/**
 * Get enabled tools list from cache
 * If cache is not initialized, it will load from config (fallback for lazy initialization)
 */
function getEnabledToolsList() {
    // Return cached value if available
    if (cachedEnabledTools) {
        return cachedEnabledTools;
    }
    // Fallback: lazy initialization if cache not initialized (shouldn't happen in normal operation)
    console.warn('[Loop V2] CLI tools cache not initialized, performing lazy load');
    initializeCliToolsCache();
    return cachedEnabledTools || ['bash', 'gemini', 'qwen', 'codex', 'claude'];
}
/**
 * Map issue tool to loop tool
 */
function mapIssueToolToLoopTool(tool) {
    const validTools = getEnabledToolsList();
    if (validTools.includes(tool))
        return tool;
    // Map aliases
    if (tool === 'ccw')
        return 'gemini';
    if (tool === 'ai')
        return 'gemini';
    return null;
}
/**
 * Map issue mode to loop mode
 */
function mapIssueModeToLoopMode(mode) {
    const validModes = ['analysis', 'write', 'review'];
    if (validModes.includes(mode))
        return mode;
    // Map aliases
    if (mode === 'read')
        return 'analysis';
    if (mode === 'create' || mode === 'modify')
        return 'write';
    return null;
}
/**
 * Map issue on_error value
 */
function mapIssueOnError(onError) {
    const validValues = ['continue', 'pause', 'fail_fast'];
    if (validValues.includes(onError))
        return onError;
    // Map aliases
    if (onError === 'stop')
        return 'pause';
    if (onError === 'abort')
        return 'fail_fast';
    return undefined;
}
/**
 * Validate tool value
 */
function validateTool(tool) {
    const validTools = getEnabledToolsList();
    return validTools.includes(tool);
}
/**
 * Validate mode value
 */
function validateMode(mode) {
    const validModes = ['analysis', 'write', 'review'];
    return validModes.includes(mode);
}
/**
 * Validate on_error value
 */
function validateOnError(onError) {
    const validValues = ['continue', 'pause', 'fail_fast'];
    return validValues.includes(onError);
}
/**
 * Parse generated tasks from CLI output
 * Extracts JSON array from output, handles various response formats
 */
function parseGeneratedTasks(output) {
    let tasks = [];
    // Try to find JSON array in output
    const jsonMatch = output.match(/\[[\s\S]*\]/);
    if (jsonMatch) {
        try {
            tasks = JSON.parse(jsonMatch[0]);
        }
        catch {
            // Invalid JSON, try alternative parsing
        }
    }
    // If no valid JSON array found, try parsing line by line
    if (tasks.length === 0) {
        const lines = output.split('\n');
        for (const line of lines) {
            const trimmed = line.trim();
            if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
                try {
                    tasks.push(JSON.parse(trimmed));
                }
                catch {
                    // Skip invalid lines
                }
            }
        }
    }
    // Filter and validate task objects
    return tasks.filter(t => t &&
        typeof t === 'object' &&
        (t.description || t.title || t.task) &&
        (t.tool || t.mode || t.prompt_template)).map(t => ({
        description: t.description || t.title || t.task || 'Untitled task',
        tool: t.tool || 'gemini',
        mode: t.mode || 'write',
        prompt_template: t.prompt_template || t.prompt || `Execute: ${t.description || t.title || t.task}`,
        command: t.command,
        on_error: t.on_error || 'continue'
    }));
}
//# sourceMappingURL=loop-v2-routes.js.map