"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.preprocessLanguageService = preprocessLanguageService;
exports.postprocessLanguageService = postprocessLanguageService;
exports.resolveCompletionResult = resolveCompletionResult;
exports.resolveCompletionEntryDetails = resolveCompletionEntryDetails;
const transform_1 = require("@volar/typescript/lib/node/transform");
const utils_1 = require("@volar/typescript/lib/node/utils");
const language_core_1 = require("@vue/language-core");
const shared_1 = require("@vue/shared");
const utils_2 = require("./requests/utils");
const windowsPathRE = /\\/g;
function preprocessLanguageService(languageService, getLanguage) {
    const proxyCache = new Map();
    const getProxyMethod = (target, p) => {
        switch (p) {
            case 'getQuickInfoAtPosition':
                return getQuickInfoAtPosition(target[p]);
            case 'getCompletionsAtPosition':
                return getCompletionsAtPosition(target[p]);
            case 'getSuggestionDiagnostics':
                return getSuggestionDiagnostics(target[p]);
            case 'getCodeFixesAtPosition':
                return getCodeFixesAtPosition(target[p]);
            case 'findRenameLocations':
                return findRenameLocations(target[p]);
        }
    };
    return new Proxy(languageService, {
        get(target, p, receiver) {
            if (!proxyCache.has(p)) {
                proxyCache.set(p, getProxyMethod(target, p));
            }
            const proxyMethod = proxyCache.get(p);
            if (proxyMethod) {
                return proxyMethod;
            }
            return Reflect.get(target, p, receiver);
        },
    });
    function getQuickInfoAtPosition(getQuickInfoAtPosition) {
        return (fileName, position, ...rests) => {
            const result = getQuickInfoAtPosition(fileName, position, ...rests);
            if (!result || result.tags?.length) {
                return result;
            }
            const language = getLanguage();
            if (!language) {
                return result;
            }
            const [serviceScript, , sourceScript] = (0, utils_1.getServiceScript)(language, fileName);
            if (!serviceScript || !(sourceScript?.generated?.root instanceof language_core_1.VueVirtualCode)) {
                return result;
            }
            const codegen = language_core_1.tsCodegen.get(sourceScript.generated.root.ir);
            const leadingOffset = sourceScript.snapshot.getLength();
            for (const sourceRange of (0, transform_1.toSourceRanges)(sourceScript, language, serviceScript, result.textSpan.start, result.textSpan.start + result.textSpan.length, true, () => true)) {
                const generateRange2 = (0, transform_1.toGeneratedRange)(language, serviceScript, sourceScript, sourceRange[1], sourceRange[2], (data) => !!data.__importCompletion);
                if (generateRange2 !== undefined) {
                    const variableName = serviceScript.code.snapshot.getText(generateRange2[0] - leadingOffset, generateRange2[1] - leadingOffset);
                    if (codegen?.getSetupExposed().has(variableName)) {
                        const extraInfo = getQuickInfoAtPosition(fileName, generateRange2[0], ...rests);
                        result.tags = extraInfo?.tags;
                    }
                }
            }
            return result;
        };
    }
    function getCompletionsAtPosition(getCompletionsAtPosition) {
        return (fileName, position, ...rests) => {
            const result = getCompletionsAtPosition(fileName, position, ...rests);
            if (!result) {
                return result;
            }
            const language = getLanguage();
            if (!language) {
                return result;
            }
            const [serviceScript, , sourceScript] = (0, utils_1.getServiceScript)(language, fileName);
            if (!serviceScript || !(sourceScript?.generated?.root instanceof language_core_1.VueVirtualCode)) {
                return result;
            }
            for (const sourceOffset of (0, transform_1.toSourceOffsets)(sourceScript, language, serviceScript, position, () => true)) {
                const generatedOffset2 = (0, transform_1.toGeneratedOffset)(language, serviceScript, sourceScript, sourceOffset[1], (data) => !!data.__importCompletion);
                if (generatedOffset2 !== undefined) {
                    const completion2 = getCompletionsAtPosition(fileName, generatedOffset2, ...rests);
                    if (completion2) {
                        const nameToIndex = new Map(result.entries.map((entry, index) => [entry.name, index]));
                        for (const entry of completion2.entries) {
                            if (entry.kind === 'warning') {
                                continue;
                            }
                            if (nameToIndex.has(entry.name)) {
                                const index = nameToIndex.get(entry.name);
                                const existingEntry = result.entries[index];
                                if (existingEntry.kind === 'warning') {
                                    result.entries[index] = entry;
                                }
                            }
                            else {
                                result.entries.push(entry);
                            }
                        }
                    }
                }
            }
            return result;
        };
    }
    function getSuggestionDiagnostics(getSuggestionDiagnostics) {
        return (fileName, ...rests) => {
            const result = getSuggestionDiagnostics(fileName, ...rests);
            const language = getLanguage();
            if (!language) {
                return result;
            }
            const [serviceScript, , sourceScript] = (0, utils_1.getServiceScript)(language, fileName);
            if (!serviceScript || !(sourceScript?.generated?.root instanceof language_core_1.VueVirtualCode)) {
                return result;
            }
            const codegen = language_core_1.tsCodegen.get(sourceScript.generated.root.ir);
            const leadingOffset = sourceScript.snapshot.getLength();
            for (const diagnostic of result) {
                for (const sourceRange of (0, transform_1.toSourceRanges)(sourceScript, language, serviceScript, diagnostic.start, diagnostic.start + diagnostic.length, true, (data) => !!data.__importCompletion)) {
                    const generateRange2 = (0, transform_1.toGeneratedRange)(language, serviceScript, sourceScript, sourceRange[1], sourceRange[2], (data) => !data.__importCompletion);
                    if (generateRange2 !== undefined) {
                        const variableName = serviceScript.code.snapshot.getText(generateRange2[0] - leadingOffset, generateRange2[1] - leadingOffset);
                        if (codegen?.getSetupExposed().has(variableName)) {
                            diagnostic.start = generateRange2[0];
                            diagnostic.length = generateRange2[1] - generateRange2[0];
                            break;
                        }
                    }
                }
            }
            return result;
        };
    }
    function getCodeFixesAtPosition(getCodeFixesAtPosition) {
        return (fileName, start, end, errorCodes, ...rests) => {
            let result = getCodeFixesAtPosition(fileName, start, end, errorCodes, ...rests);
            // Property 'xxx' does not exist on type 'yyy'.ts(2339)
            if (!errorCodes.includes(2339)) {
                return result;
            }
            const language = getLanguage();
            if (!language) {
                return result;
            }
            const [serviceScript, , sourceScript] = (0, utils_1.getServiceScript)(language, fileName);
            if (!serviceScript || !(sourceScript?.generated?.root instanceof language_core_1.VueVirtualCode)) {
                return result;
            }
            for (const sourceRange of (0, transform_1.toSourceRanges)(sourceScript, language, serviceScript, start, end, true, () => true)) {
                const generateRange2 = (0, transform_1.toGeneratedRange)(language, serviceScript, sourceScript, sourceRange[1], sourceRange[2], (data) => !!data.__importCompletion);
                if (generateRange2 !== undefined) {
                    let importFixes = getCodeFixesAtPosition(fileName, generateRange2[0], generateRange2[1], [2304], // Cannot find name 'xxx'.ts(2304)
                    ...rests);
                    importFixes = importFixes.filter(fix => fix.fixName === 'import');
                    result = result.concat(importFixes);
                }
            }
            return result;
        };
    }
    function findRenameLocations(findRenameLocations) {
        return (fileName, position, ...rests) => {
            // @ts-expect-error
            const result = findRenameLocations(fileName, position, ...rests);
            if (!result?.length) {
                return result;
            }
            const language = getLanguage();
            if (!language) {
                return result;
            }
            const [serviceScript, , sourceScript] = (0, utils_1.getServiceScript)(language, fileName);
            if (!serviceScript || !(sourceScript?.generated?.root instanceof language_core_1.VueVirtualCode)) {
                return result;
            }
            const map = language.maps.get(serviceScript.code, sourceScript);
            const leadingOffset = sourceScript.snapshot.getLength();
            const isShorthand = (data) => !!data.__shorthandExpression;
            // { foo: __VLS_ctx.foo }
            //   ^^^            ^^^
            // if the rename is triggered directly on the shorthand,
            // skip the entire request on the generated property name
            if ([...map.toSourceLocation(position - leadingOffset, isShorthand)].length === 0) {
                for (const [offset] of map.toSourceLocation(position - leadingOffset, () => true)) {
                    for (const _ of map.toGeneratedLocation(offset, isShorthand)) {
                        return;
                    }
                }
            }
            const preferAlias = typeof rests[2] === 'boolean'
                ? rests[2]
                : rests[2]?.providePrefixAndSuffixTextForRename ?? true;
            if (!preferAlias) {
                return result;
            }
            const locations = [...result];
            outer: for (let i = 0; i < locations.length; i++) {
                const { textSpan } = locations[i];
                const generatedLeft = textSpan.start - leadingOffset;
                const generatedRight = textSpan.start + textSpan.length - leadingOffset;
                // { foo: __VLS_ctx.foo }
                //                  ^^^
                for (const [start, end, { data }] of map.toSourceRange(generatedLeft, generatedRight, true, isShorthand)) {
                    locations.splice(i, 1, {
                        ...locations[i],
                        ...getPrefixAndSuffixForShorthandRename(data.__shorthandExpression, 'right', sourceScript.snapshot.getText(start, end)),
                    });
                    continue outer;
                }
                // { foo: __VLS_ctx.foo }
                //   ^^^
                for (const [start, end] of map.toSourceRange(generatedLeft, generatedRight, true, () => true)) {
                    for (const [, , { data }] of map.toGeneratedRange(start, end, true, isShorthand)) {
                        locations.splice(i, 1, {
                            ...locations[i],
                            ...getPrefixAndSuffixForShorthandRename(data.__shorthandExpression, 'left', sourceScript.snapshot.getText(start, end)),
                        });
                        continue outer;
                    }
                }
            }
            return locations;
        };
    }
}
function getPrefixAndSuffixForShorthandRename(type, target, originalText) {
    if (type === 'html') {
        if (target === 'left') {
            return {
                suffixText: `="${(0, shared_1.camelize)(originalText)}"`,
            };
        }
        else {
            return {
                prefixText: `${originalText}="`,
                suffixText: `"`,
            };
        }
    }
    else {
        if (target === 'left') {
            return {
                suffixText: `: ${originalText}`,
            };
        }
        else {
            return {
                prefixText: `${originalText}: `,
            };
        }
    }
}
function postprocessLanguageService(ts, language, languageService, vueOptions, asScriptId) {
    const proxyCache = new Map();
    const getProxyMethod = (target, p) => {
        switch (p) {
            case 'findReferences':
                return findReferences(target[p]);
            case 'getCompletionsAtPosition':
                return getCompletionsAtPosition(target[p]);
            case 'getCompletionEntryDetails':
                return getCompletionEntryDetails(target[p]);
            case 'getCodeFixesAtPosition':
                return getCodeFixesAtPosition(target[p]);
            case 'getDefinitionAndBoundSpan':
                return getDefinitionAndBoundSpan(target[p]);
        }
    };
    return new Proxy(languageService, {
        get(target, p, receiver) {
            if (!proxyCache.has(p)) {
                proxyCache.set(p, getProxyMethod(target, p));
            }
            const proxyMethod = proxyCache.get(p);
            if (proxyMethod) {
                return proxyMethod;
            }
            return Reflect.get(target, p, receiver);
        },
    });
    function findReferences(findReferences) {
        return (fileName, ...rest) => {
            const result = findReferences(fileName, ...rest);
            if (!result) {
                return result;
            }
            // #5719
            for (const { references } of result) {
                for (const reference of references) {
                    const sourceScript = language.scripts.get(asScriptId(reference.fileName));
                    const root = sourceScript?.generated?.root;
                    if (!sourceScript || !(root instanceof language_core_1.VueVirtualCode)) {
                        continue;
                    }
                    const styles = root.ir.styles;
                    if (!styles.length) {
                        return result;
                    }
                    const isInStyle = styles.some(style => reference.textSpan.start >= style.startTagEnd
                        && reference.textSpan.start + reference.textSpan.length <= style.endTagStart);
                    if (!isInStyle) {
                        continue;
                    }
                    const leadingChar = sourceScript.snapshot.getText(reference.textSpan.start - 1, reference.textSpan.start);
                    if (leadingChar === '.') {
                        reference.textSpan.start--;
                        reference.textSpan.length++;
                    }
                }
            }
            return result;
        };
    }
    function getCompletionsAtPosition(getCompletionsAtPosition) {
        return (filePath, position, ...rests) => {
            const fileName = filePath.replace(windowsPathRE, '/');
            const result = getCompletionsAtPosition(fileName, position, ...rests);
            if (result) {
                resolveCompletionResult(ts, language, asScriptId, vueOptions, fileName, position, result);
            }
            return result;
        };
    }
    function getCompletionEntryDetails(getCompletionEntryDetails) {
        return (...args) => {
            const details = getCompletionEntryDetails(...args);
            if (details) {
                resolveCompletionEntryDetails(language, details, args[6]);
            }
            return details;
        };
    }
    function getCodeFixesAtPosition(getCodeFixesAtPosition) {
        return (...args) => {
            let result = getCodeFixesAtPosition(...args);
            // filter __VLS_
            result = result.filter(entry => !entry.description.includes('__VLS_'));
            return result;
        };
    }
    function getDefinitionAndBoundSpan(getDefinitionAndBoundSpan) {
        return (fileName, position, ...rests) => {
            const result = getDefinitionAndBoundSpan(fileName, position, ...rests);
            if (!result?.definitions?.length) {
                return result;
            }
            const program = languageService.getProgram();
            const sourceScript = language.scripts.get(asScriptId(fileName));
            const root = sourceScript?.generated?.root;
            if (!(root instanceof language_core_1.VueVirtualCode)) {
                return result;
            }
            if (!root.ir.template
                || position < root.ir.template.startTagEnd
                || position > root.ir.template.endTagStart) {
                return result;
            }
            const definitions = new Set(result.definitions);
            // #5275
            if (result.definitions.length >= 2) {
                for (const definition of result.definitions) {
                    if (root.ir.content[definition.textSpan.start - 1] === '@'
                        || root.ir.content.slice(definition.textSpan.start - 5, definition.textSpan.start) === 'v-on:') {
                        definitions.delete(definition);
                    }
                }
            }
            for (const definition of result.definitions) {
                if (vueOptions.extensions.some(ext => definition.fileName.endsWith(ext))) {
                    continue;
                }
                const sourceFile = program.getSourceFile(definition.fileName);
                if (!sourceFile) {
                    continue;
                }
                const node = visit(sourceFile, definition);
                if (!node) {
                    continue;
                }
                const position = node.getStart(sourceFile);
                const res = getDefinitionAndBoundSpan(definition.fileName, position);
                if (res?.definitions?.length) {
                    definitions.delete(definition);
                    for (const definition of res.definitions) {
                        definitions.add(definition);
                    }
                }
            }
            return {
                definitions: [...definitions],
                textSpan: result.textSpan,
            };
            function visit(sourceFile, definition) {
                for (const node of (0, utils_2.forEachTouchingNode)(ts, sourceFile, definition.textSpan.start)) {
                    if (ts.isPropertySignature(node) && node.type
                        || ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.type) {
                        if (definition.textSpan.start + definition.textSpan.length > node.name.end
                            || definition.textSpan.start < node.name.getStart(sourceFile)) {
                            continue;
                        }
                        let type = node.type;
                        while (ts.isTypeReferenceNode(type) && type.typeArguments?.length) {
                            type = type.typeArguments[0];
                        }
                        if (ts.isIndexedAccessTypeNode(type)) {
                            return type.indexType;
                        }
                        else if (ts.isImportTypeNode(type)) {
                            return type.qualifier ?? type.argument;
                        }
                    }
                }
            }
        };
    }
}
function resolveCompletionResult(ts, language, asScriptId, vueOptions, fileName, position, result) {
    // filter __VLS_
    result.entries = result.entries.filter(entry => !entry.name.includes('__VLS_')
        && !entry.labelDetails?.description?.includes('__VLS_'));
    // filter global variables in template and styles
    const sourceScript = language.scripts.get(asScriptId(fileName));
    const root = sourceScript?.generated?.root;
    if (root instanceof language_core_1.VueVirtualCode) {
        const blocks = [
            root.ir.template,
            ...root.ir.styles,
        ];
        const ranges = blocks.filter(Boolean).map(block => [
            block.startTagEnd,
            block.endTagStart,
        ]);
        if (ranges.some(([start, end]) => position >= start && position <= end)) {
            const globalKinds = new Set(['var', 'function', 'module', 'const']);
            const globalsOrKeywords = ts.Completions.SortText.GlobalsOrKeywords;
            const sortTexts = new Set([
                globalsOrKeywords,
                'z' + globalsOrKeywords,
                globalsOrKeywords + '1',
            ]);
            result.entries = result.entries.filter(entry => !(entry.kind === 'const' && entry.name in vueOptions.macros) && (!globalKinds.has(entry.kind)
                || !sortTexts.has(entry.sortText)
                || (0, shared_1.isGloballyAllowed)(entry.name)));
        }
    }
    // modify label
    for (const item of result.entries) {
        if (item.source) {
            // tsslint-ignore @typescript-eslint/no-unnecessary-type-assertion
            const data = item.data;
            const oldName = item.name;
            for (const vueExt of vueOptions.extensions) {
                const suffix = (0, shared_1.capitalize)(vueExt.slice(1)); // .vue -> Vue
                if (item.source.endsWith(vueExt) && item.name.endsWith(suffix)) {
                    item.name = (0, shared_1.capitalize)(item.name.slice(0, -suffix.length));
                    if (item.insertText) {
                        // #2286
                        item.insertText = item.insertText.replace(`${suffix}$1`, '$1');
                    }
                    if (data) {
                        data.__vue__componentAutoImport = {
                            oldName,
                            newName: item.name,
                        };
                    }
                    break;
                }
            }
            if (data) {
                data.__vue__autoImport = {
                    fileName,
                };
            }
        }
    }
}
function resolveCompletionEntryDetails(language, details, data) {
    // modify import statement
    if (data?.__vue__componentAutoImport) {
        const { oldName, newName } = data.__vue__componentAutoImport;
        for (const codeAction of details?.codeActions ?? []) {
            for (const change of codeAction.changes) {
                for (const textChange of change.textChanges) {
                    textChange.newText = textChange.newText.replace('import ' + oldName + ' from ', 'import ' + newName + ' from ');
                }
            }
        }
    }
    // #5874
    if (data?.__vue__autoImportSuggestions) {
        for (const codeAction of details?.codeActions ?? []) {
            for (const change of codeAction.changes) {
                for (const textChange of change.textChanges) {
                    if (data.__vue__componentAutoImport) {
                        const { oldName, newName } = data.__vue__componentAutoImport;
                        textChange.newText = textChange.newText.replace('import type ' + oldName + ' from ', 'import ' + newName + ' from ');
                    }
                    const { entryName } = data.__vue__autoImportSuggestions;
                    textChange.newText = textChange.newText.replace('import type { ' + entryName + ' } from ', 'import { ' + entryName + ' } from ');
                }
            }
        }
    }
    if (data?.__vue__autoImport) {
        const { fileName } = data.__vue__autoImport;
        const sourceScript = language.scripts.get(fileName);
        if (sourceScript?.generated?.root instanceof language_core_1.VueVirtualCode) {
            const { vueSfc } = sourceScript.generated.root;
            if (!vueSfc?.descriptor.script && !vueSfc?.descriptor.scriptSetup) {
                for (const codeAction of details?.codeActions ?? []) {
                    for (const change of codeAction.changes) {
                        for (const textChange of change.textChanges) {
                            textChange.newText = `<script setup lang="ts">${textChange.newText}</script>\n\n`;
                            break;
                        }
                        break;
                    }
                    break;
                }
            }
        }
    }
}
//# sourceMappingURL=common.js.map