"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createModificationRouter = exports.withTimeoutFallback = void 0;
const crypto_1 = require("crypto");
const path_1 = __importDefault(require("path"));
const express_1 = require("express");
const index_core_1 = require("../index-core");
const agent_1 = require("../services/agent");
const log = (0, index_core_1.createLogger)('ModificationRoutes');
const MAX_FILE_MATCHES_PER_ELEMENT = 6;
const ELEMENT_ENRICHMENT_TIMEOUT_MS = 20_000;
const withTimeoutFallback = async (params) => new Promise((resolve, reject) => {
    let isSettled = false;
    const timeoutId = setTimeout(() => {
        if (isSettled) {
            return;
        }
        isSettled = true;
        resolve(params.onTimeout());
    }, params.timeoutMs);
    params.operation
        .then((value) => {
        if (isSettled) {
            return;
        }
        isSettled = true;
        clearTimeout(timeoutId);
        resolve(value);
    })
        .catch((error) => {
        if (isSettled) {
            return;
        }
        isSettled = true;
        clearTimeout(timeoutId);
        reject(error);
    });
});
exports.withTimeoutFallback = withTimeoutFallback;
/** Shorten an absolute file path to a relative one for display */
const shortenPath = (filePath, projectPath) => {
    if (!filePath)
        return 'file';
    const relative = path_1.default.relative(projectPath, String(filePath));
    return relative || filePath;
};
/** Formats a tool call into a human-readable progress message with context */
const formatToolProgress = (name, input, projectPath) => {
    const data = input;
    switch (name) {
        case 'Read':
            return `[Read] Reading ${shortenPath(data.file_path, projectPath)}`;
        case 'Grep':
            return `[Grep] Searching for "${data.pattern || ''}"`;
        case 'Glob':
            return `[Glob] Finding files matching ${data.pattern || ''}`;
        case 'Edit':
            return `[Edit] Editing ${shortenPath(data.file_path, projectPath)}`;
        case 'Write':
            return `[Write] Writing ${shortenPath(data.file_path, projectPath)}`;
        case 'Bash': {
            const desc = data.description;
            const cmd = (data.command || '').trim();
            return `[Bash] Running: ${desc || cmd}`;
        }
        case 'Task':
            return '[Task] Applying changes';
        default:
            return `[${name}]`;
    }
};
// SSE Helper Functions for Hybrid Streaming
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setupSSEHeaders = (res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Cache-Control');
    res.setHeader('X-Accel-Buffering', 'no');
    if (typeof res.flushHeaders === 'function') {
        res.flushHeaders();
    }
    res.write(': connected\n\n');
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sendProgress = (res, message) => {
    if (res.writableEnded || res.destroyed)
        return;
    res.write(`event: progress\n`);
    res.write(`data: ${JSON.stringify({ content: message + '\n', done: false })}\n\n`);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sendChunk = (res, content) => {
    if (res.writableEnded || res.destroyed)
        return;
    res.write(`event: chunk\n`);
    res.write(`data: ${JSON.stringify({ content, done: false })}\n\n`);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sendCompletion = (res, data) => {
    if (res.writableEnded || res.destroyed)
        return;
    res.write(`event: done\n`);
    res.write(`data: ${JSON.stringify({ ...data, done: true })}\n\n`);
    res.end();
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sendError = (res, error, details) => {
    if (res.writableEnded)
        return;
    res.write(`event: error\n`);
    res.write(`data: ${JSON.stringify({ error, details, done: true })}\n\n`);
    res.end();
};
/**
 * Populate missing element file hints so agent discovery starts with stronger context.
 *
 * @effect Searches project files for elements missing filePaths
 */
const enrichElementsForAgent = async (projectPath, elements) => {
    if (elements.length === 0)
        return elements;
    const enrichmentStart = Date.now();
    log.info('Starting element enrichment for agent context', {
        elementCount: elements.length,
    });
    const searchService = new index_core_1.ComponentSearchService();
    const enriched = await Promise.all(elements.map(async (element) => {
        if (element.filePaths && element.filePaths.length > 0) {
            return element;
        }
        try {
            const matches = await searchService.findMatchingComponents(projectPath, element);
            if (matches.length === 0) {
                return element;
            }
            return {
                ...element,
                filePaths: matches
                    .slice(0, MAX_FILE_MATCHES_PER_ELEMENT)
                    .map((m) => ({
                    filePath: m.filePath,
                })),
            };
        }
        catch (error) {
            log.warn('Failed to enrich element file paths:', {
                xpath: element.xpath,
                error: error instanceof Error ? error.message : String(error),
            });
            return element;
        }
    }));
    const withFileHints = enriched.filter((element) => element.filePaths && element.filePaths.length > 0).length;
    const newlyEnriched = enriched.filter((element, index) => {
        const before = elements[index];
        const hadFileHintsBefore = !!(before.filePaths && before.filePaths.length > 0);
        const hasFileHintsNow = !!(element.filePaths && element.filePaths.length > 0);
        return !hadFileHintsBefore && hasFileHintsNow;
    }).length;
    log.info('Completed element enrichment for agent context', {
        durationMs: Date.now() - enrichmentStart,
        elementCount: elements.length,
        withFileHints,
        newlyEnriched,
        unresolved: elements.length - withFileHints,
    });
    return enriched;
};
const createModificationRouter = (projectPath, telemetry) => {
    const router = (0, express_1.Router)();
    // Agentic code modification using Claude Agent SDK
    router.post('/code/modify-agentic', async (req, res) => {
        const startTime = Date.now();
        const rawRequest = (req.body ?? {});
        const traceId = rawRequest.traceId?.trim() || (0, crypto_1.randomUUID)();
        const agentRunId = (0, crypto_1.randomUUID)();
        const posthogSessionId = rawRequest.posthogSessionId?.trim() || undefined;
        let isRunStartedTracked = false;
        let isRunFailureTracked = false;
        let heartbeatInterval = null;
        try {
            const request = rawRequest;
            const { instruction, elements, images, designTokens, sessionId, model } = request;
            log.info('Agentic code modification requested:', {
                elementCount: elements?.length || 0,
                instructionLength: instruction?.length || 0,
            });
            // Validation
            if (!instruction?.trim()) {
                return res.status(400).json({
                    error: 'Instruction is required',
                    details: 'request.instruction must be a non-empty string',
                });
            }
            // Setup SSE streaming
            setupSSEHeaders(res);
            sendProgress(res, 'Starting agent run...');
            let isClientDisconnected = false;
            req.on('aborted', () => {
                isClientDisconnected = true;
            });
            res.on('close', () => {
                if (!res.writableEnded) {
                    isClientDisconnected = true;
                }
            });
            heartbeatInterval = setInterval(() => {
                sendProgress(res, 'Agent is working...');
            }, 5_000);
            heartbeatInterval.unref();
            const requestedElements = (elements || []);
            sendProgress(res, 'Preparing element context...');
            const enrichedElements = await (0, exports.withTimeoutFallback)({
                operation: enrichElementsForAgent(projectPath, requestedElements),
                timeoutMs: ELEMENT_ENRICHMENT_TIMEOUT_MS,
                onTimeout: () => {
                    log.warn('Element enrichment timed out, continuing with request payload', {
                        timeoutMs: ELEMENT_ENRICHMENT_TIMEOUT_MS,
                        elementCount: requestedElements.length,
                    });
                    return requestedElements;
                },
            });
            const enrichedWithFiles = enrichedElements.filter((el) => el.filePaths && el.filePaths.length > 0).length;
            log.info('Prepared agent context elements:', {
                requested: requestedElements.length,
                withFileHints: enrichedWithFiles,
            });
            // Create agent service (uses gateway with Supabase JWT auth)
            const agentService = new agent_1.AgentModService(projectPath);
            telemetry?.trackAgentRunStarted({
                traceId,
                agentRunId,
                instructionLength: instruction.length,
                elementCount: requestedElements.length,
                posthogSessionId,
                resumeSessionId: sessionId,
                model,
            });
            isRunStartedTracked = true;
            // Execute agentic modification with progress streaming
            await agentService.executeAgenticModification(instruction, enrichedElements, (event) => {
                if (isClientDisconnected || res.writableEnded || res.destroyed) {
                    log.debug(`Agent stream progress skipped: disconnected=${String(isClientDisconnected)} writableEnded=${String(res.writableEnded)} destroyed=${String(res.destroyed)}`);
                    return;
                }
                if (event.type === 'step') {
                    const step = event.step;
                    if (step?.type === 'reasoning') {
                        const text = step.data.text;
                        const formattedText = text + '\n\n';
                        sendChunk(res, formattedText);
                        sendProgress(res, `Agent: ${text.slice(0, 200)}...`);
                    }
                    else if (step?.type === 'tool_call') {
                        const data = step.data;
                        sendProgress(res, formatToolProgress(data.name, data.input, projectPath));
                    }
                }
                else if (event.type === 'complete') {
                    const result = event.result;
                    // Track successful agent modification
                    telemetry?.trackAgentModification({
                        success: true,
                        instruction,
                        instructionLength: instruction.length,
                        elementCount: enrichedElements.length,
                        fileCount: result.filesModified.length,
                        modificationTimeMs: Date.now() - startTime,
                        model: result.model,
                        sessionId: result.sessionId,
                        ttfmMs: result.ttfmMs,
                        traceId,
                        agentRunId,
                        posthogSessionId,
                    });
                    telemetry?.trackAgentRunCompleted({
                        traceId,
                        agentRunId,
                        sessionId: result.sessionId,
                        posthogSessionId,
                        model: result.model,
                        durationMs: Date.now() - startTime,
                        ttfmMs: result.ttfmMs,
                        ttfwMs: result.ttfwMs,
                        fileCount: result.filesModified.length,
                        toolSummary: result.toolSummary,
                    });
                    // Send completion
                    const response = {
                        changes: result.changes,
                        filesModified: result.filesModified.map((fp) => ({
                            filePath: fp,
                            changes: 'Modified by agent',
                        })),
                        totalElements: enrichedElements.length,
                        sessionId: result.sessionId,
                        model: result.model,
                        ttfmMs: result.ttfmMs,
                        traceId,
                        agentRunId,
                    };
                    sendCompletion(res, response);
                }
                else if (event.type === 'error') {
                    // Track failed agent modification
                    telemetry?.trackAgentModification({
                        success: false,
                        instruction,
                        instructionLength: instruction.length,
                        elementCount: enrichedElements.length,
                        errorType: event.error || 'unknown',
                        modificationTimeMs: Date.now() - startTime,
                        traceId,
                        agentRunId,
                        posthogSessionId,
                    });
                    if (isRunStartedTracked && !isRunFailureTracked) {
                        isRunFailureTracked = true;
                        telemetry?.trackAgentRunFailed({
                            traceId,
                            agentRunId,
                            posthogSessionId,
                            model,
                            durationMs: Date.now() - startTime,
                            errorType: 'agent_stream_error',
                            errorMessage: event.error || 'Unknown stream error',
                        });
                    }
                    sendError(res, event.error || 'Unknown error');
                }
            }, designTokens, images, sessionId, model, {
                traceId,
                agentRunId,
                posthogSessionId,
            }, telemetry);
            if (heartbeatInterval) {
                clearInterval(heartbeatInterval);
                heartbeatInterval = null;
            }
        }
        catch (error) {
            log.error('Error in agentic code modification:', error);
            if (heartbeatInterval) {
                clearInterval(heartbeatInterval);
            }
            // Track exception in agent modification
            telemetry?.trackAgentModification({
                success: false,
                instruction: rawRequest.instruction,
                instructionLength: rawRequest.instruction?.length,
                elementCount: rawRequest.elements?.length,
                errorType: error instanceof Error ? error.name : 'unknown_exception',
                traceId,
                agentRunId,
                posthogSessionId,
            });
            if (isRunStartedTracked && !isRunFailureTracked) {
                telemetry?.trackAgentRunFailed({
                    traceId,
                    agentRunId,
                    posthogSessionId,
                    model: rawRequest.model,
                    durationMs: Date.now() - startTime,
                    errorType: error instanceof Error ? error.name : 'unknown_exception',
                    errorMessage: error instanceof Error
                        ? error.message
                        : 'Unknown agent route error',
                });
            }
            if (res.headersSent && !res.writableEnded) {
                sendError(res, error instanceof Error
                    ? error.message
                    : 'Agentic modification failed');
            }
            else if (!res.headersSent) {
                res.status(500).json({
                    error: 'Agentic modification failed',
                    details: error instanceof Error ? error.message : 'Unknown error',
                    traceId,
                    agentRunId,
                });
            }
        }
    });
    return router;
};
exports.createModificationRouter = createModificationRouter;
//# sourceMappingURL=modifications.js.map