"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.create = create;
const language_service_1 = require("@volar/language-service");
const featureWorkers_1 = require("@volar/language-service/lib/utils/featureWorkers");
const language_core_1 = require("@vue/language-core");
const shared_1 = require("@vue/shared");
const volar_service_html_1 = require("volar-service-html");
const volar_service_pug_1 = require("volar-service-pug");
const lspConverters_1 = require("volar-service-typescript/lib/utils/lspConverters");
const html = __importStar(require("vscode-html-languageservice"));
const vscode_uri_1 = require("vscode-uri");
const data_1 = require("../data");
const htmlFormatter_1 = require("../htmlFormatter");
const nameCasing_1 = require("../nameCasing");
const utils_1 = require("../utils");
const EVENT_PROP_REGEX = /^on[A-Z]/;
// String constants
const AUTO_IMPORT_PLACEHOLDER = 'AutoImportsPlaceholder';
const UPDATE_EVENT_PREFIX = 'update:';
const UPDATE_PROP_PREFIX = 'onUpdate:';
// Directive prefixes
const DIRECTIVE_V_ON = 'v-on:';
const DIRECTIVE_V_BIND = 'v-bind:';
const DIRECTIVE_V_MODEL = 'v-model:';
const V_ON_SHORTHAND = '@';
const V_BIND_SHORTHAND = ':';
const DIRECTIVE_V_FOR_NAME = 'v-for';
// Templates
const V_FOR_SNIPPET = '="${1:value} in ${2:source}"';
let customData;
let builtInData;
let modelData;
function create(ts, languageId, tsserver) {
    const defaultDataProvider = html.getDefaultHTMLDataProvider();
    let htmlData = [defaultDataProvider];
    // htmlData is shared by every project's service instance
    // lock for avoid concurrent requests from different projects
    let htmlDataLock = Promise.resolve();
    let modulePathCache;
    const onDidChangeCustomDataListeners = new Set();
    const serviceOptions = {
        useDefaultDataProvider: false,
        getDocumentContext: context => ({
            resolveReference(ref, base) {
                let baseUri = vscode_uri_1.URI.parse(base);
                const decoded = context.decodeEmbeddedDocumentUri(baseUri);
                if (decoded) {
                    baseUri = decoded[0];
                }
                if (modulePathCache
                    && baseUri.scheme === 'file'
                    && !ref.startsWith('./')
                    && !ref.startsWith('../')) {
                    const map = modulePathCache;
                    if (!map.has(ref)) {
                        const fileName = baseUri.fsPath.replace(/\\/g, '/');
                        const promise = tsserver.resolveModuleName(fileName, ref);
                        map.set(ref, promise);
                        if (promise instanceof Promise) {
                            promise.then(res => map.set(ref, res));
                        }
                    }
                    const cached = modulePathCache.get(ref);
                    if (cached instanceof Promise) {
                        throw cached;
                    }
                    if (cached) {
                        return vscode_uri_1.URI.file(cached).toString();
                    }
                }
                return (0, volar_service_html_1.resolveReference)(ref, baseUri, context.env.workspaceFolders);
            },
        }),
        getCustomData() {
            return customData?.length ? htmlData.concat(customData) : htmlData;
        },
        onDidChangeCustomData(listener) {
            onDidChangeCustomDataListeners.add(listener);
            return {
                dispose() {
                    onDidChangeCustomDataListeners.delete(listener);
                },
            };
        },
    };
    const baseService = languageId === 'jade'
        ? (0, volar_service_pug_1.create)(serviceOptions)
        : (0, volar_service_html_1.create)({
            ...serviceOptions,
            documentSelector: ['html', 'markdown'],
        });
    return {
        name: `vue-template (${languageId})`,
        capabilities: {
            ...baseService.capabilities,
            completionProvider: {
                triggerCharacters: [
                    ...baseService.capabilities.completionProvider?.triggerCharacters ?? [],
                    '@', // vue event shorthand
                ],
            },
            hoverProvider: true,
        },
        create(context) {
            const baseServiceInstance = baseService.create(context);
            if (baseServiceInstance.provide['html/languageService']) {
                const htmlService = baseServiceInstance.provide['html/languageService']();
                const parseHTMLDocument = htmlService.parseHTMLDocument.bind(htmlService);
                htmlService.parseHTMLDocument = document => {
                    const info = (0, utils_1.resolveEmbeddedCode)(context, document.uri);
                    if (info?.code.id === 'template') {
                        const templateAst = info.root.ir.template?.ast;
                        if (templateAst) {
                            let text = document.getText();
                            for (const node of (0, language_core_1.forEachInterpolationNode)(templateAst)) {
                                text = text.substring(0, node.loc.start.offset)
                                    + ' '.repeat(node.loc.end.offset - node.loc.start.offset)
                                    + text.substring(node.loc.end.offset);
                            }
                            return parseHTMLDocument({
                                ...document,
                                getText: () => text,
                            });
                        }
                    }
                    return parseHTMLDocument(document);
                };
                htmlService.format = (document, range, options) => {
                    let voidElements;
                    const info = (0, utils_1.resolveEmbeddedCode)(context, document.uri);
                    const codegen = info && language_core_1.tsCodegen.get(info.root.ir);
                    if (codegen) {
                        const componentNames = new Set([
                            ...codegen.getImportedComponents(),
                            ...codegen.getSetupExposed(),
                        ]);
                        voidElements = defaultDataProvider.provideTags()
                            .filter(tag => tag.void)
                            .map(tag => tag.name)
                            .filter(tag => !componentNames.has(tag) && !componentNames.has((0, shared_1.capitalize)(tag)));
                    }
                    return (0, htmlFormatter_1.format)(document, range, options, voidElements);
                };
            }
            builtInData ??= (0, data_1.loadTemplateData)(context.env.locale ?? 'en');
            modelData ??= (0, data_1.loadModelModifiersData)(context.env.locale ?? 'en');
            // https://vuejs.org/api/built-in-directives.html#v-on
            const vOnModifiers = extractDirectiveModifiers(builtInData.globalAttributes?.find(x => x.name === 'v-on'));
            // https://vuejs.org/api/built-in-directives.html#v-bind
            const vBindModifiers = extractDirectiveModifiers(builtInData.globalAttributes?.find(x => x.name === 'v-bind'));
            const vModelModifiers = extractModelModifiers(modelData.globalAttributes);
            const transformedItems = new WeakSet();
            const defaultTags = new Map();
            for (const tag of defaultDataProvider.provideTags()) {
                defaultTags.set(tag.name, tag);
            }
            let lastCompletionDocument;
            return {
                ...baseServiceInstance,
                dispose() {
                    baseServiceInstance.dispose?.();
                },
                async provideCompletionItems(document, position, completionContext, token) {
                    if (document.languageId !== languageId) {
                        return;
                    }
                    const info = (0, utils_1.resolveEmbeddedCode)(context, document.uri);
                    if (info?.code.id !== 'template') {
                        return;
                    }
                    const prevText = document.getText({ start: { line: 0, character: 0 }, end: position });
                    const hint = prevText.match(/(\S)[\S]*$/)?.[1];
                    const { result: htmlCompletion, info: { tagNameCasing, components, }, } = await runWithVueDataProvider(info.script.id, info.root, document.offsetAt(position), hint, 'completion', () => baseServiceInstance.provideCompletionItems(document, position, completionContext, token));
                    const componentSet = new Set(components);
                    if (!htmlCompletion) {
                        return;
                    }
                    if (!hint) {
                        htmlCompletion.isIncomplete = true;
                    }
                    if (htmlCompletion.items[0]?.kind === 12) {
                        addDirectiveModifiers(htmlCompletion, htmlCompletion.items[0], document, position);
                    }
                    await resolveAutoImportPlaceholder(htmlCompletion, info);
                    resolveComponentItemKinds(htmlCompletion);
                    return htmlCompletion;
                    async function resolveAutoImportPlaceholder(htmlCompletion, info) {
                        const autoImportPlaceholderIndex = htmlCompletion.items.findIndex(item => item.label === AUTO_IMPORT_PLACEHOLDER);
                        if (autoImportPlaceholderIndex === -1) {
                            return;
                        }
                        const offset = document.offsetAt(position);
                        const map = context.language.maps.get(info.code, info.script);
                        let spliced = false;
                        for (const [sourceOffset] of map.toSourceLocation(offset)) {
                            const autoImport = await tsserver.getAutoImportSuggestions(info.root.fileName, sourceOffset);
                            if (!autoImport) {
                                continue;
                            }
                            const tsCompletion = (0, lspConverters_1.convertCompletionInfo)(ts, autoImport, document, position, entry => entry.data);
                            const placeholder = htmlCompletion.items[autoImportPlaceholderIndex];
                            for (const tsItem of tsCompletion.items) {
                                if (placeholder.textEdit) {
                                    const newText = tsItem.textEdit?.newText ?? tsItem.label;
                                    tsItem.textEdit = {
                                        ...placeholder.textEdit,
                                        newText: tagNameCasing === 0 /* TagNameCasing.Kebab */
                                            ? (0, language_core_1.hyphenateTag)(newText)
                                            : newText,
                                    };
                                }
                                else {
                                    tsItem.textEdit = undefined;
                                }
                            }
                            htmlCompletion.items.splice(autoImportPlaceholderIndex, 1, ...tsCompletion.items);
                            spliced = true;
                            lastCompletionDocument = document;
                            break;
                        }
                        if (!spliced) {
                            htmlCompletion.items.splice(autoImportPlaceholderIndex, 1);
                        }
                    }
                    function resolveComponentItemKinds(htmlCompletion) {
                        for (const item of htmlCompletion.items) {
                            switch (item.kind) {
                                case 10:
                                    if (componentSet.has(item.label)
                                        || componentSet.has((0, shared_1.capitalize)((0, shared_1.camelize)(item.label)))) {
                                        item.kind = 6;
                                    }
                                    break;
                                case 12:
                                    if (typeof item.documentation === 'object' && item.documentation.value.includes('*@deprecated*')) {
                                        item.tags = [1];
                                    }
                                    if (item.label.startsWith(DIRECTIVE_V_ON) || item.label.startsWith(V_ON_SHORTHAND)) {
                                        item.kind = 23;
                                    }
                                    else if (item.label.startsWith(DIRECTIVE_V_BIND)
                                        || item.label.startsWith(V_BIND_SHORTHAND)
                                        || item.label.startsWith(DIRECTIVE_V_MODEL)) {
                                        item.kind = 5;
                                    }
                                    else if (item.label.startsWith('v-')) {
                                        item.kind = 14;
                                    }
                                    if (item.label === DIRECTIVE_V_FOR_NAME) {
                                        item.textEdit.newText = item.label + V_FOR_SNIPPET;
                                    }
                                    break;
                            }
                        }
                    }
                },
                async resolveCompletionItem(item) {
                    const data = item.data;
                    if (data?.__vue__autoImport || data?.__vue__componentAutoImport) {
                        const embeddedUri = vscode_uri_1.URI.parse(lastCompletionDocument.uri);
                        const decoded = context.decodeEmbeddedDocumentUri(embeddedUri);
                        if (!decoded) {
                            return item;
                        }
                        const sourceScript = context.language.scripts.get(decoded[0]);
                        if (!sourceScript) {
                            return item;
                        }
                        const details = await tsserver.resolveAutoImportCompletionEntry(data);
                        if (details) {
                            const virtualCode = sourceScript.generated.embeddedCodes.get(decoded[1]);
                            const sourceDocument = context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot);
                            const embeddedDocument = context.documents.get(embeddedUri, virtualCode.languageId, virtualCode.snapshot);
                            const map = context.language.maps.get(virtualCode, sourceScript);
                            item = (0, language_service_1.transformCompletionItem)(item, embeddedRange => (0, featureWorkers_1.getSourceRange)([sourceDocument, embeddedDocument, map], embeddedRange), embeddedDocument, context);
                            (0, lspConverters_1.applyCompletionEntryDetails)(ts, item, details, sourceDocument, fileName => vscode_uri_1.URI.file(fileName), () => undefined);
                            transformedItems.add(item);
                        }
                    }
                    return item;
                },
                transformCompletionItem(item) {
                    if (transformedItems.has(item)) {
                        return item;
                    }
                },
                async provideHover(document, position, token) {
                    if (document.languageId !== languageId) {
                        return;
                    }
                    const info = (0, utils_1.resolveEmbeddedCode)(context, document.uri);
                    if (info?.code.id !== 'template') {
                        return;
                    }
                    let { result: htmlHover, } = await runWithVueDataProvider(info.script.id, info.root, document.offsetAt(position), undefined, 'hover', () => baseServiceInstance.provideHover(document, position, token));
                    const templateAst = info.root.ir.template?.ast;
                    const enabledRichMessage = await context.env.getConfiguration?.('vue.hover.rich');
                    if (!templateAst || !enabledRichMessage || (htmlHover && hasContents(htmlHover.contents))) {
                        return htmlHover;
                    }
                    for (const element of (0, language_core_1.forEachElementNode)(templateAst)) {
                        const tagStart = element.loc.start.offset + element.loc.source.indexOf(element.tag);
                        const tagEnd = tagStart + element.tag.length;
                        const offset = document.offsetAt(position);
                        if (offset >= tagStart && offset <= tagEnd) {
                            const meta = await tsserver.getComponentMeta(info.root.fileName, element.tag);
                            const props = meta?.props.filter(p => !p.global);
                            const modelProps = new Set();
                            let tableContents = [];
                            for (const event of meta?.events ?? []) {
                                if (event.name.startsWith(UPDATE_EVENT_PREFIX)) {
                                    const modelName = event.name.slice(UPDATE_EVENT_PREFIX.length);
                                    const modelProp = props?.find(p => p.name === modelName);
                                    if (modelProp) {
                                        modelProps.add(modelProp);
                                    }
                                }
                            }
                            for (const prop of props ?? []) {
                                if (prop.name.startsWith(UPDATE_PROP_PREFIX)) {
                                    const modelName = prop.name.slice(UPDATE_PROP_PREFIX.length);
                                    const modelProp = props?.find(p => p.name === modelName);
                                    if (modelProp) {
                                        modelProps.add(modelProp);
                                    }
                                }
                            }
                            if (props?.length) {
                                let table = `<tr><th align="left">Prop</th><th align="left">Description</th><th align="left">Default</th></tr>\n`;
                                for (const p of props) {
                                    table += `<tr>
											<td>${printName(p, modelProps.has(p))}</td>
											<td>${printDescription(p)}</td>
											<td>${p.default ? `<code>${p.default}</code>` : ''}</td>
										</tr>\n`;
                                }
                                tableContents.push(table);
                            }
                            if (meta?.events?.length) {
                                let table = `<tr><th align="left">Event</th><th align="left">Description</th><th></th></tr>\n`;
                                for (const e of meta.events) {
                                    table += `<tr>
											<td>${printName(e)}</td>
											<td colspan="2">${printDescription(e)}</td>
										</tr>\n`;
                                }
                                tableContents.push(table);
                            }
                            if (meta?.slots?.length) {
                                let table = `<tr><th align="left">Slot</th><th align="left">Description</th><th></th></tr>\n`;
                                for (const s of meta.slots) {
                                    table += `<tr>
											<td>${printName(s)}</td>
											<td colspan="2">${printDescription(s)}</td>
										</tr>\n`;
                                }
                                tableContents.push(table);
                            }
                            if (meta?.exposed.length) {
                                let table = `<tr><th align="left">Exposed</th><th align="left">Description</th><th></th></tr>\n`;
                                for (const e of meta.exposed) {
                                    table += `<tr>
											<td>${printName(e)}</td>
											<td colspan="2">${printDescription(e)}</td>
										</tr>\n`;
                                }
                                tableContents.push(table);
                            }
                            htmlHover ??= {
                                range: {
                                    start: document.positionAt(tagStart),
                                    end: document.positionAt(tagEnd),
                                },
                                contents: '',
                            };
                            // 2px height per <tr>
                            const tableGap = `<tr></tr>`.repeat(4);
                            htmlHover.contents = {
                                kind: 'markdown',
                                value: tableContents
                                    ? `<table>\n${tableContents.join(`\n${tableGap}\n`)}\n</table>`
                                    : `No type information available.`,
                            };
                        }
                    }
                    return htmlHover;
                    function printName(meta, model) {
                        let name = meta.name;
                        if (meta.tags.some(tag => tag.name === 'deprecated')) {
                            name = `<del>${name}</del>`;
                        }
                        if (meta.required) {
                            name += ' <sup><em>required</em></sup>';
                        }
                        if (model) {
                            name += ' <sup><em>model</em></sup>';
                        }
                        return name;
                    }
                    function printDescription(meta) {
                        let desc = `<code>${meta.type}</code>`;
                        if (meta.description) {
                            // blank line for terminate HTML to support markdown
                            // see: https://github.github.com/gfm/#example-118
                            desc = `\n\n${meta.description}<br>${desc}`;
                        }
                        return desc;
                    }
                    function hasContents(contents) {
                        if (typeof contents === 'string') {
                            return !!contents;
                        }
                        if (Array.isArray(contents)) {
                            return contents.some(hasContents);
                        }
                        return !!contents.value;
                    }
                },
                async provideDocumentLinks(document, token) {
                    modulePathCache = new Map();
                    while (true) {
                        try {
                            const result = await baseServiceInstance.provideDocumentLinks?.(document, token);
                            modulePathCache = undefined;
                            return result;
                        }
                        catch (e) {
                            if (e instanceof Promise) {
                                await e;
                            }
                            else {
                                throw e;
                            }
                        }
                    }
                },
            };
            async function runWithVueDataProvider(sourceDocumentUri, root, position, hint, mode, fn) {
                const { install, sync } = await provideHtmlData(sourceDocumentUri, root, position, hint, mode);
                const previousLock = htmlDataLock;
                let unlock;
                htmlDataLock = new Promise(resolve => unlock = resolve);
                await previousLock;
                try {
                    // #4298: Precompute HTMLDocument before installing providers to avoid parseHTMLDocument requesting component names from tsserver
                    await fn();
                    install();
                    let lastSync = await sync();
                    let result = await fn();
                    while (lastSync.version !== (lastSync = await sync()).version) {
                        result = await fn();
                    }
                    return {
                        result,
                        ...lastSync,
                    };
                }
                finally {
                    updateHtmlData([
                        defaultDataProvider,
                    ]);
                    unlock();
                }
            }
            async function provideHtmlData(sourceDocumentUri, root, position, hint, mode) {
                const [tagNameCasing, attrNameCasing] = await Promise.all([
                    (0, nameCasing_1.getTagNameCasing)(context, sourceDocumentUri),
                    (0, nameCasing_1.getAttrNameCasing)(context, sourceDocumentUri),
                ]);
                customData ??= await getCustomData();
                const tasks = [];
                let version = 0;
                let components;
                const provideTags = createProvider({
                    async fetch() {
                        const res = await Promise.all([
                            tsserver.getComponentNames(root.fileName),
                            tsserver.getElementNames(root.fileName),
                        ]);
                        components = res[0] ?? [];
                        return {
                            components,
                            elements: res[1] ?? [],
                        };
                    },
                    provide({ components, elements }) {
                        const codegen = language_core_1.tsCodegen.get(root.ir);
                        const names = new Set();
                        const tags = new Map();
                        for (const tag of components) {
                            names.add(tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(tag) : tag);
                        }
                        for (const tag of elements) {
                            names.add(tag);
                        }
                        if (codegen) {
                            for (const name of [
                                ...codegen.getImportedComponents(),
                                ...codegen.getSetupExposed(),
                            ]) {
                                names.add(tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(name) : name);
                            }
                        }
                        for (const name of names) {
                            tags.set(name, {
                                ...defaultTags.get(name),
                                name,
                                attributes: [],
                            });
                        }
                        for (const tag of builtInData?.tags ?? []) {
                            const name = tagNameCasing === 0 /* TagNameCasing.Kebab */ ? (0, language_core_1.hyphenateTag)(tag.name) : tag.name;
                            tags.set(name, {
                                ...tag,
                                name,
                            });
                        }
                        return [...tags.values()];
                    },
                });
                const provideAttributes = createProvider({
                    async fetch(tag) {
                        const res = await Promise.all([
                            tsserver.getElementAttrs(root.fileName, tag),
                            tsserver.getComponentProps(root.fileName, position),
                            tsserver.getComponentDirectives(root.fileName),
                        ]);
                        return {
                            attrs: res[0] ?? [],
                            props: res[1] ?? [],
                            directives: res[2] ?? [],
                        };
                    },
                    provide({ attrs, props, directives }, tag) {
                        const attributes = [];
                        let addPlainAttrs = false;
                        let addVBinds = false;
                        let addVBindShorthands = false;
                        let addVOns = false;
                        let addVOnShorthands = false;
                        if (!hint) {
                            addVBindShorthands = true;
                            addVOnShorthands = true;
                        }
                        else if (hint === ':') {
                            addVBindShorthands = true;
                        }
                        else if (hint === '@') {
                            addVOnShorthands = true;
                        }
                        else {
                            addPlainAttrs = true;
                            addVBinds = true;
                            addVOns = true;
                            addVBindShorthands = true;
                            addVOnShorthands = true;
                        }
                        for (const attr of builtInData?.globalAttributes ?? []) {
                            if (attr.name === 'is' && tag.toLowerCase() !== 'component') {
                                continue;
                            }
                            if (attr.name === 'ref' || attr.name.startsWith('v-')) {
                                attributes.push(attr);
                                continue;
                            }
                            if (addPlainAttrs) {
                                attributes.push({ ...attr, name: attr.name });
                            }
                            if (addVBindShorthands) {
                                attributes.push({ ...attr, name: V_BIND_SHORTHAND + attr.name });
                            }
                            if (addVBinds) {
                                attributes.push({ ...attr, name: DIRECTIVE_V_BIND + attr.name });
                            }
                        }
                        for (const [propName, propInfo] of [
                            ...props.map(prop => [prop.name, prop]),
                            ...attrs.map(attr => [attr.name, attr]),
                        ]) {
                            if (EVENT_PROP_REGEX.test(propName)) {
                                let labelName = propName.slice(2);
                                labelName = labelName.charAt(0).toLowerCase() + labelName.slice(1);
                                if (attrNameCasing === 0 /* AttrNameCasing.Kebab */) {
                                    labelName = (0, language_core_1.hyphenateAttr)(labelName);
                                }
                                if (addVOnShorthands) {
                                    attributes.push({
                                        name: V_ON_SHORTHAND + labelName,
                                        description: propInfo?.description,
                                    });
                                }
                                if (addVOns) {
                                    attributes.push({
                                        name: DIRECTIVE_V_ON + labelName,
                                        description: propInfo?.description,
                                    });
                                }
                            }
                            else {
                                const labelName = attrNameCasing === 1 /* AttrNameCasing.Camel */ ? propName : (0, language_core_1.hyphenateAttr)(propName);
                                if (addPlainAttrs) {
                                    attributes.push({
                                        name: labelName,
                                        description: propInfo?.description,
                                        valueSet: propInfo?.boolean ? 'v' : undefined,
                                    });
                                }
                                if (addVBindShorthands) {
                                    attributes.push({
                                        name: V_BIND_SHORTHAND + labelName,
                                        description: propInfo?.description,
                                    });
                                }
                                if (addVBinds) {
                                    attributes.push({
                                        name: DIRECTIVE_V_BIND + labelName,
                                        description: propInfo?.description,
                                    });
                                }
                            }
                            if (propName.startsWith(UPDATE_PROP_PREFIX)) {
                                const model = propName.slice(UPDATE_PROP_PREFIX.length);
                                const label = DIRECTIVE_V_MODEL
                                    + (attrNameCasing === 1 /* AttrNameCasing.Camel */ ? model : (0, language_core_1.hyphenateAttr)(model));
                                attributes.push({
                                    name: label,
                                    description: propInfo?.description,
                                });
                            }
                        }
                        for (const directive of directives) {
                            attributes.push({
                                name: (0, language_core_1.hyphenateAttr)(directive),
                            });
                        }
                        // dedupe from tsserver
                        if (mode === 'hover') {
                            for (const item of attributes) {
                                item.description = undefined;
                            }
                        }
                        return attributes;
                    },
                });
                const provideValues = createProvider({
                    async fetch(tag, attr) {
                        if (tag === 'slot' && attr === 'name') {
                            return await tsserver.getComponentSlots(root.fileName) ?? [];
                        }
                        return [];
                    },
                    provide(values) {
                        return values.map(value => ({ name: value }));
                    },
                });
                return {
                    install() {
                        updateHtmlData([
                            {
                                getId: () => 'vue-template',
                                isApplicable: () => true,
                                provideTags,
                                provideAttributes,
                                provideValues,
                            },
                            {
                                getId: () => 'vue-auto-imports',
                                isApplicable: () => true,
                                provideTags: () => [{ name: AUTO_IMPORT_PLACEHOLDER, attributes: [] }],
                                provideAttributes: () => [],
                                provideValues: () => [],
                            },
                        ]);
                    },
                    async sync() {
                        await Promise.all(tasks);
                        return {
                            version,
                            info: {
                                tagNameCasing,
                                components,
                            },
                        };
                    },
                };
                function createProvider(options) {
                    let status = 'idle';
                    let data;
                    return (...args) => {
                        if (status === 'idle') {
                            status = 'pending';
                            tasks.push((async () => {
                                data = await options.fetch(...args);
                                status = 'done';
                                version++;
                            })());
                        }
                        else if (status === 'done') {
                            return options.provide(data, ...args);
                        }
                        return [];
                    };
                }
            }
            async function getCustomData() {
                const paths = await context.env.getConfiguration?.('html.customData') ?? [];
                const customData = [];
                for (const path of paths) {
                    for (const workspaceFolder of context.env.workspaceFolders) {
                        const uri = vscode_uri_1.Utils.resolvePath(workspaceFolder, path);
                        const text = await context.env.fs?.readFile(uri) ?? '';
                        try {
                            const data = JSON.parse(text);
                            customData.push(html.newHTMLDataProvider(path, data));
                        }
                        catch (error) {
                            console.error(error);
                        }
                    }
                }
                return customData;
            }
            function addDirectiveModifiers(list, item, document, position) {
                const replacement = getReplacement(item, document);
                if (!replacement?.text) {
                    return;
                }
                const replacementStart = document.offsetAt(replacement.textEdit.range.start);
                const start = replacement.text.lastIndexOf('.', document.offsetAt(position) - replacementStart - 1) + 1;
                if (start === 0) {
                    return;
                }
                const [text, ...existingModifiers] = replacement.text.split('.');
                let matchedModifiers;
                if (text.startsWith(DIRECTIVE_V_ON) || text.startsWith(V_ON_SHORTHAND) && text.length > 1) {
                    matchedModifiers = vOnModifiers;
                }
                else if (text.startsWith(DIRECTIVE_V_BIND) || text.startsWith(V_BIND_SHORTHAND) && text.length > 1) {
                    matchedModifiers = vBindModifiers;
                }
                else if (text.startsWith(DIRECTIVE_V_MODEL) || text === 'v-model') {
                    matchedModifiers = vModelModifiers;
                }
                if (matchedModifiers) {
                    const nextModifierStart = replacement.text.indexOf('.', start);
                    const end = nextModifierStart !== -1 ? nextModifierStart : replacement.text.length;
                    const range = {
                        start: document.positionAt(replacementStart + start),
                        end: document.positionAt(replacementStart + end),
                    };
                    const currentModifier = replacement.text.slice(start, end);
                    for (const modifier in matchedModifiers) {
                        if (existingModifiers.includes(modifier) && modifier !== currentModifier) {
                            continue;
                        }
                        list.items.push({
                            label: modifier,
                            filterText: modifier,
                            documentation: {
                                kind: 'markdown',
                                value: matchedModifiers[modifier],
                            },
                            textEdit: {
                                range,
                                newText: modifier,
                            },
                            kind: 20,
                        });
                    }
                }
            }
        },
    };
    function updateHtmlData(newData) {
        htmlData = newData;
        onDidChangeCustomDataListeners.forEach(l => l());
    }
}
function getReplacement(item, doc) {
    if (item.textEdit && 'range' in item.textEdit) {
        return {
            item: item,
            textEdit: item.textEdit,
            text: doc.getText(item.textEdit.range),
        };
    }
}
function extractDirectiveModifiers(directive) {
    const modifiers = {};
    if (!directive) {
        return modifiers;
    }
    const markdown = typeof directive.description === 'object'
        ? directive.description.value
        : directive.description ?? '';
    const modifierLines = markdown
        .split('\n- ')[4]
        ?.split('\n').slice(2, -1) ?? [];
    for (let text of modifierLines) {
        text = text.slice('  - `.'.length);
        const [name, desc] = text.split('` - ');
        modifiers[name] = desc;
    }
    return modifiers;
}
function extractModelModifiers(attributes) {
    const modifiers = {};
    if (!attributes) {
        return modifiers;
    }
    for (const modifier of attributes) {
        const description = typeof modifier.description === 'object'
            ? modifier.description.value
            : modifier.description ?? '';
        const references = modifier.references?.map(ref => `[${ref.name}](${ref.url})`).join(' | ');
        modifiers[modifier.name] = description + '\n\n' + references;
    }
    return modifiers;
}
//# sourceMappingURL=vue-template.js.map