import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { createHash } from 'crypto';
/**
 * Captures apply_patch execution failures (tool role message contents) into errorsamples.
 *
 * IMPORTANT (RouteCodex policy):
 * - Only keep apply_patch "shape" failures in errorsamples.
 * - Context/execution failures (file missing, context mismatch, expected lines mismatch)
 *   are noisy and not actionable, so they are NOT captured by default.
 *
 * Explicit opt-in for debugging:
 *   ROUTECODEX_ERRORSAMPLES_APPLY_PATCH_EXEC=1
 *
 * Default destination:
 *   ~/.routecodex/errorsamples/apply_patch_exec/<error-type>/
 *
 * Explicit override:
 *   ROUTECODEX_APPLY_PATCH_EXEC_DIR=/abs/path
 *
 * Note: if ROUTECODEX_ERRORSAMPLES_DIR is set, it is used as the base root.
 */
const MAX_SAMPLES_PER_TYPE = 250;
const EXEC_ERRORSAMPLES_ENABLED = String(process?.env?.ROUTECODEX_ERRORSAMPLES_APPLY_PATCH_EXEC || '').trim() === '1';
function resolveSamplesRoot() {
    const explicit = String(process?.env?.ROUTECODEX_APPLY_PATCH_EXEC_DIR || '').trim();
    if (explicit)
        return path.resolve(explicit);
    const base = String(process?.env?.ROUTECODEX_ERRORSAMPLES_DIR || '').trim();
    if (base)
        return path.join(path.resolve(base), 'apply_patch_exec');
    return path.join(os.homedir(), '.routecodex', 'errorsamples', 'apply_patch_exec');
}
function classifyExecutionFailure(content) {
    const raw = String(content || '');
    const trimmed = raw.trim();
    const prefix = 'apply_patch verification failed:';
    const msg = trimmed.toLowerCase().startsWith(prefix) ? trimmed.slice(prefix.length).trim() : trimmed;
    const lower = msg.toLowerCase();
    if (lower.includes('failed to read file'))
        return { errorType: 'read_file_failed', message: msg };
    if (lower.includes('no such file') || lower.includes('file not found'))
        return { errorType: 'file_not_found', message: msg };
    if (lower.includes('failed to find context'))
        return { errorType: 'context_not_found', message: msg };
    if (lower.includes('failed to find expected lines'))
        return { errorType: 'expected_lines_not_found', message: msg };
    if (lower.includes('invalid patch'))
        return { errorType: 'invalid_patch', message: msg };
    if (lower.includes('failed to parse'))
        return { errorType: 'parse_failed', message: msg };
    return { errorType: 'unknown', message: msg };
}
function isContextExecutionFailureType(errorType) {
    const t = String(errorType || '').trim().toLowerCase();
    return (t === 'read_file_failed' ||
        t === 'file_not_found' ||
        t === 'context_not_found' ||
        t === 'expected_lines_not_found');
}
function stableSampleId(fields) {
    const key = `${String(fields.errorType || 'unknown')}:${String(fields.errorMessage || '')}:` +
        `${String(fields.toolCallId || '')}:${String(fields.toolCallArgs || '')}:${String(fields.requestId || '')}:${String(fields.mode || '')}`;
    return createHash('sha1').update(key).digest('hex').slice(0, 16);
}
export function captureApplyPatchExecutionFailure(content, context) {
    try {
        if (!EXEC_ERRORSAMPLES_ENABLED)
            return;
        const { errorType, message } = classifyExecutionFailure(content);
        // Keep only non-context types (i.e. parse/format issues). Context mismatches are not captured.
        if (isContextExecutionFailureType(errorType))
            return;
        if (String(errorType || '').trim().toLowerCase() === 'unknown')
            return;
        const root = resolveSamplesRoot();
        const safeType = String(errorType || 'unknown').replace(/[^a-z0-9-]/gi, '_');
        const typeDir = path.join(root, safeType);
        if (!fs.existsSync(typeDir))
            fs.mkdirSync(typeDir, { recursive: true });
        // Check limit (best-effort; keep runtime safe)
        try {
            const existing = fs.readdirSync(typeDir).filter((f) => f.endsWith('.json'));
            if (existing.length >= MAX_SAMPLES_PER_TYPE)
                return;
        }
        catch {
            // ignore
        }
        const mode = String(context.meta?.applyPatchToolMode || '').trim();
        const id = `sample_${stableSampleId({
            errorType,
            errorMessage: message,
            toolCallId: context.toolCallId,
            toolCallArgs: context.toolCallArgs,
            requestId: context.requestId,
            mode
        })}`;
        const file = path.join(typeDir, `${id}.json`);
        if (fs.existsSync(file))
            return;
        const sample = {
            id,
            timestamp: new Date().toISOString(),
            errorType,
            errorMessage: message,
            toolCallId: context.toolCallId,
            toolCallArgs: context.toolCallArgs,
            requestId: context.requestId,
            entryEndpoint: context.entryEndpoint,
            providerKey: context.providerKey,
            model: context.model,
            source: context.source,
            meta: context.meta
        };
        fs.writeFileSync(file, JSON.stringify(sample, null, 2), 'utf-8');
    }
    catch {
        // Silently fail to avoid disrupting runtime
    }
}
export function captureApplyPatchExecutionFailuresFromProcessedRequest(processedRequest) {
    try {
        if (!EXEC_ERRORSAMPLES_ENABLED)
            return;
        const anyReq = processedRequest;
        const messages = Array.isArray(anyReq?.messages) ? anyReq.messages : [];
        if (!messages.length)
            return;
        const metadata = anyReq?.metadata || {};
        const requestId = typeof metadata?.requestId === 'string' ? metadata.requestId : undefined;
        const entryEndpoint = typeof metadata?.originalEndpoint === 'string' ? metadata.originalEndpoint : undefined;
        const providerKey = typeof metadata?.providerKey === 'string' ? metadata.providerKey : undefined;
        const model = typeof anyReq?.model === 'string' ? anyReq.model : undefined;
        const applyPatchToolMode = typeof metadata?.applyPatchToolMode === 'string' ? String(metadata.applyPatchToolMode).trim().toLowerCase() : '';
        const resolvedMode = applyPatchToolMode === 'schema' ? 'schema' : 'freeform';
        const toolCallArgsById = new Map();
        for (const msg of messages) {
            if (!msg || typeof msg !== 'object')
                continue;
            const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
            for (const tc of toolCalls) {
                const id = typeof tc?.id === 'string' ? String(tc.id) : '';
                const fn = tc?.function;
                const name = typeof fn?.name === 'string' ? String(fn.name).trim().toLowerCase() : '';
                if (!id || name !== 'apply_patch')
                    continue;
                const args = typeof fn?.arguments === 'string' ? String(fn.arguments) : '';
                if (args)
                    toolCallArgsById.set(id, args);
            }
        }
        for (const msg of messages) {
            if (!msg || typeof msg !== 'object')
                continue;
            const role = msg.role;
            const name = typeof msg.name === 'string' ? String(msg.name).trim().toLowerCase() : '';
            const content = typeof msg.content === 'string' ? String(msg.content) : '';
            if (role !== 'tool' || name !== 'apply_patch')
                continue;
            if (!content.toLowerCase().includes('apply_patch verification failed'))
                continue;
            const toolCallId = typeof msg.tool_call_id === 'string' ? String(msg.tool_call_id) : undefined;
            const toolCallArgs = toolCallId ? toolCallArgsById.get(toolCallId) : undefined;
            captureApplyPatchExecutionFailure(content, {
                requestId,
                entryEndpoint,
                providerKey,
                model,
                source: 'chat_process.req.stage4.tool_governance',
                meta: { applyPatchToolMode: resolvedMode },
                toolCallId,
                toolCallArgs
            });
        }
    }
    catch {
        // ignore
    }
}
