"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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getHtmlDocument = void 0;
const html = __importStar(require("vscode-html-languageservice"));
const vscode = __importStar(require("vscode-languageserver-protocol"));
const path = __importStar(require("path"));
const parserLs = html.getLanguageService();
const htmlDocuments = new WeakMap();
function getHtmlDocument(document) {
    const cache = htmlDocuments.get(document);
    if (cache) {
        const [cacheVersion, cacheDoc] = cache;
        if (cacheVersion === document.version) {
            return cacheDoc;
        }
    }
    const doc = parserLs.parseHTMLDocument(document);
    htmlDocuments.set(document, [document.version, doc]);
    return doc;
}
exports.getHtmlDocument = getHtmlDocument;
exports.default = (options = {}) => (context) => {
    const triggerCharacters = {
        // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/html-language-features/server/src/htmlServer.ts#L183
        triggerCharacters: ['.', ':', '<', '"', '=', '/'],
    };
    if (!context) {
        return triggerCharacters;
    }
    let shouldUpdateCustomData = true;
    let customData = [];
    let extraData = [];
    const htmlLs = html.getLanguageService({ fileSystemProvider: context.fileSystemProvider });
    context.configurationHost?.onDidChangeConfiguration(() => {
        shouldUpdateCustomData = true;
    });
    return {
        ...triggerCharacters,
        async resolveRuleContext(context) {
            if (options.validLang === 'html') {
                await worker(context.document, (htmlDocument) => {
                    context.html = {
                        document: htmlDocument,
                        languageService: htmlLs,
                    };
                });
            }
            return context;
        },
        getHtmlLs: () => htmlLs,
        updateCustomData: updateExtraCustomData,
        async provideCompletionItems(document, position) {
            return worker(document, async (htmlDocument) => {
                const configs = await context.configurationHost?.getConfiguration('html.completion');
                if (context.documentContext) {
                    return htmlLs.doComplete2(document, position, htmlDocument, context.documentContext, configs);
                }
                else {
                    return htmlLs.doComplete(document, position, htmlDocument, configs);
                }
            });
        },
        provideRenameRange(document, position) {
            return worker(document, (htmlDocument) => {
                const offset = document.offsetAt(position);
                return htmlLs
                    .findDocumentHighlights(document, position, htmlDocument)
                    ?.find(h => offset >= document.offsetAt(h.range.start) && offset <= document.offsetAt(h.range.end))
                    ?.range;
            });
        },
        provideRenameEdits(document, position, newName) {
            return worker(document, (htmlDocument) => {
                return htmlLs.doRename(document, position, newName, htmlDocument);
            });
        },
        async provideHover(document, position) {
            return worker(document, async (htmlDocument) => {
                const hoverSettings = await context.configurationHost?.getConfiguration('html.hover');
                return htmlLs.doHover(document, position, htmlDocument, hoverSettings);
            });
        },
        provideDocumentHighlights(document, position) {
            return worker(document, (htmlDocument) => {
                return htmlLs.findDocumentHighlights(document, position, htmlDocument);
            });
        },
        provideDocumentLinks(document) {
            return worker(document, () => {
                if (!context.documentContext)
                    return;
                return htmlLs.findDocumentLinks(document, context.documentContext);
            });
        },
        provideDocumentSymbols(document) {
            return worker(document, (htmlDocument) => {
                // TODO: wait for https://github.com/microsoft/vscode-html-languageservice/pull/152
                const symbols = [];
                htmlDocument.roots.forEach(node => {
                    provideFileSymbolsInternal(document, node, symbols);
                });
                return symbols;
            });
        },
        provideFoldingRanges(document) {
            return worker(document, () => {
                return htmlLs.getFoldingRanges(document);
            });
        },
        provideSelectionRanges(document, positions) {
            return worker(document, () => {
                return htmlLs.getSelectionRanges(document, positions);
            });
        },
        async provideDocumentFormattingEdits(document, formatRange, options) {
            return worker(document, async () => {
                const options_2 = await context.configurationHost?.getConfiguration('html.format');
                if (options_2?.enable === false) {
                    return;
                }
                { // https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/extensions/html-language-features/server/src/modes/formatting.ts#L13-L23
                    const endPos = formatRange.end;
                    let endOffset = document.offsetAt(endPos);
                    const content = document.getText();
                    if (endPos.character === 0 && endPos.line > 0 && endOffset !== content.length) {
                        // if selection ends after a new line, exclude that new line
                        const prevLineStart = document.offsetAt(vscode.Position.create(endPos.line - 1, 0));
                        while (isEOL(content, endOffset - 1) && endOffset > prevLineStart) {
                            endOffset--;
                        }
                        formatRange = vscode.Range.create(formatRange.start, document.positionAt(endOffset));
                    }
                }
                return htmlLs.format(document, formatRange, {
                    ...options_2,
                    ...options,
                });
            });
        },
        provideFormattingIndentSensitiveLines(document) {
            return worker(document, (htmlDocument) => {
                const lines = [];
                /**
                 * comments
                 */
                const scanner = htmlLs.createScanner(document.getText());
                let token = scanner.scan();
                let startCommentTagLine;
                while (token !== html.TokenType.EOS) {
                    if (token === html.TokenType.StartCommentTag) {
                        startCommentTagLine = document.positionAt(scanner.getTokenOffset()).line;
                    }
                    else if (token === html.TokenType.EndCommentTag) {
                        const line = document.positionAt(scanner.getTokenOffset()).line;
                        for (let i = startCommentTagLine + 1; i <= line; i++) {
                            lines.push(i);
                        }
                        startCommentTagLine = undefined;
                    }
                    else if (token === html.TokenType.AttributeValue) {
                        const startLine = document.positionAt(scanner.getTokenOffset()).line;
                        for (let i = 1; i < scanner.getTokenText().split('\n').length; i++) {
                            lines.push(startLine + i);
                        }
                    }
                    token = scanner.scan();
                }
                /**
                 * tags
                 */
                // https://github.com/beautify-web/js-beautify/blob/686f8c1b265990908ece86ce39291733c75c997c/js/src/html/options.js#L81
                const indentSensitiveTags = new Set(['pre', 'textarea']);
                htmlDocument.roots.forEach(function visit(node) {
                    if (node.tag !== undefined
                        && node.startTagEnd !== undefined
                        && node.endTagStart !== undefined
                        && indentSensitiveTags.has(node.tag)) {
                        for (let i = document.positionAt(node.startTagEnd).line + 1; i <= document.positionAt(node.endTagStart).line; i++) {
                            lines.push(i);
                        }
                    }
                    else {
                        node.children.forEach(visit);
                    }
                });
                return lines;
            });
        },
        provideLinkedEditingRanges(document, position) {
            return worker(document, (htmlDocument) => {
                const ranges = htmlLs.findLinkedEditingRanges(document, position, htmlDocument);
                if (!ranges)
                    return;
                return { ranges };
            });
        },
        async provideAutoInsertionEdit(document, position, insertContext) {
            return worker(document, async (htmlDocument) => {
                const lastCharacter = insertContext.lastChange.text[insertContext.lastChange.text.length - 1];
                if (insertContext.lastChange.rangeLength === 0 && lastCharacter === '=') {
                    const enabled = (await context.configurationHost?.getConfiguration('html.autoCreateQuotes')) ?? true;
                    if (enabled) {
                        const text = htmlLs.doQuoteComplete(document, position, htmlDocument, await context.configurationHost?.getConfiguration('html.completion'));
                        if (text) {
                            return text;
                        }
                    }
                }
                if (insertContext.lastChange.rangeLength === 0 && (lastCharacter === '>' || lastCharacter === '/')) {
                    const enabled = (await context.configurationHost?.getConfiguration('html.autoClosingTags')) ?? true;
                    if (enabled) {
                        const text = htmlLs.doTagComplete(document, position, htmlDocument);
                        if (text) {
                            return text;
                        }
                    }
                }
            });
        },
    };
    async function initCustomData() {
        if (shouldUpdateCustomData && !options.disableCustomData) {
            shouldUpdateCustomData = false;
            customData = await getCustomData();
            htmlLs.setDataProviders(true, [...customData, ...extraData]);
        }
    }
    function updateExtraCustomData(data) {
        extraData = data;
        htmlLs.setDataProviders(true, [...customData, ...extraData]);
    }
    async function getCustomData() {
        const configHost = context?.configurationHost;
        if (configHost) {
            const customData = await configHost.getConfiguration('html.customData') ?? [];
            const newData = [];
            for (const customDataPath of customData) {
                try {
                    const jsonPath = path.resolve(customDataPath);
                    newData.push(html.newHTMLDataProvider(customDataPath, require(jsonPath)));
                }
                catch (error) {
                    console.error(error);
                }
            }
            return newData;
        }
        return [];
    }
    async function worker(document, callback) {
        if (document.languageId !== (options.validLang ?? 'html'))
            return;
        const htmlDocument = getHtmlDocument(document);
        if (!htmlDocument)
            return;
        await initCustomData();
        return callback(htmlDocument);
    }
};
function isEOL(content, offset) {
    return isNewlineCharacter(content.charCodeAt(offset));
}
const CR = '\r'.charCodeAt(0);
const NL = '\n'.charCodeAt(0);
function isNewlineCharacter(charCode) {
    return charCode === CR || charCode === NL;
}
function provideFileSymbolsInternal(document, node, symbols) {
    const name = nodeToName(node);
    const range = vscode.Range.create(document.positionAt(node.start), document.positionAt(node.end));
    const symbol = vscode.DocumentSymbol.create(name, undefined, vscode.SymbolKind.Field, range, range);
    symbols.push(symbol);
    node.children.forEach(child => {
        symbol.children ??= [];
        provideFileSymbolsInternal(document, child, symbol.children);
    });
}
function nodeToName(node) {
    let name = node.tag;
    if (node.attributes) {
        const id = node.attributes['id'];
        const classes = node.attributes['class'];
        if (id) {
            name += `#${id.replace(/[\"\']/g, '')}`;
        }
        if (classes) {
            name += classes.replace(/[\"\']/g, '').split(/\s+/).map(className => `.${className}`).join('');
        }
    }
    return name || '?';
}
//# sourceMappingURL=index.js.map