"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.LlmJsonSchemaError = exports.LlmJsonParseError = exports.LLM_JSON_PARSER_VERSION = void 0;
exports.isPlainObject = isPlainObject;
exports.isStringArray = isStringArray;
exports.stripOuterMarkdownCodeFence = stripOuterMarkdownCodeFence;
exports.extractFirstJsonValue = extractFirstJsonValue;
exports.parseLlmJson = parseLlmJson;
exports.tryParseLlmJson = tryParseLlmJson;
const llmPrivacy_1 = require("./llmPrivacy");
exports.LLM_JSON_PARSER_VERSION = '1.0.0';
class LlmJsonError extends Error {
    constructor(message) {
        super(message);
        this.parserVersion = exports.LLM_JSON_PARSER_VERSION;
        this.name = 'LlmJsonError';
    }
}
class LlmJsonParseError extends LlmJsonError {
    constructor(message) {
        super(message);
        this.name = 'LlmJsonParseError';
    }
}
exports.LlmJsonParseError = LlmJsonParseError;
class LlmJsonSchemaError extends LlmJsonError {
    constructor(schemaName, message) {
        super(`[${schemaName}] ${message}`);
        this.name = 'LlmJsonSchemaError';
    }
}
exports.LlmJsonSchemaError = LlmJsonSchemaError;
function isPlainObject(value) {
    return !!value && typeof value === 'object' && !Array.isArray(value);
}
function isStringArray(value) {
    return Array.isArray(value) && value.every((v) => typeof v === 'string');
}
function stripOuterMarkdownCodeFence(text) {
    const trimmed = (text || '').trim();
    if (!trimmed.startsWith('```'))
        return trimmed;
    const firstNewline = trimmed.indexOf('\n');
    if (firstNewline < 0)
        return trimmed;
    const closing = trimmed.lastIndexOf('```');
    if (closing <= firstNewline)
        return trimmed;
    return trimmed.slice(firstNewline + 1, closing).trim();
}
/**
 * Extract the first JSON object/array substring from a (possibly messy) LLM response.
 * Uses a small state machine to handle nested braces/brackets and quoted strings.
 */
function extractFirstJsonValue(text) {
    const input = (text || '').trim();
    if (!input)
        return null;
    const startObj = input.indexOf('{');
    const startArr = input.indexOf('[');
    let start = -1;
    if (startObj >= 0 && startArr >= 0)
        start = Math.min(startObj, startArr);
    else
        start = startObj >= 0 ? startObj : startArr;
    if (start < 0)
        return null;
    const stack = [];
    let inString = false;
    let escaped = false;
    for (let i = start; i < input.length; i++) {
        const ch = input[i];
        if (inString) {
            if (escaped) {
                escaped = false;
                continue;
            }
            if (ch === '\\') {
                escaped = true;
                continue;
            }
            if (ch === '"') {
                inString = false;
            }
            continue;
        }
        if (ch === '"') {
            inString = true;
            continue;
        }
        if (ch === '{') {
            stack.push('}');
            continue;
        }
        if (ch === '[') {
            stack.push(']');
            continue;
        }
        if (ch === '}' || ch === ']') {
            if (stack.length === 0)
                return null;
            const expected = stack[stack.length - 1];
            if (ch !== expected)
                return null;
            stack.pop();
            if (stack.length === 0) {
                return input.slice(start, i + 1).trim();
            }
        }
    }
    return null;
}
function parseLlmJson(text, schema) {
    const raw = stripOuterMarkdownCodeFence(text);
    const trimmed = (raw || '').trim();
    if (!trimmed)
        throw new LlmJsonParseError('Empty LLM response');
    const candidate = trimmed.startsWith('{') || trimmed.startsWith('[')
        ? trimmed
        : extractFirstJsonValue(trimmed);
    if (!candidate) {
        const safeSnippet = (0, llmPrivacy_1.redactTextForLLM)(trimmed.slice(0, 200)).text;
        throw new LlmJsonParseError(`No JSON object/array found in LLM response (len=${trimmed.length}, head="${safeSnippet}")`);
    }
    let parsed;
    try {
        parsed = JSON.parse(candidate);
    }
    catch (error) {
        throw new LlmJsonParseError(`Invalid JSON (candidateLen=${candidate.length}): ${error.message}`);
    }
    if (schema && !schema.validate(parsed)) {
        throw new LlmJsonSchemaError(schema.name, 'JSON matched but schema validation failed');
    }
    return parsed;
}
function tryParseLlmJson(text, schema) {
    try {
        return { ok: true, value: parseLlmJson(text, schema) };
    }
    catch (error) {
        return { ok: false, error };
    }
}
//# sourceMappingURL=llmJson.js.map