"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSPlugin = void 0;
const emmet_helper_1 = require("@vscode/emmet-helper");
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../lib/documents");
const CSSDocument_1 = require("./CSSDocument");
const service_1 = require("./service");
const global_vars_1 = require("./global-vars");
const getIdClassCompletion_1 = require("./features/getIdClassCompletion");
const parseHtml_1 = require("../../lib/documents/parseHtml");
const StyleAttributeDocument_1 = require("./StyleAttributeDocument");
const documentContext_1 = require("../documentContext");
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const indentFolding_1 = require("../../lib/foldingRange/indentFolding");
const wordHighlight_1 = require("../../lib/documentHighlight/wordHighlight");
const utils_1 = require("../../utils");
// https://github.com/microsoft/vscode/blob/c6f507deeb99925e713271b1048f21dbaab4bd54/extensions/css/language-configuration.json#L34
const wordPattern = /(#?-?\d*\.\d\w*%?)|(::?[\w-]*(?=[^,{;]*[,{]))|(([@#.!])?[\w-?]+%?|[@#!.])/g;
class CSSPlugin {
    constructor(docManager, configManager, workspaceFolders, cssLanguageServices) {
        this.__name = 'css';
        this.cssDocuments = new WeakMap();
        this.triggerCharacters = ['.', ':', '-', '/'];
        this.cssLanguageServices = cssLanguageServices;
        this.workspaceFolders = workspaceFolders;
        this.configManager = configManager;
        this.updateConfigs();
        const workspacePaths = workspaceFolders
            .map((folder) => (0, utils_1.urlToPath)(folder.uri))
            .filter(utils_1.isNotNullOrUndefined);
        this.globalVars = new global_vars_1.GlobalVars(workspacePaths);
        this.globalVars.watchFiles(this.configManager.get('css.globals'));
        this.configManager.onChange((config) => {
            this.globalVars.watchFiles(config.get('css.globals'));
            this.updateConfigs();
        });
        const sync = (document) => {
            this.cssDocuments.set(document, new CSSDocument_1.CSSDocument(document, this.cssLanguageServices));
        };
        docManager.on('documentChange', sync);
        docManager.on('documentOpen', sync);
        docManager.on('documentClose', (document) => this.cssDocuments.delete(document));
    }
    getSelectionRange(document, position) {
        if (!this.featureEnabled('selectionRange') || !(0, documents_1.isInTag)(position, document.styleInfo)) {
            return null;
        }
        const cssDocument = this.getCSSDoc(document);
        const [range] = this.getLanguageService(extractLanguage(cssDocument)).getSelectionRanges(cssDocument, [cssDocument.getGeneratedPosition(position)], cssDocument.stylesheet);
        if (!range) {
            return null;
        }
        return (0, documents_1.mapSelectionRangeToParent)(cssDocument, range);
    }
    getDiagnostics(document) {
        if (!this.featureEnabled('diagnostics')) {
            return [];
        }
        const cssDocument = this.getCSSDoc(document);
        const kind = extractLanguage(cssDocument);
        if (shouldExcludeValidation(kind)) {
            return [];
        }
        return this.getLanguageService(kind)
            .doValidation(cssDocument, cssDocument.stylesheet)
            .map((diagnostic) => ({ ...diagnostic, source: (0, service_1.getLanguage)(kind) }))
            .map((diagnostic) => (0, documents_1.mapObjWithRangeToOriginal)(cssDocument, diagnostic));
    }
    getDiagnosticsForPullMode(document, previousResultId) {
        if (!this.featureEnabled('diagnostics')) {
            return {
                kind: 'full',
                items: []
            };
        }
        const resultId = document.version.toString();
        if (previousResultId === resultId) {
            return {
                kind: 'unchanged',
                resultId
            };
        }
        const diagnostics = this.getDiagnostics(document);
        return {
            kind: 'full',
            items: diagnostics,
            resultId
        };
    }
    doHover(document, position) {
        if (!this.featureEnabled('hover')) {
            return null;
        }
        const cssDocument = this.getCSSDocAtPosition(document, position);
        if (cssDocument) {
            if (shouldExcludeHover(cssDocument)) {
                return null;
            }
            return this.doHoverInternal(cssDocument, position);
        }
        const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, position);
        if (attributeContext &&
            this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())) {
            const [start, end] = attributeContext.valueRange;
            return this.doHoverInternal(new StyleAttributeDocument_1.StyleAttributeDocument(document, start, end, this.cssLanguageServices), position);
        }
        return null;
    }
    doHoverInternal(cssDocument, position) {
        const hoverInfo = this.getLanguageService(extractLanguage(cssDocument)).doHover(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet);
        return hoverInfo ? (0, documents_1.mapHoverToParent)(cssDocument, hoverInfo) : hoverInfo;
    }
    async getCompletions(document, position, completionContext) {
        const triggerCharacter = completionContext?.triggerCharacter;
        const triggerKind = completionContext?.triggerKind;
        const isCustomTriggerCharacter = triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter;
        if (isCustomTriggerCharacter &&
            triggerCharacter &&
            !this.triggerCharacters.includes(triggerCharacter)) {
            return null;
        }
        if (!this.featureEnabled('completions')) {
            return null;
        }
        const cssDocument = this.getCSSDocAtPosition(document, position);
        if (cssDocument) {
            return this.getCompletionsInternal(document, position, cssDocument);
        }
        const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, position);
        if (!attributeContext) {
            return null;
        }
        if (this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())) {
            const [start, end] = attributeContext.valueRange;
            return this.getCompletionsInternal(document, position, new StyleAttributeDocument_1.StyleAttributeDocument(document, start, end, this.cssLanguageServices));
        }
        else {
            return (0, getIdClassCompletion_1.getIdClassCompletion)(this.getCSSDoc(document), attributeContext);
        }
    }
    inStyleAttributeWithoutInterpolation(attrContext, text) {
        return (attrContext.name === 'style' &&
            !!attrContext.valueRange &&
            !text.substring(attrContext.valueRange[0], attrContext.valueRange[1]).includes('{'));
    }
    async getCompletionsInternal(document, position, cssDocument) {
        if (isSASS(cssDocument)) {
            // the css language service does not support sass, still we can use
            // the emmet helper directly to at least get emmet completions
            return ((0, emmet_helper_1.doComplete)(document, position, 'sass', this.configManager.getEmmetConfig()) ||
                null);
        }
        const type = extractLanguage(cssDocument);
        if (shouldExcludeCompletion(type)) {
            return null;
        }
        const lang = this.getLanguageService(type);
        let emmetResults = {
            isIncomplete: false,
            items: []
        };
        if (this.configManager.getConfig().css.completions.emmet &&
            this.configManager.getEmmetConfig().showExpandedAbbreviation !== 'never') {
            lang.setCompletionParticipants([
                {
                    onCssProperty: (context) => {
                        if (context?.propertyName) {
                            emmetResults =
                                (0, emmet_helper_1.doComplete)(cssDocument, cssDocument.getGeneratedPosition(position), (0, service_1.getLanguage)(type), this.configManager.getEmmetConfig()) || emmetResults;
                        }
                    },
                    onCssPropertyValue: (context) => {
                        if (context?.propertyValue) {
                            emmetResults =
                                (0, emmet_helper_1.doComplete)(cssDocument, cssDocument.getGeneratedPosition(position), (0, service_1.getLanguage)(type), this.configManager.getEmmetConfig()) || emmetResults;
                        }
                    }
                }
            ]);
        }
        const results = await lang.doComplete2(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet, (0, documentContext_1.getDocumentContext)(cssDocument.uri, this.workspaceFolders));
        return vscode_languageserver_1.CompletionList.create(this.appendGlobalVars([...(results ? results.items : []), ...emmetResults.items].map((completionItem) => (0, documents_1.mapCompletionItemToOriginal)(cssDocument, completionItem))), 
        // Emmet completions change on every keystroke, so they are never complete
        emmetResults.items.length > 0);
    }
    appendGlobalVars(items) {
        // Finding one value with that item kind means we are in a value completion scenario
        const value = items.find((item) => item.kind === vscode_languageserver_1.CompletionItemKind.Value);
        if (!value) {
            return items;
        }
        const additionalItems = this.globalVars
            .getGlobalVars()
            .map((globalVar) => ({
            label: `var(${globalVar.name})`,
            sortText: '-',
            detail: `${globalVar.filename}\n\n${globalVar.name}: ${globalVar.value}`,
            kind: vscode_languageserver_1.CompletionItemKind.Value
        }));
        return [...items, ...additionalItems];
    }
    getDocumentColors(document) {
        if (!this.featureEnabled('documentColors')) {
            return [];
        }
        const cssDocument = this.getCSSDoc(document);
        if (shouldExcludeColor(cssDocument)) {
            return [];
        }
        return this.getLanguageService(extractLanguage(cssDocument))
            .findDocumentColors(cssDocument, cssDocument.stylesheet)
            .map((colorInfo) => (0, documents_1.mapObjWithRangeToOriginal)(cssDocument, colorInfo));
    }
    getColorPresentations(document, range, color) {
        if (!this.featureEnabled('colorPresentations')) {
            return [];
        }
        const cssDocument = this.getCSSDoc(document);
        if ((!cssDocument.isInGenerated(range.start) && !cssDocument.isInGenerated(range.end)) ||
            shouldExcludeColor(cssDocument)) {
            return [];
        }
        return this.getLanguageService(extractLanguage(cssDocument))
            .getColorPresentations(cssDocument, cssDocument.stylesheet, color, (0, documents_1.mapRangeToGenerated)(cssDocument, range))
            .map((colorPres) => (0, documents_1.mapColorPresentationToOriginal)(cssDocument, colorPres));
    }
    getDocumentSymbols(document) {
        if (!this.featureEnabled('documentColors')) {
            return [];
        }
        const cssDocument = this.getCSSDoc(document);
        if (shouldExcludeDocumentSymbols(cssDocument)) {
            return [];
        }
        return this.getLanguageService(extractLanguage(cssDocument))
            .findDocumentSymbols(cssDocument, cssDocument.stylesheet)
            .map((symbol) => {
            if (!symbol.containerName) {
                return {
                    ...symbol,
                    // TODO: this could contain other things, e.g. style.myclass
                    containerName: 'style'
                };
            }
            return symbol;
        })
            .map((symbol) => (0, documents_1.mapSymbolInformationToOriginal)(cssDocument, symbol));
    }
    getFoldingRanges(document) {
        if (!document.styleInfo) {
            return [];
        }
        const cssDocument = this.getCSSDoc(document);
        if (shouldUseIndentBasedFolding(cssDocument.languageId)) {
            return this.nonSyntacticFolding(document, document.styleInfo);
        }
        return this.getLanguageService(extractLanguage(cssDocument))
            .getFoldingRanges(cssDocument)
            .map((range) => {
            const originalRange = (0, documents_1.mapRangeToOriginal)(cssDocument, {
                start: { line: range.startLine, character: range.startCharacter ?? 0 },
                end: { line: range.endLine, character: range.endCharacter ?? 0 }
            });
            return {
                startLine: originalRange.start.line,
                endLine: originalRange.end.line,
                kind: range.kind
            };
        });
    }
    nonSyntacticFolding(document, styleInfo) {
        const ranges = (0, indentFolding_1.indentBasedFoldingRangeForTag)(document, styleInfo);
        const startRegion = /^\s*(\/\/|\/\*\*?)\s*#?region\b/;
        const endRegion = /^\s*(\/\/|\/\*\*?)\s*#?endregion\b/;
        const lines = document
            .getText()
            .split(/\r?\n/)
            .slice(styleInfo.startPos.line, styleInfo.endPos.line);
        let start = -1;
        for (let index = 0; index < lines.length; index++) {
            const line = lines[index];
            if (startRegion.test(line)) {
                start = index;
            }
            else if (endRegion.test(line)) {
                if (start >= 0) {
                    ranges.push({
                        startLine: start + styleInfo.startPos.line,
                        endLine: index + styleInfo.startPos.line,
                        kind: vscode_languageserver_types_1.FoldingRangeKind.Region
                    });
                }
                start = -1;
            }
        }
        return ranges.sort((a, b) => a.startLine - b.startLine);
    }
    findDocumentHighlight(document, position) {
        const cssDocument = this.getCSSDocAtPosition(document, position);
        if (cssDocument) {
            if (shouldExcludeDocumentHighlights(cssDocument)) {
                return (0, wordHighlight_1.wordHighlightForTag)(document, position, document.styleInfo, wordPattern);
            }
            return this.findDocumentHighlightInternal(cssDocument, position);
        }
        const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, position);
        if (attributeContext &&
            this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())) {
            const [start, end] = attributeContext.valueRange;
            return this.findDocumentHighlightInternal(new StyleAttributeDocument_1.StyleAttributeDocument(document, start, end, this.cssLanguageServices), position);
        }
        return null;
    }
    findDocumentHighlightInternal(cssDocument, position) {
        const kind = extractLanguage(cssDocument);
        const result = (0, service_1.getLanguageService)(this.cssLanguageServices, kind)
            .findDocumentHighlights(cssDocument, cssDocument.getGeneratedPosition(position), cssDocument.stylesheet)
            .map((highlight) => (0, documents_1.mapObjWithRangeToOriginal)(cssDocument, highlight));
        return result;
    }
    getCSSDoc(document) {
        let cssDoc = this.cssDocuments.get(document);
        if (!cssDoc || cssDoc.version < document.version) {
            cssDoc = new CSSDocument_1.CSSDocument(document, this.cssLanguageServices);
            this.cssDocuments.set(document, cssDoc);
        }
        return cssDoc;
    }
    getCSSDocAtPosition(document, position) {
        if ((0, documents_1.isInTag)(position, document.styleInfo)) {
            return this.getCSSDoc(document);
        }
        if ((0, documents_1.isInTag)(position, document.scriptInfo) ||
            (0, documents_1.isInTag)(position, document.moduleScriptInfo) ||
            (0, documents_1.isInTag)(position, document.templateInfo)) {
            return null;
        }
        const styleInfo = (0, documents_1.extractStyleTagAtOffset)(document.getText(), document.offsetAt(position), document.html);
        return styleInfo ? new CSSDocument_1.CSSDocument(document, this.cssLanguageServices, styleInfo) : null;
    }
    updateConfigs() {
        this.getLanguageService('css')?.configure(this.configManager.getCssConfig());
        this.getLanguageService('scss')?.configure(this.configManager.getScssConfig());
        this.getLanguageService('less')?.configure(this.configManager.getLessConfig());
    }
    featureEnabled(feature) {
        return (this.configManager.enabled('css.enable') &&
            this.configManager.enabled(`css.${feature}.enable`));
    }
    getLanguageService(kind) {
        return (0, service_1.getLanguageService)(this.cssLanguageServices, kind);
    }
}
exports.CSSPlugin = CSSPlugin;
function shouldExcludeValidation(kind) {
    switch (kind) {
        case 'postcss':
        case 'sass':
        case 'stylus':
        case 'styl':
            return true;
        default:
            return false;
    }
}
function shouldExcludeCompletion(kind) {
    switch (kind) {
        case 'stylus':
        case 'styl':
            return true;
        default:
            return false;
    }
}
function shouldExcludeDocumentSymbols(document) {
    switch (extractLanguage(document)) {
        case 'sass':
        case 'stylus':
        case 'styl':
            return true;
        default:
            return false;
    }
}
function shouldExcludeHover(document) {
    switch (extractLanguage(document)) {
        case 'sass':
        case 'stylus':
        case 'styl':
            return true;
        default:
            return false;
    }
}
function shouldExcludeColor(document) {
    switch (extractLanguage(document)) {
        case 'sass':
        case 'stylus':
        case 'styl':
            return true;
        default:
            return false;
    }
}
function shouldUseIndentBasedFolding(kind) {
    switch (kind) {
        case 'postcss':
        case 'sass':
        case 'stylus':
        case 'styl':
            return true;
        default:
            return false;
    }
}
function shouldExcludeDocumentHighlights(document) {
    switch (extractLanguage(document)) {
        case 'postcss':
        case 'sass':
        case 'stylus':
        case 'styl':
            return true;
        default:
            return false;
    }
}
function isSASS(document) {
    switch (extractLanguage(document)) {
        case 'sass':
            return true;
        default:
            return false;
    }
}
function extractLanguage(document) {
    const lang = document.languageId;
    return lang.replace(/^text\//, '');
}
//# sourceMappingURL=CSSPlugin.js.map