"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LintErrors = void 0;
const diagnosticData_1 = require("../../../platform/inlineEdits/common/dataTypes/diagnosticData");
const xtabPromptOptions_1 = require("../../../platform/inlineEdits/common/dataTypes/xtabPromptOptions");
const languageDiagnosticsService_1 = require("../../../platform/languages/common/languageDiagnosticsService");
const errors_1 = require("../../../util/vs/base/common/errors");
const resources_1 = require("../../../util/vs/base/common/resources");
const range_1 = require("../../../util/vs/editor/common/core/range");
const offsetRange_1 = require("../../../util/vs/editor/common/core/ranges/offsetRange");
const vscodeTypesShim_1 = require("../../../util/common/test/shims/vscodeTypesShim");
const tags_1 = require("./tags");
let LintErrors = class LintErrors {
    constructor(_documentId, _document, _langDiagService, _xtabHistory) {
        this._documentId = _documentId;
        this._document = _document;
        this._langDiagService = _langDiagService;
        this._xtabHistory = _xtabHistory;
    }
    _diagnostics(resource) {
        const allDiagnostics = resource ? [[resource, this._langDiagService.getDiagnostics(resource)]] : this._langDiagService.getAllDiagnostics();
        const activeDocumentUri = this._documentId.toUri();
        return allDiagnostics.map(fileDiagnostics => {
            const [uri, diagnostics] = fileDiagnostics;
            return diagnostics.map(diagnostic => {
                const range = new range_1.Range(diagnostic.range.start.line + 1, diagnostic.range.start.character + 1, diagnostic.range.end.line + 1, diagnostic.range.end.character + 1);
                const distance = (0, resources_1.isEqual)(activeDocumentUri, uri) ? CursorDistance.fromPositions(range.getStartPosition(), this._document.cursorPosition) : undefined;
                return new DiagnosticDataWithDistance(uri, diagnostic.message, diagnostic.severity === vscodeTypesShim_1.DiagnosticSeverity.Error ? 'error' : 'warning', distance, range, this._document.transformer.getOffsetRange(range), diagnostic.code && !(typeof diagnostic.code === 'number') && !(typeof diagnostic.code === 'string') ? diagnostic.code.value : diagnostic.code, diagnostic.source);
            });
        }).flat();
    }
    _getRelevantDiagnostics(options, resource) {
        let diagnostics = this._diagnostics(resource);
        diagnostics = filterDiagnosticsByDistance(diagnostics, options.maxLineDistance);
        diagnostics = sortDiagnosticsByDistance(diagnostics);
        diagnostics = filterDiagnosticsBySeverity(diagnostics, options.warnings);
        return diagnostics.slice(0, options.maxLints);
    }
    getFormattedLintErrors(options) {
        const currentFileDiagnostics = this._getRelevantDiagnostics(options, this._documentId.toUri());
        let allDiagnostics;
        if (options.nRecentFiles > 0 && this._xtabHistory) {
            const recentFileUris = this._collectRecentFileUris(options.nRecentFiles);
            const recentDiagnostics = this._getRecentFileDiagnostics(recentFileUris, options);
            allDiagnostics = [...currentFileDiagnostics, ...recentDiagnostics].slice(0, options.maxLints);
        }
        else {
            allDiagnostics = currentFileDiagnostics;
        }
        this._previousFormttedDiagnostics = allDiagnostics;
        const activeDocUri = this._documentId.toUri();
        const formattedDiagnostics = allDiagnostics.map(d => {
            // Only show code context for diagnostics from the current file,
            // since we don't have the document lines for recent files.
            const isCurrentFile = (0, resources_1.isEqual)(d.documentUri, activeDocUri);
            const effectiveOptions = isCurrentFile ? options : { ...options, showCode: xtabPromptOptions_1.LintOptionShowCode.NO };
            return formatSingleDiagnostic(d, this._document.lines, effectiveOptions);
        }).join('\n');
        const lintTag = tags_1.PromptTags.createLintTag(options.tagName);
        return `${lintTag.start}\n${formattedDiagnostics}\n${lintTag.end}`;
    }
    /**
     * Collects URIs of the N most recently edited/viewed files from xtab history,
     * excluding the current document. Files are selected in recency order.
     */
    _collectRecentFileUris(nRecentFiles) {
        if (!this._xtabHistory) {
            return [];
        }
        const result = [];
        const seenDocuments = new Set();
        const activeDocId = this._documentId;
        // Iterate from most recent (end) to least recent (start)
        for (let i = this._xtabHistory.length - 1; i >= 0; --i) {
            const entry = this._xtabHistory[i];
            if (entry.docId === activeDocId || seenDocuments.has(entry.docId)) {
                continue;
            }
            result.push(entry.docId.toUri());
            seenDocuments.add(entry.docId);
            if (result.length >= nRecentFiles) {
                break;
            }
        }
        return result;
    }
    /**
     * Collects diagnostics from recent files, maintaining the file recency order.
     * Within each file, diagnostics are sorted by line number.
     */
    _getRecentFileDiagnostics(recentFileUris, options) {
        const result = [];
        for (const uri of recentFileUris) {
            let fileDiags = this._diagnostics(uri);
            fileDiags = filterDiagnosticsBySeverity(fileDiags, options.warnings);
            // Sort by line number within each file
            fileDiags = fileDiags.slice().sort((a, b) => a.documentRange.startLineNumber - b.documentRange.startLineNumber);
            result.push(...fileDiags);
        }
        return result;
    }
    lineNumberInPreviousFormattedPrompt(options, lineNumber) {
        if (!this._previousFormttedDiagnostics) {
            throw new errors_1.BugIndicatingError('No previous formatted diagnostics available to check line number against.');
        }
        const activeDocUri = this._documentId.toUri();
        for (const diagnostic of this._previousFormttedDiagnostics) {
            // Only consider diagnostics from the current file
            if (!(0, resources_1.isEqual)(diagnostic.documentUri, activeDocUri)) {
                continue;
            }
            // Convert diagnostic position (1-based) to 0-based for comparison with formatted output
            if (diagnostic.documentRange.getStartPosition().lineNumber - 1 === lineNumber) {
                return true;
            }
            if (options.showCode === xtabPromptOptions_1.LintOptionShowCode.NO) {
                continue;
            }
            const lineRange = diagnosticsToCodeLineRange(diagnostic.documentRange, options);
            if (lineRange.contains(lineNumber)) {
                return true;
            }
        }
        return false;
    }
    getData() {
        // Create options with everything enabled for comprehensive telemetry
        const telemetryOptions = {
            tagName: 'telemetry',
            warnings: xtabPromptOptions_1.LintOptionWarning.YES,
            showCode: xtabPromptOptions_1.LintOptionShowCode.NO,
            maxLints: Number.MAX_SAFE_INTEGER,
            maxLineDistance: Number.MAX_SAFE_INTEGER, // Include all diagnostics regardless of distance
            nRecentFiles: 0,
        };
        let diagnostics = this._diagnostics(undefined);
        diagnostics = filterDiagnosticsBySeverity(diagnostics, xtabPromptOptions_1.LintOptionWarning.YES);
        diagnostics = sortDiagnosticsByDistance(diagnostics);
        diagnostics = diagnostics.slice(0, 20);
        const telemetryDiagnostics = diagnostics.map(diagnostic => ({
            uri: diagnostic.documentUri.toString(),
            line: diagnostic.documentRange.startLineNumber,
            column: diagnostic.documentRange.startColumn,
            endLine: diagnostic.documentRange.endLineNumber,
            endColumn: diagnostic.documentRange.endColumn,
            severity: diagnostic.severity,
            message: diagnostic.message,
            code: diagnostic.code,
            source: diagnostic.source,
            lineDistance: diagnostic.distance?.lineDistance,
            formatted: formatSingleDiagnostic(diagnostic, this._document.lines, telemetryOptions),
            formattedCode: formatSingleDiagnostic(diagnostic, this._document.lines, { ...telemetryOptions, showCode: xtabPromptOptions_1.LintOptionShowCode.YES }),
            formattedCodeWithSurrounding: formatSingleDiagnostic(diagnostic, this._document.lines, { ...telemetryOptions, showCode: xtabPromptOptions_1.LintOptionShowCode.YES_WITH_SURROUNDING }),
        }));
        return JSON.stringify(telemetryDiagnostics);
    }
};
exports.LintErrors = LintErrors;
exports.LintErrors = LintErrors = __decorate([
    __param(2, languageDiagnosticsService_1.ILanguageDiagnosticsService)
], LintErrors);
/**
 * Formats a single diagnostic with optional code context.
 */
function formatSingleDiagnostic(diagnostic, documentLines, lintOptions) {
    const headerLine = formatDiagnosticMessage(diagnostic, diagnostic.documentRange);
    if (lintOptions.showCode === xtabPromptOptions_1.LintOptionShowCode.NO) {
        return headerLine;
    }
    const codeLines = formatCodeLines(diagnostic.documentRange, lintOptions, documentLines);
    return headerLine + '\n' + codeLines.join('\n');
}
function formatDiagnosticMessage(diagnostic, diagnosticRange) {
    // Format: "line:column - severity CODE: message"
    let codeStr = '';
    if (diagnostic.code) {
        const source = diagnostic.source ? diagnostic.source.toUpperCase() : '';
        codeStr = ` ${source}${diagnostic.code}`;
    }
    const diagnosticStartPosition = diagnosticRange.getStartPosition();
    const headerLine = `${diagnosticStartPosition.lineNumber - 1}:${diagnosticStartPosition.column - 1} - ${diagnostic.severity}${codeStr}: ${diagnostic.message}`;
    return headerLine;
}
function formatCodeLines(diagnosticRange, lintOptions, documentLines) {
    const lineRangeToInclude = diagnosticsToCodeLineRange(diagnosticRange, lintOptions);
    const lineRange = lineRangeToInclude.intersect(new offsetRange_1.OffsetRange(0, documentLines.length));
    if (!lineRange) {
        // Diagnostic refers to lines that no longer exist (stale diagnostic after document edit)
        return [];
    }
    const codeLines = [];
    for (let i = lineRange.start; i < lineRange.endExclusive; i++) {
        codeLines.push(formatCodeLine(i, documentLines[i] ?? ''));
    }
    return codeLines;
}
function diagnosticsToCodeLineRange(diagnosticRange, lintOptions) {
    const diagnosticStartLine = diagnosticRange.getStartPosition().lineNumber - 1; // 0-based for rendering and array access
    const diagnosticEndLine = diagnosticRange.getEndPosition().lineNumber - 1; // 0-based for rendering and array access
    let lineRangeToInclude = new offsetRange_1.OffsetRange(diagnosticStartLine, diagnosticEndLine + 1);
    if (lintOptions.showCode === xtabPromptOptions_1.LintOptionShowCode.YES_WITH_SURROUNDING) {
        lineRangeToInclude = lineRangeToInclude.deltaStart(-1).deltaEnd(1);
    }
    return lineRangeToInclude;
}
function formatCodeLine(lineNumber, lineContent) {
    return `${lineNumber}|${lineContent}`;
}
function filterDiagnosticsByDistance(diagnostics, distance) {
    return diagnostics.filter(d => d.distance?.lineDistance !== undefined && d.distance.lineDistance <= distance);
}
function sortDiagnosticsByDistance(diagnostics) {
    return diagnostics.slice().sort((a, b) => {
        if (a.distance === undefined && b.distance === undefined) {
            return 0;
        }
        if (a.distance === undefined) {
            return 1;
        }
        if (b.distance === undefined) {
            return -1;
        }
        return CursorDistance.compareFn(a.distance, b.distance);
    });
}
function filterDiagnosticsBySeverity(diagnostics, warnings) {
    switch (warnings) {
        case xtabPromptOptions_1.LintOptionWarning.NO:
            return diagnostics.filter(d => d.severity === 'error');
        case xtabPromptOptions_1.LintOptionWarning.YES: {
            return diagnostics.filter(d => d.severity === 'error' || d.severity === 'warning');
        }
        case xtabPromptOptions_1.LintOptionWarning.YES_IF_NO_ERRORS: {
            const errorDiagnostics = diagnostics.filter(d => d.severity === 'error');
            return errorDiagnostics.length > 0
                ? errorDiagnostics
                : diagnostics.filter(d => d.severity === 'error' || d.severity === 'warning');
        }
    }
}
class CursorDistance {
    static compareFn(a, b) {
        if (a.lineDistance !== b.lineDistance) {
            return a.lineDistance - b.lineDistance;
        }
        return a.columnDistance - b.columnDistance;
    }
    static fromPositions(pos1, pos2) {
        return new CursorDistance(Math.abs(pos1.lineNumber - pos2.lineNumber), Math.abs(pos1.column - pos2.column));
    }
    constructor(lineDistance, columnDistance) {
        this.lineDistance = lineDistance;
        this.columnDistance = columnDistance;
    }
}
class DiagnosticDataWithDistance extends diagnosticData_1.DiagnosticData {
    constructor(documentUri, message, severity, distance, documentRange, range, code, source) {
        super(documentUri, message, severity, range, code, source);
        this.distance = distance;
        this.documentRange = documentRange;
    }
}
//# sourceMappingURL=lintErrors.js.map