"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodeLensProviderImpl = void 0;
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../lib/documents");
const utils_1 = require("../../../utils");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
const svelte2tsx_1 = require("svelte2tsx");
class CodeLensProviderImpl {
    constructor(lsAndTsDocResolver, referenceProvider, implementationProvider, configManager) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
        this.referenceProvider = referenceProvider;
        this.implementationProvider = implementationProvider;
        this.configManager = configManager;
    }
    async getCodeLens(document) {
        if (!this.anyCodeLensEnabled('typescript') && !this.anyCodeLensEnabled('javascript')) {
            return null;
        }
        const { lang, tsDoc } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document);
        const results = [];
        const collectors = [];
        const clientTsConfig = this.configManager.getClientTsUserConfig(tsDoc.scriptKind === typescript_1.default.ScriptKind.TS ? 'typescript' : 'javascript');
        if (clientTsConfig.referencesCodeLens?.enabled) {
            collectors.push({
                type: 'reference',
                collect: (tsDoc, item, parent) => this.extractReferenceLocation(tsDoc, item, parent, clientTsConfig)
            });
            if (!tsDoc.parserError) {
                // always add a reference code lens for the generated component
                results.push([
                    'reference',
                    {
                        start: { line: 0, character: 0 },
                        // some client refused to resolve the code lens if the start is the same as the end
                        end: { line: 0, character: 1 }
                    }
                ]);
            }
        }
        if (tsDoc.scriptKind === typescript_1.default.ScriptKind.TS &&
            clientTsConfig.implementationsCodeLens?.enabled) {
            collectors.push({
                type: 'implementation',
                collect: (tsDoc, item, parent) => this.extractImplementationLocation(tsDoc, item, clientTsConfig, parent)
            });
        }
        if (!collectors.length) {
            return null;
        }
        const navigationTree = lang.getNavigationTree(tsDoc.filePath);
        const renderFunction = navigationTree?.childItems?.find((item) => item.text === svelte2tsx_1.internalHelpers.renderName);
        if (renderFunction) {
            // pretty rare that there is anything to show in the template, so we skip it
            const notTemplate = renderFunction.childItems?.filter((item) => item.text !== '<function>');
            renderFunction.childItems = notTemplate;
        }
        this.walkTree(tsDoc, navigationTree, undefined, results, collectors);
        const uri = document.uri;
        return results.map(([type, range]) => vscode_languageserver_1.CodeLens.create(range, { type, uri }));
    }
    anyCodeLensEnabled(lang) {
        const vscodeTsConfig = this.configManager.getClientTsUserConfig(lang);
        return (vscodeTsConfig.referencesCodeLens?.enabled ||
            vscodeTsConfig.implementationsCodeLens?.enabled);
    }
    /**
     * https://github.com/microsoft/vscode/blob/062ba1ed6c2b9ff4819f4f7dad76de3fde0044ab/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts#L61
     */
    extractReferenceLocation(tsDoc, item, parent, config) {
        if (parent && parent.kind === typescript_1.default.ScriptElementKind.enumElement) {
            return this.getSymbolRange(tsDoc, item);
        }
        switch (item.kind) {
            case typescript_1.default.ScriptElementKind.functionElement: {
                const showOnAllFunctions = config.referencesCodeLens?.showOnAllFunctions;
                if (showOnAllFunctions) {
                    return this.getSymbolRange(tsDoc, item);
                }
                if (this.isExported(item, tsDoc)) {
                    return this.getSymbolRange(tsDoc, item);
                }
                break;
            }
            case typescript_1.default.ScriptElementKind.constElement:
            case typescript_1.default.ScriptElementKind.letElement:
            case typescript_1.default.ScriptElementKind.variableElement:
                // Only show references for exported variables
                if (this.isExported(item, tsDoc)) {
                    return this.getSymbolRange(tsDoc, item);
                }
                break;
            case typescript_1.default.ScriptElementKind.classElement:
                if (item.text === '<class>') {
                    break;
                }
                return this.getSymbolRange(tsDoc, item);
            case typescript_1.default.ScriptElementKind.interfaceElement:
            case typescript_1.default.ScriptElementKind.typeElement:
            case typescript_1.default.ScriptElementKind.enumElement:
                return this.getSymbolRange(tsDoc, item);
            case typescript_1.default.ScriptElementKind.memberFunctionElement:
            case typescript_1.default.ScriptElementKind.memberGetAccessorElement:
            case typescript_1.default.ScriptElementKind.memberSetAccessorElement:
            case typescript_1.default.ScriptElementKind.constructorImplementationElement:
            case typescript_1.default.ScriptElementKind.memberVariableElement:
                if (parent?.spans[0].start === item.spans[0].start) {
                    return undefined;
                }
                // Only show if parent is a class type object (not a literal)
                switch (parent?.kind) {
                    case typescript_1.default.ScriptElementKind.classElement:
                    case typescript_1.default.ScriptElementKind.interfaceElement:
                    case typescript_1.default.ScriptElementKind.typeElement:
                        return this.getSymbolRange(tsDoc, item);
                }
                break;
        }
        return undefined;
    }
    isExported(item, tsDoc) {
        return !tsDoc.parserError && item.kindModifiers.match(/\bexport\b/g) !== null;
    }
    /**
     * https://github.com/microsoft/vscode/blob/062ba1ed6c2b9ff4819f4f7dad76de3fde0044ab/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts#L66
     */
    extractImplementationLocation(tsDoc, item, config, parent) {
        if (item.kind === typescript_1.default.ScriptElementKind.memberFunctionElement &&
            parent &&
            parent.kind === typescript_1.default.ScriptElementKind.interfaceElement &&
            config.implementationsCodeLens?.showOnInterfaceMethods === true) {
            return this.getSymbolRange(tsDoc, item);
        }
        switch (item.kind) {
            case typescript_1.default.ScriptElementKind.interfaceElement:
                return this.getSymbolRange(tsDoc, item);
            case typescript_1.default.ScriptElementKind.classElement:
            case typescript_1.default.ScriptElementKind.memberFunctionElement:
            case typescript_1.default.ScriptElementKind.memberVariableElement:
            case typescript_1.default.ScriptElementKind.memberGetAccessorElement:
            case typescript_1.default.ScriptElementKind.memberSetAccessorElement:
                if (item.kindModifiers.match(/\babstract\b/g)) {
                    return this.getSymbolRange(tsDoc, item);
                }
                break;
        }
        return undefined;
    }
    getSymbolRange(tsDoc, item) {
        if (!item.nameSpan || (0, utils_3.isTextSpanInGeneratedCode)(tsDoc.getFullText(), item.nameSpan)) {
            return;
        }
        const range = (0, documents_1.mapRangeToOriginal)(tsDoc, (0, utils_2.convertRange)(tsDoc, item.nameSpan));
        if (range.start.line >= 0 && range.end.line >= 0) {
            return (0, utils_1.isZeroLengthRange)(range) ? undefined : range;
        }
    }
    walkTree(tsDoc, item, parent, results, collectors) {
        for (const collector of collectors) {
            const range = collector.collect(tsDoc, item, parent);
            if (range) {
                results.push([collector.type, range]);
            }
        }
        item.childItems?.forEach((child) => this.walkTree(tsDoc, child, item, results, collectors));
    }
    async resolveCodeLens(textDocument, codeLensToResolve, cancellationToken) {
        if (codeLensToResolve.data.type === 'reference') {
            return await this.resolveReferenceCodeLens(textDocument, codeLensToResolve, cancellationToken);
        }
        if (codeLensToResolve.data.type === 'implementation') {
            return await this.resolveImplementationCodeLens(textDocument, codeLensToResolve, cancellationToken);
        }
        return codeLensToResolve;
    }
    async resolveReferenceCodeLens(textDocument, codeLensToResolve, cancellationToken) {
        const references = (await this.referenceProvider.findReferences(textDocument, codeLensToResolve.range.start, { includeDeclaration: false }, cancellationToken)) ?? [];
        codeLensToResolve.command = {
            title: references.length === 1 ? `1 reference` : `${references.length} references`,
            // language clients need to map this to the corresponding command in each editor
            // see example in svelte-vscode/src/middlewares.ts
            command: '',
            arguments: [textDocument.uri, codeLensToResolve.range.start, references]
        };
        return codeLensToResolve;
    }
    async resolveImplementationCodeLens(textDocument, codeLensToResolve, cancellationToken) {
        const implementations = (await this.implementationProvider.getImplementation(textDocument, codeLensToResolve.range.start, cancellationToken)) ?? [];
        codeLensToResolve.command = {
            title: implementations.length === 1
                ? `1 implementation`
                : `${implementations.length} implementations`,
            command: '',
            arguments: [textDocument.uri, codeLensToResolve.range.start, implementations]
        };
        return codeLensToResolve;
    }
}
exports.CodeLensProviderImpl = CodeLensProviderImpl;
//# sourceMappingURL=CodeLensProvider.js.map