"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.create = create;
exports.findDestructuredProps = findDestructuredProps;
const language_core_1 = require("@vue/language-core");
const utils_1 = require("../utils");
function create(ts) {
    return {
        name: 'vue-inlayhints',
        capabilities: {
            inlayHintProvider: {},
        },
        create(context) {
            return {
                async provideInlayHints(document, range) {
                    const info = (0, utils_1.resolveEmbeddedCode)(context, document.uri);
                    if (info?.code.id !== 'main') {
                        return;
                    }
                    const settings = {};
                    async function getSettingEnabled(key) {
                        return settings[key] ??= await context.env.getConfiguration?.(key) ?? false;
                    }
                    const result = [];
                    const { ir } = info.root;
                    const codegen = language_core_1.tsCodegen.get(ir);
                    const inlayHints = [
                        ...codegen?.getGeneratedTemplate()?.inlayHints ?? [],
                        ...codegen?.getGeneratedScript().inlayHints ?? [],
                    ];
                    const scriptSetupRanges = codegen?.getScriptSetupRanges();
                    if (scriptSetupRanges?.defineProps?.destructured && ir.scriptSetup?.ast) {
                        const setting = 'vue.inlayHints.destructuredProps';
                        const enabled = await getSettingEnabled(setting);
                        if (enabled) {
                            for (const [prop, isShorthand] of findDestructuredProps(ts, ir.scriptSetup.ast, scriptSetupRanges.defineProps.destructured.keys())) {
                                const name = prop.text;
                                const end = prop.getEnd();
                                const pos = isShorthand ? end : end - name.length;
                                const label = isShorthand ? `: props.${name}` : 'props.';
                                inlayHints.push({
                                    blockName: 'scriptSetup',
                                    offset: pos,
                                    setting,
                                    label,
                                });
                            }
                        }
                    }
                    const blocks = [
                        ir.template,
                        ir.script,
                        ir.scriptSetup,
                    ];
                    const start = document.offsetAt(range.start);
                    const end = document.offsetAt(range.end);
                    for (const hint of inlayHints) {
                        const block = blocks.find(block => block?.name === hint.blockName);
                        if (!block) {
                            continue;
                        }
                        const hintOffset = block.startTagEnd + hint.offset;
                        if (hintOffset < start || hintOffset >= end) {
                            continue;
                        }
                        const enabled = await getSettingEnabled(hint.setting);
                        if (!enabled) {
                            continue;
                        }
                        result.push({
                            label: hint.label,
                            paddingRight: hint.paddingRight,
                            paddingLeft: hint.paddingLeft,
                            position: document.positionAt(hintOffset),
                            kind: 2,
                            tooltip: hint.tooltip
                                ? {
                                    kind: 'markdown',
                                    value: hint.tooltip,
                                }
                                : undefined,
                        });
                    }
                    return result;
                },
            };
        },
    };
}
/**
 * Refactored from https://github.com/vuejs/core/blob/main/packages/compiler-sfc/src/script/definePropsDestructure.ts
 */
function findDestructuredProps(ts, ast, props) {
    const rootScope = Object.create(null);
    const scopeStack = [rootScope];
    const functionScopeStack = [rootScope];
    let currentScope = rootScope;
    const excludedIds = new WeakSet();
    for (const prop of props) {
        rootScope[prop] = true;
    }
    function pushScope(isFunctionScope = false) {
        const scope = currentScope = Object.create(currentScope);
        scopeStack.push(scope);
        if (isFunctionScope) {
            functionScopeStack.push(scope);
        }
    }
    function popScope(isFunctionScope = false) {
        scopeStack.pop();
        if (isFunctionScope) {
            functionScopeStack.pop();
        }
        currentScope = scopeStack.at(-1) || null;
    }
    function registerLocalBinding(id, scope = currentScope) {
        excludedIds.add(id);
        if (scope) {
            scope[id.text] = false;
        }
    }
    const references = [];
    walkFunctionScopeVarDeclarations(ast, true);
    walkScope(ast, true);
    walk(ast);
    return references;
    function walkScope(node, isRoot = false) {
        ts.forEachChild(node, stmt => {
            if (ts.isVariableStatement(stmt)) {
                for (const decl of stmt.declarationList.declarations) {
                    walkVariableDeclaration(decl, isRoot);
                }
            }
            else if (ts.isFunctionDeclaration(stmt)
                || ts.isClassDeclaration(stmt)) {
                const declare = ts.getModifiers(stmt)?.find(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword);
                if (!stmt.name || declare) {
                    return;
                }
                registerLocalBinding(stmt.name);
            }
            else if ((ts.isForOfStatement(stmt) || ts.isForInStatement(stmt))
                && ts.isVariableDeclarationList(stmt.initializer)) {
                walkVariableDeclaration(stmt.initializer.declarations[0], isRoot);
            }
            else if (ts.isLabeledStatement(stmt)
                && ts.isVariableDeclaration(stmt.statement)) {
                walkVariableDeclaration(stmt.statement, isRoot);
            }
        });
    }
    function walkVariableDeclaration(decl, isRoot = false, scope = currentScope) {
        const { initializer, name } = decl;
        const isDefineProps = isRoot
            && initializer
            && ts.isCallExpression(initializer)
            && initializer.expression.getText(ast) === 'defineProps';
        for (const { id } of (0, language_core_1.collectBindingIdentifiers)(ts, name)) {
            if (isDefineProps) {
                excludedIds.add(id);
            }
            else {
                registerLocalBinding(id, scope);
            }
        }
    }
    function walkFunctionScopeVarDeclarations(scopeNode, isRoot = false) {
        const scope = functionScopeStack.at(-1);
        walk(scopeNode);
        function walk(parent) {
            ts.forEachChild(parent, node => {
                if (ts.isFunctionLike(node)
                    || ts.isClassDeclaration(node)
                    || ts.isClassExpression(node)) {
                    return;
                }
                if (ts.isVariableDeclarationList(node)
                    && !(node.flags & ts.NodeFlags.BlockScoped)) {
                    for (const decl of node.declarations) {
                        walkVariableDeclaration(decl, isRoot && node.parent.parent === scopeNode, scope);
                    }
                }
                walk(node);
            });
        }
    }
    function walkFunctionDeclaration(node) {
        const { name, parameters } = node;
        if (name && ts.isIdentifier(name)) {
            registerLocalBinding(name);
        }
        for (const p of parameters) {
            for (const { id } of (0, language_core_1.collectBindingIdentifiers)(ts, p)) {
                registerLocalBinding(id);
            }
        }
    }
    function walk(parent) {
        ts.forEachChild(parent, node => {
            if (enter(node) ?? true) {
                walk(node);
                leave(node);
            }
        });
        function enter(node) {
            if (ts.isTypeLiteralNode(node)
                || ts.isTypeReferenceNode(node)) {
                return false;
            }
            if (ts.isFunctionLike(node)) {
                pushScope(true);
                walkFunctionDeclaration(node);
                if ('body' in node && node.body) {
                    walkFunctionScopeVarDeclarations(node.body);
                    walkScope(node.body);
                }
                return;
            }
            if (ts.isCatchClause(node)) {
                pushScope();
                const { variableDeclaration: p } = node;
                if (p && ts.isIdentifier(p.name)) {
                    registerLocalBinding(p.name);
                }
                walkScope(node.block);
                return;
            }
            if (ts.isBlock(node)
                && !ts.isFunctionLike(parent)
                && !ts.isCatchClause(parent)) {
                pushScope();
                walkScope(node);
                return;
            }
            if (ts.isIdentifier(node)
                && isReferencedIdentifier(node, parent)
                && !excludedIds.has(node)) {
                const name = node.text;
                if (currentScope[name]) {
                    const isShorthand = ts.isShorthandPropertyAssignment(parent);
                    references.push([node, isShorthand]);
                }
            }
        }
        function leave(node) {
            if (ts.isFunctionLike(node)) {
                popScope(true);
            }
            else if (ts.isCatchClause(node)
                || (ts.isBlock(node)
                    && !ts.isFunctionLike(parent)
                    && !ts.isCatchClause(parent))) {
                popScope();
            }
        }
    }
    // TODO: more conditions
    function isReferencedIdentifier(id, parent) {
        if (!parent) {
            return false;
        }
        if (id.text === 'arguments') {
            return false;
        }
        if (ts.isExpressionWithTypeArguments(parent)
            || ts.isInterfaceDeclaration(parent)
            || ts.isTypeAliasDeclaration(parent)
            || ts.isPropertySignature(parent)) {
            return false;
        }
        if (ts.isPropertyAccessExpression(parent)
            || ts.isPropertyAssignment(parent)
            || ts.isPropertyDeclaration(parent)) {
            if (parent.name === id) {
                return false;
            }
        }
        if (ts.isBindingElement(parent)
            || ts.isImportSpecifier(parent)
            || ts.isExportSpecifier(parent)) {
            if (parent.propertyName === id) {
                return false;
            }
        }
        return true;
    }
}
//# sourceMappingURL=vue-inlayhints.js.map