/**
 * JSON Parsing Utilities
 *
 * Safe JSON parsing with context for better error messages.
 *
 * @module util/json
 */
/**
 * Error thrown when JSON parsing fails
 */
export class JsonParseError extends Error {
    source;
    originalError;
    rawContent;
    constructor(message, source, originalError, rawContent) {
        super(message);
        this.source = source;
        this.originalError = originalError;
        this.rawContent = rawContent;
        this.name = "JsonParseError";
    }
}
/**
 * Parse JSON with context for better error messages
 *
 * @param raw - The raw JSON string to parse
 * @param source - A label describing where this JSON came from (e.g., "semgrep output", "npm audit")
 * @param options - Parsing options
 * @returns The parsed JSON value
 * @throws JsonParseError if parsing fails
 *
 * @example
 * ```typescript
 * const data = parseJson(stdout, "gosec output");
 * const config = parseJson(fileContent, `config file: ${filePath}`);
 * ```
 */
export function parseJson(raw, source, options = {}) {
    const { includeRawInError = false, maxRawLength = 500, reviver } = options;
    try {
        return JSON.parse(raw, reviver);
    }
    catch (error) {
        const originalError = error;
        // Build a helpful error message
        let message = `Failed to parse JSON from ${source}`;
        // Add position info if available
        const posMatch = originalError.message.match(/position (\d+)/);
        if (posMatch) {
            const position = parseInt(posMatch[1], 10);
            const context = getJsonErrorContext(raw, position);
            message += `: ${originalError.message}`;
            if (context) {
                message += `\n  Near: "${context}"`;
            }
        }
        else {
            message += `: ${originalError.message}`;
        }
        // Include raw content snippet if requested
        let rawSnippet;
        if (includeRawInError && raw) {
            rawSnippet = raw.length > maxRawLength ? raw.slice(0, maxRawLength) + "..." : raw;
        }
        throw new JsonParseError(message, source, originalError, rawSnippet);
    }
}
/**
 * Try to parse JSON, returning undefined on failure
 */
export function tryParseJson(raw, source, options = {}) {
    try {
        return parseJson(raw, source, options);
    }
    catch {
        return undefined;
    }
}
/**
 * Parse JSON with a default value on failure
 */
export function parseJsonOrDefault(raw, source, defaultValue, options = {}) {
    try {
        return parseJson(raw, source, options);
    }
    catch {
        return defaultValue;
    }
}
/**
 * Get context around a JSON parse error position
 */
function getJsonErrorContext(raw, position, contextSize = 30) {
    if (!raw || position < 0 || position >= raw.length) {
        return null;
    }
    const start = Math.max(0, position - contextSize);
    const end = Math.min(raw.length, position + contextSize);
    let context = raw.slice(start, end);
    // Clean up for display
    context = context.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
    // Add ellipsis
    if (start > 0)
        context = "..." + context;
    if (end < raw.length)
        context = context + "...";
    return context;
}
/**
 * Validate that a value is valid JSON (for pre-checks)
 */
export function isValidJson(raw) {
    try {
        JSON.parse(raw);
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Safely stringify JSON with error handling
 */
export function safeStringify(value, options = {}) {
    const { pretty = false, replacer } = options;
    try {
        return JSON.stringify(value, replacer, pretty ? 2 : undefined);
    }
    catch (error) {
        // Handle circular references and other stringify errors
        return `[Unserializable: ${error.message}]`;
    }
}
//# sourceMappingURL=json.js.map