"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InlayHintProviderImpl = void 0;
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const documents_1 = require("../../../lib/documents");
const parseHtml_1 = require("../../../lib/documents/parseHtml");
const utils_1 = require("./utils");
const utils_2 = require("../utils");
class InlayHintProviderImpl {
    constructor(lsAndTsDocResolver) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
    }
    async getInlayHints(document, range, cancellationToken) {
        // Don't sync yet so we can skip TypeScript's synchronizeHostData if inlay hints are disabled
        const { userPreferences } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document);
        if (cancellationToken?.isCancellationRequested ||
            !this.areInlayHintsEnabled(userPreferences)) {
            return null;
        }
        const { tsDoc, lang, lsContainer } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
        const inlayHints = lang.provideInlayHints(tsDoc.filePath, this.convertToTargetTextSpan(range, tsDoc), userPreferences);
        const sourceFile = lang.getProgram()?.getSourceFile(tsDoc.filePath);
        if (!sourceFile) {
            return [];
        }
        const renderFunction = (0, utils_1.findRenderFunction)(sourceFile);
        const renderFunctionReturnTypeLocation = renderFunction && this.getTypeAnnotationPosition(renderFunction);
        const snapshotMap = new utils_1.SnapshotMap(this.lsAndTsDocResolver, lsContainer);
        snapshotMap.set(tsDoc.filePath, tsDoc);
        const convertPromises = inlayHints
            .filter((inlayHint) => !(0, utils_1.isInGeneratedCode)(tsDoc.getFullText(), inlayHint.position) &&
            inlayHint.position !== renderFunctionReturnTypeLocation &&
            !this.isSvelte2tsxFunctionHints(sourceFile, inlayHint) &&
            !this.isGeneratedVariableTypeHint(sourceFile, inlayHint) &&
            !this.isGeneratedAsyncFunctionReturnType(sourceFile, inlayHint) &&
            !this.isGeneratedFunctionReturnType(sourceFile, inlayHint))
            .map(async (inlayHint) => ({
            label: await this.convertInlayHintLabelParts(inlayHint, snapshotMap),
            position: this.getOriginalPosition(document, tsDoc, inlayHint),
            kind: this.convertInlayHintKind(inlayHint.kind),
            paddingLeft: inlayHint.whitespaceBefore,
            paddingRight: inlayHint.whitespaceAfter
        }));
        return (await Promise.all(convertPromises)).filter((inlayHint) => inlayHint.position.line >= 0 &&
            inlayHint.position.character >= 0 &&
            !this.checkGeneratedFunctionHintWithSource(inlayHint, document));
    }
    areInlayHintsEnabled(preferences) {
        return (preferences.includeInlayParameterNameHints === 'literals' ||
            preferences.includeInlayParameterNameHints === 'all' ||
            preferences.includeInlayEnumMemberValueHints ||
            preferences.includeInlayFunctionLikeReturnTypeHints ||
            preferences.includeInlayFunctionParameterTypeHints ||
            preferences.includeInlayPropertyDeclarationTypeHints ||
            preferences.includeInlayVariableTypeHints);
    }
    convertToTargetTextSpan(range, snapshot) {
        const generatedStartOffset = snapshot.getGeneratedPosition(range.start);
        const generatedEndOffset = snapshot.getGeneratedPosition(range.end);
        const start = generatedStartOffset.line < 0 ? 0 : snapshot.offsetAt(generatedStartOffset);
        const end = generatedEndOffset.line < 0
            ? snapshot.getLength()
            : snapshot.offsetAt(generatedEndOffset);
        return {
            start,
            length: end - start
        };
    }
    async convertInlayHintLabelParts(inlayHint, snapshotMap) {
        if (!inlayHint.displayParts) {
            return inlayHint.text;
        }
        const convertPromises = inlayHint.displayParts.map(async (part) => {
            if (!part.file || !part.span) {
                return {
                    value: part.text
                };
            }
            const snapshot = await snapshotMap.retrieve(part.file);
            if (!snapshot) {
                return {
                    value: part.text
                };
            }
            const originalLocation = (0, documents_1.mapLocationToOriginal)(snapshot, (0, utils_2.convertRange)(snapshot, part.span));
            return {
                value: part.text,
                location: originalLocation.range.start.line < 0 ? undefined : originalLocation
            };
        });
        const parts = await Promise.all(convertPromises);
        return parts;
    }
    getOriginalPosition(document, tsDoc, inlayHint) {
        let originalPosition = tsDoc.getOriginalPosition(tsDoc.positionAt(inlayHint.position));
        if (inlayHint.kind === typescript_1.default.InlayHintKind.Type) {
            const originalOffset = document.offsetAt(originalPosition);
            const source = document.getText();
            // detect if inlay hint position is off by one
            // by checking if source[offset] is part of an identifier
            // https://github.com/sveltejs/language-tools/pull/2070
            if (originalOffset < source.length &&
                !/[\x00-\x23\x25-\x2F\x3A-\x40\x5B\x5D-\x5E\x60\x7B-\x7F]/.test(source[originalOffset])) {
                originalPosition.character += 1;
            }
        }
        return originalPosition;
    }
    convertInlayHintKind(kind) {
        switch (kind) {
            case 'Parameter':
                return vscode_languageserver_types_1.InlayHintKind.Parameter;
            case 'Type':
                return vscode_languageserver_types_1.InlayHintKind.Type;
            case 'Enum':
                return undefined;
            default:
                return undefined;
        }
    }
    isSvelte2tsxFunctionHints(sourceFile, inlayHint) {
        if (inlayHint.kind !== typescript_1.default.InlayHintKind.Parameter) {
            return false;
        }
        if (inlayHint.displayParts?.some((v) => (0, utils_2.isSvelte2tsxShimFile)(v.file))) {
            return true;
        }
        const hasParameterWithSamePosition = (node) => node.arguments !== undefined &&
            node.arguments.some((arg) => arg.getStart() === inlayHint.position);
        const node = (0, utils_1.findContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, (node) => typescript_1.default.isCallOrNewExpression(node) && hasParameterWithSamePosition(node));
        if (!node) {
            return false;
        }
        const expressionText = node.expression.getText();
        const isComponentEventHandler = expressionText.includes('.$on');
        return (isComponentEventHandler ||
            expressionText.includes('.createElement') ||
            expressionText.includes('__sveltets_') ||
            expressionText.startsWith('$$_'));
    }
    isGeneratedVariableTypeHint(sourceFile, inlayHint) {
        if (inlayHint.kind !== typescript_1.default.InlayHintKind.Type) {
            return false;
        }
        if ((0, utils_1.startsWithIgnoredPosition)(sourceFile.text, inlayHint.position)) {
            return true;
        }
        const declaration = (0, utils_1.findClosestContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, typescript_1.default.isVariableDeclaration);
        if (!declaration) {
            return false;
        }
        // $$_tnenopmoC, $$_value, $$props, $$slots, $$restProps...
        return ((0, utils_1.isInGeneratedCode)(sourceFile.text, declaration.pos) ||
            declaration.name.getText().startsWith('$$'));
    }
    /** `true` if is one of the `async () => {...}` functions svelte2tsx generates */
    isGeneratedAsyncFunctionReturnType(sourceFile, inlayHint) {
        if (inlayHint.kind !== typescript_1.default.InlayHintKind.Type) {
            return false;
        }
        const expression = (0, utils_1.findContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, (node) => typescript_1.default.isArrowFunction(node));
        if (!expression?.modifiers?.some((m) => m.kind === typescript_1.default.SyntaxKind.AsyncKeyword) ||
            !expression.parent?.parent ||
            !typescript_1.default.isBlock(expression.parent.parent)) {
            return false;
        }
        return this.getTypeAnnotationPosition(expression) === inlayHint.position;
    }
    isGeneratedFunctionReturnType(sourceFile, inlayHint) {
        if (inlayHint.kind !== typescript_1.default.InlayHintKind.Type) {
            return false;
        }
        // $: a = something
        // it's always top level and shouldn't be under other function call
        // so we don't need to use findClosestContainingNode
        const expression = (0, utils_1.findContainingNode)(sourceFile, { start: inlayHint.position, length: 0 }, (node) => typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression));
        if (!expression) {
            return false;
        }
        return (expression.expression.text === '__sveltets_2_invalidate' &&
            typescript_1.default.isArrowFunction(expression.arguments[0]) &&
            this.getTypeAnnotationPosition(expression.arguments[0]) === inlayHint.position);
    }
    getTypeAnnotationPosition(decl) {
        const closeParenToken = (0, utils_1.findChildOfKind)(decl, typescript_1.default.SyntaxKind.CloseParenToken);
        if (closeParenToken) {
            return closeParenToken.end;
        }
        return decl.parameters.end;
    }
    checkGeneratedFunctionHintWithSource(inlayHint, document) {
        if ((0, documents_1.isInTag)(inlayHint.position, document.moduleScriptInfo)) {
            return false;
        }
        if ((0, documents_1.isInTag)(inlayHint.position, document.scriptInfo)) {
            return document
                .getText()
                .slice(document.offsetAt(inlayHint.position))
                .trimStart()
                .startsWith('$:');
        }
        const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, inlayHint.position);
        if (!attributeContext || attributeContext.inValue || !attributeContext.name.includes(':')) {
            return false;
        }
        const { name, elementTag } = attributeContext;
        // <div on:click>
        if (name.startsWith('on:') && !elementTag.attributes?.[attributeContext.name]) {
            return true;
        }
        const directives = ['in', 'out', 'animate', 'transition', 'use'];
        // hide
        // - transitionCall: for __sveltets_2_ensureTransition
        // - tag: for svelteHTML.mapElementTag inside transition call and action call
        // - animationCall: for __sveltets_2_ensureAnimation
        // - actionCall for __sveltets_2_ensureAction
        return directives.some((directive) => name.startsWith(directive + ':'));
    }
}
exports.InlayHintProviderImpl = InlayHintProviderImpl;
//# sourceMappingURL=InlayHintProvider.js.map