
import { RunOnceScheduler } from '../../../base/common/async.js';
import { Color } from '../../../base/common/color.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { localize } from '../../../nls.js';
import { Extensions as Extensions$1 } from '../../jsonschemas/common/jsonContributionRegistry.js';
import { Registry } from '../../registry/common/platform.js';

const TOKEN_TYPE_WILDCARD = "*";
const TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR = ":";
const CLASSIFIER_MODIFIER_SEPARATOR = ".";
const idPattern = "\\w+[-_\\w+]*";
const typeAndModifierIdPattern = `^${idPattern}$`;
const selectorPattern = `^(${idPattern}|\\*)(\\${CLASSIFIER_MODIFIER_SEPARATOR}${idPattern})*(${TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR}${idPattern})?$`;
const fontStylePattern = "^(\\s*(italic|bold|underline|strikethrough))*\\s*$";
class TokenStyle {
    constructor(foreground, bold, underline, strikethrough, italic) {
        this.foreground = foreground;
        this.bold = bold;
        this.underline = underline;
        this.strikethrough = strikethrough;
        this.italic = italic;
    }
}
(function(TokenStyle) {
    function toJSONObject(style) {
        return {
            _foreground: style.foreground === undefined ? null : Color.Format.CSS.formatHexA(style.foreground, true),
            _bold: style.bold === undefined ? null : style.bold,
            _underline: style.underline === undefined ? null : style.underline,
            _italic: style.italic === undefined ? null : style.italic,
            _strikethrough: style.strikethrough === undefined ? null : style.strikethrough
        };
    }
    TokenStyle.toJSONObject = toJSONObject;
    function fromJSONObject(obj) {
        if (obj) {
            const boolOrUndef = b => (typeof b === "boolean") ? b : undefined;
            const colorOrUndef = s => (typeof s === "string") ? ( Color.fromHex(s)) : undefined;
            return ( new TokenStyle(
                colorOrUndef(obj._foreground),
                boolOrUndef(obj._bold),
                boolOrUndef(obj._underline),
                boolOrUndef(obj._strikethrough),
                boolOrUndef(obj._italic)
            ));
        }
        return undefined;
    }
    TokenStyle.fromJSONObject = fromJSONObject;
    function equals(s1, s2) {
        if (s1 === s2) {
            return true;
        }
        return s1 !== undefined && s2 !== undefined && (s1.foreground instanceof Color ? s1.foreground.equals(s2.foreground) : s2.foreground === undefined) && s1.bold === s2.bold && s1.underline === s2.underline && s1.strikethrough === s2.strikethrough && s1.italic === s2.italic;
    }
    TokenStyle.equals = equals;
    function is(s) {
        return s instanceof TokenStyle;
    }
    TokenStyle.is = is;
    function fromData(data) {
        return ( new TokenStyle(
            data.foreground,
            data.bold,
            data.underline,
            data.strikethrough,
            data.italic
        ));
    }
    TokenStyle.fromData = fromData;
    function fromSettings(foreground, fontStyle, bold, underline, strikethrough, italic) {
        let foregroundColor = undefined;
        if (foreground !== undefined) {
            foregroundColor = ( Color.fromHex(foreground));
        }
        if (fontStyle !== undefined) {
            bold = italic = underline = strikethrough = false;
            const expression = /italic|bold|underline|strikethrough/g;
            let match;
            while ((match = expression.exec(fontStyle))) {
                switch (match[0]) {
                case "bold":
                    bold = true;
                    break;
                case "italic":
                    italic = true;
                    break;
                case "underline":
                    underline = true;
                    break;
                case "strikethrough":
                    strikethrough = true;
                    break;
                }
            }
        }
        return ( new TokenStyle(foregroundColor, bold, underline, strikethrough, italic));
    }
    TokenStyle.fromSettings = fromSettings;
})(TokenStyle || (TokenStyle = {}));
var SemanticTokenRule;
(function(SemanticTokenRule) {
    function fromJSONObject(registry, o) {
        if (o && typeof o._selector === "string" && o._style) {
            const style = TokenStyle.fromJSONObject(o._style);
            if (style) {
                try {
                    return {
                        selector: registry.parseTokenSelector(o._selector),
                        style
                    };
                } catch (_ignore) {}
            }
        }
        return undefined;
    }
    SemanticTokenRule.fromJSONObject = fromJSONObject;
    function toJSONObject(rule) {
        return {
            _selector: rule.selector.id,
            _style: TokenStyle.toJSONObject(rule.style)
        };
    }
    SemanticTokenRule.toJSONObject = toJSONObject;
    function equals(r1, r2) {
        if (r1 === r2) {
            return true;
        }
        return r1 !== undefined && r2 !== undefined && r1.selector && r2.selector && r1.selector.id === r2.selector.id && TokenStyle.equals(r1.style, r2.style);
    }
    SemanticTokenRule.equals = equals;
    function is(r) {
        return r && r.selector && typeof r.selector.id === "string" && TokenStyle.is(r.style);
    }
    SemanticTokenRule.is = is;
})(SemanticTokenRule || (SemanticTokenRule = {}));
const Extensions = {
    TokenClassificationContribution: "base.contributions.tokenClassification"
};
class TokenClassificationRegistry extends Disposable {
    constructor() {
        super();
        this._onDidChangeSchema = this._register(( new Emitter()));
        this.onDidChangeSchema = this._onDidChangeSchema.event;
        this.currentTypeNumber = 0;
        this.currentModifierBit = 1;
        this.tokenStylingDefaultRules = [];
        this.tokenStylingSchema = {
            type: "object",
            properties: {},
            patternProperties: {
                [selectorPattern]: getStylingSchemeEntry()
            },
            additionalProperties: false,
            definitions: {
                style: {
                    type: "object",
                    description: ( localize(2517, "Colors and styles for the token.")),
                    properties: {
                        foreground: {
                            type: "string",
                            description: ( localize(2518, "Foreground color for the token.")),
                            format: "color-hex",
                            default: "#ff0000"
                        },
                        background: {
                            type: "string",
                            deprecationMessage: ( localize(2519, "Token background colors are currently not supported."))
                        },
                        fontStyle: {
                            type: "string",
                            description: ( localize(
                                2520,
                                "Sets the all font styles of the rule: 'italic', 'bold', 'underline' or 'strikethrough' or a combination. All styles that are not listed are unset. The empty string unsets all styles."
                            )),
                            pattern: fontStylePattern,
                            patternErrorMessage: ( localize(
                                2521,
                                "Font style must be 'italic', 'bold', 'underline' or 'strikethrough' or a combination. The empty string unsets all styles."
                            )),
                            defaultSnippets: [{
                                label: ( localize(2522, "None (clear inherited style)")),
                                bodyText: "\"\""
                            }, {
                                body: "italic"
                            }, {
                                body: "bold"
                            }, {
                                body: "underline"
                            }, {
                                body: "strikethrough"
                            }, {
                                body: "italic bold"
                            }, {
                                body: "italic underline"
                            }, {
                                body: "italic strikethrough"
                            }, {
                                body: "bold underline"
                            }, {
                                body: "bold strikethrough"
                            }, {
                                body: "underline strikethrough"
                            }, {
                                body: "italic bold underline"
                            }, {
                                body: "italic bold strikethrough"
                            }, {
                                body: "italic underline strikethrough"
                            }, {
                                body: "bold underline strikethrough"
                            }, {
                                body: "italic bold underline strikethrough"
                            }]
                        },
                        bold: {
                            type: "boolean",
                            description: ( localize(
                                2523,
                                "Sets or unsets the font style to bold. Note, the presence of 'fontStyle' overrides this setting."
                            ))
                        },
                        italic: {
                            type: "boolean",
                            description: ( localize(
                                2524,
                                "Sets or unsets the font style to italic. Note, the presence of 'fontStyle' overrides this setting."
                            ))
                        },
                        underline: {
                            type: "boolean",
                            description: ( localize(
                                2525,
                                "Sets or unsets the font style to underline. Note, the presence of 'fontStyle' overrides this setting."
                            ))
                        },
                        strikethrough: {
                            type: "boolean",
                            description: ( localize(
                                2526,
                                "Sets or unsets the font style to strikethrough. Note, the presence of 'fontStyle' overrides this setting."
                            ))
                        }
                    },
                    defaultSnippets: [{
                        body: {
                            foreground: "${1:#FF0000}",
                            fontStyle: "${2:bold}"
                        }
                    }]
                }
            }
        };
        this.tokenTypeById = Object.create(null);
        this.tokenModifierById = Object.create(null);
        this.typeHierarchy = Object.create(null);
    }
    registerTokenType(id, description, superType, deprecationMessage) {
        if (!id.match(typeAndModifierIdPattern)) {
            throw ( new Error("Invalid token type id."));
        }
        if (superType && !superType.match(typeAndModifierIdPattern)) {
            throw ( new Error("Invalid token super type id."));
        }
        const num = this.currentTypeNumber++;
        const tokenStyleContribution = {
            num,
            id,
            superType,
            description,
            deprecationMessage
        };
        this.tokenTypeById[id] = tokenStyleContribution;
        const stylingSchemeEntry = getStylingSchemeEntry(description, deprecationMessage);
        this.tokenStylingSchema.properties[id] = stylingSchemeEntry;
        this.typeHierarchy = Object.create(null);
    }
    registerTokenModifier(id, description, deprecationMessage) {
        if (!id.match(typeAndModifierIdPattern)) {
            throw ( new Error("Invalid token modifier id."));
        }
        const num = this.currentModifierBit;
        this.currentModifierBit = this.currentModifierBit * 2;
        const tokenStyleContribution = {
            num,
            id,
            description,
            deprecationMessage
        };
        this.tokenModifierById[id] = tokenStyleContribution;
        this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage);
    }
    parseTokenSelector(selectorString, language) {
        const selector = parseClassifierString(selectorString, language);
        if (!selector.type) {
            return {
                match: () => -1,
                id: "$invalid"
            };
        }
        return {
            match: (type, modifiers, language) => {
                let score = 0;
                if (selector.language !== undefined) {
                    if (selector.language !== language) {
                        return -1;
                    }
                    score += 10;
                }
                if (selector.type !== TOKEN_TYPE_WILDCARD) {
                    const hierarchy = this.getTypeHierarchy(type);
                    const level = hierarchy.indexOf(selector.type);
                    if (level === -1) {
                        return -1;
                    }
                    score += (100 - level);
                }
                for (const selectorModifier of selector.modifiers) {
                    if (modifiers.indexOf(selectorModifier) === -1) {
                        return -1;
                    }
                }
                return score + selector.modifiers.length * 100;
            },
            id: `${[selector.type, ...selector.modifiers.sort()].join(".")}${selector.language !== undefined ? ":" + selector.language : ""}`
        };
    }
    registerTokenStyleDefault(selector, defaults) {
        this.tokenStylingDefaultRules.push({
            selector,
            defaults
        });
    }
    deregisterTokenStyleDefault(selector) {
        const selectorString = selector.id;
        this.tokenStylingDefaultRules = this.tokenStylingDefaultRules.filter(r => r.selector.id !== selectorString);
    }
    deregisterTokenType(id) {
        delete this.tokenTypeById[id];
        delete this.tokenStylingSchema.properties[id];
        this.typeHierarchy = Object.create(null);
    }
    deregisterTokenModifier(id) {
        delete this.tokenModifierById[id];
        delete this.tokenStylingSchema.properties[`*.${id}`];
    }
    getTokenTypes() {
        return ( ( Object.keys(this.tokenTypeById)).map(id => this.tokenTypeById[id]));
    }
    getTokenModifiers() {
        return ( ( Object.keys(this.tokenModifierById)).map(id => this.tokenModifierById[id]));
    }
    getTokenStylingSchema() {
        return this.tokenStylingSchema;
    }
    getTokenStylingDefaultRules() {
        return this.tokenStylingDefaultRules;
    }
    getTypeHierarchy(typeId) {
        let hierarchy = this.typeHierarchy[typeId];
        if (!hierarchy) {
            this.typeHierarchy[typeId] = hierarchy = [typeId];
            let type = this.tokenTypeById[typeId];
            while (type && type.superType) {
                hierarchy.push(type.superType);
                type = this.tokenTypeById[type.superType];
            }
        }
        return hierarchy;
    }
    toString() {
        const sorter = (a, b) => {
            const cat1 = a.indexOf(".") === -1 ? 0 : 1;
            const cat2 = b.indexOf(".") === -1 ? 0 : 1;
            if (cat1 !== cat2) {
                return cat1 - cat2;
            }
            return a.localeCompare(b);
        };
        return ( ( Object.keys(this.tokenTypeById)).sort(sorter).map(k => `- \`${k}\`: ${this.tokenTypeById[k].description}`)).join("\n");
    }
}
const CHAR_LANGUAGE = TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR.charCodeAt(0);
const CHAR_MODIFIER = CLASSIFIER_MODIFIER_SEPARATOR.charCodeAt(0);
function parseClassifierString(s, defaultLanguage) {
    let k = s.length;
    let language = defaultLanguage;
    const modifiers = [];
    for (let i = k - 1; i >= 0; i--) {
        const ch = s.charCodeAt(i);
        if (ch === CHAR_LANGUAGE || ch === CHAR_MODIFIER) {
            const segment = s.substring(i + 1, k);
            k = i;
            if (ch === CHAR_LANGUAGE) {
                language = segment;
            } else {
                modifiers.push(segment);
            }
        }
    }
    const type = s.substring(0, k);
    return {
        type,
        modifiers,
        language
    };
}
const tokenClassificationRegistry = createDefaultTokenClassificationRegistry();
Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry);
function createDefaultTokenClassificationRegistry() {
    const registry = ( new TokenClassificationRegistry());
    function registerTokenType(id, description, scopesToProbe = [], superType, deprecationMessage) {
        registry.registerTokenType(id, description, superType, deprecationMessage);
        if (scopesToProbe) {
            registerTokenStyleDefault(id, scopesToProbe);
        }
        return id;
    }
    function registerTokenStyleDefault(selectorString, scopesToProbe) {
        try {
            const selector = registry.parseTokenSelector(selectorString);
            registry.registerTokenStyleDefault(selector, {
                scopesToProbe
            });
        } catch (e) {
            console.log(e);
        }
    }
    registerTokenType("comment", ( localize(2527, "Style for comments.")), [["comment"]]);
    registerTokenType("string", ( localize(2528, "Style for strings.")), [["string"]]);
    registerTokenType("keyword", ( localize(2529, "Style for keywords.")), [["keyword.control"]]);
    registerTokenType("number", ( localize(2530, "Style for numbers.")), [["constant.numeric"]]);
    registerTokenType("regexp", ( localize(2531, "Style for expressions.")), [["constant.regexp"]]);
    registerTokenType("operator", ( localize(2532, "Style for operators.")), [["keyword.operator"]]);
    registerTokenType("namespace", ( localize(2533, "Style for namespaces.")), [["entity.name.namespace"]]);
    registerTokenType("type", ( localize(2534, "Style for types.")), [["entity.name.type"], ["support.type"]]);
    registerTokenType("struct", ( localize(2535, "Style for structs.")), [["entity.name.type.struct"]]);
    registerTokenType("class", ( localize(2536, "Style for classes.")), [["entity.name.type.class"], ["support.class"]]);
    registerTokenType("interface", ( localize(2537, "Style for interfaces.")), [["entity.name.type.interface"]]);
    registerTokenType("enum", ( localize(2538, "Style for enums.")), [["entity.name.type.enum"]]);
    registerTokenType("typeParameter", ( localize(2539, "Style for type parameters.")), [["entity.name.type.parameter"]]);
    registerTokenType("function", ( localize(2540, "Style for functions")), [["entity.name.function"], ["support.function"]]);
    registerTokenType("member", ( localize(2541, "Style for member functions")), [], "method", "Deprecated use `method` instead");
    registerTokenType("method", ( localize(2542, "Style for method (member functions)")), [["entity.name.function.member"], ["support.function"]]);
    registerTokenType("macro", ( localize(2543, "Style for macros.")), [["entity.name.function.preprocessor"]]);
    registerTokenType("variable", ( localize(2544, "Style for variables.")), [["variable.other.readwrite"], ["entity.name.variable"]]);
    registerTokenType("parameter", ( localize(2545, "Style for parameters.")), [["variable.parameter"]]);
    registerTokenType("property", ( localize(2546, "Style for properties.")), [["variable.other.property"]]);
    registerTokenType("enumMember", ( localize(2547, "Style for enum members.")), [["variable.other.enummember"]]);
    registerTokenType("event", ( localize(2548, "Style for events.")), [["variable.other.event"]]);
    registerTokenType("decorator", ( localize(2549, "Style for decorators & annotations.")), [["entity.name.decorator"], ["entity.name.function"]]);
    registerTokenType("label", ( localize(2550, "Style for labels. ")), undefined);
    registry.registerTokenModifier("declaration", ( localize(2551, "Style for all symbol declarations.")), undefined);
    registry.registerTokenModifier("documentation", ( localize(2552, "Style to use for references in documentation.")), undefined);
    registry.registerTokenModifier("static", ( localize(2553, "Style to use for symbols that are static.")), undefined);
    registry.registerTokenModifier("abstract", ( localize(2554, "Style to use for symbols that are abstract.")), undefined);
    registry.registerTokenModifier("deprecated", ( localize(2555, "Style to use for symbols that are deprecated.")), undefined);
    registry.registerTokenModifier("modification", ( localize(2556, "Style to use for write accesses.")), undefined);
    registry.registerTokenModifier("async", ( localize(2557, "Style to use for symbols that are async.")), undefined);
    registry.registerTokenModifier("readonly", ( localize(2558, "Style to use for symbols that are read-only.")), undefined);
    registerTokenStyleDefault("variable.readonly", [["variable.other.constant"]]);
    registerTokenStyleDefault("property.readonly", [["variable.other.constant.property"]]);
    registerTokenStyleDefault("type.defaultLibrary", [["support.type"]]);
    registerTokenStyleDefault("class.defaultLibrary", [["support.class"]]);
    registerTokenStyleDefault("interface.defaultLibrary", [["support.class"]]);
    registerTokenStyleDefault(
        "variable.defaultLibrary",
        [["support.variable"], ["support.other.variable"]]
    );
    registerTokenStyleDefault("variable.defaultLibrary.readonly", [["support.constant"]]);
    registerTokenStyleDefault("property.defaultLibrary", [["support.variable.property"]]);
    registerTokenStyleDefault("property.defaultLibrary.readonly", [["support.constant.property"]]);
    registerTokenStyleDefault("function.defaultLibrary", [["support.function"]]);
    registerTokenStyleDefault("member.defaultLibrary", [["support.function"]]);
    return registry;
}
function getTokenClassificationRegistry() {
    return tokenClassificationRegistry;
}
function getStylingSchemeEntry(description, deprecationMessage) {
    return {
        description,
        deprecationMessage,
        defaultSnippets: [{
            body: "${1:#ff0000}"
        }],
        anyOf: [{
            type: "string",
            format: "color-hex"
        }, {
            $ref: "#/definitions/style"
        }]
    };
}
const tokenStylingSchemaId = "vscode://schemas/token-styling";
const schemaRegistry = Registry.as(Extensions$1.JSONContribution);
schemaRegistry.registerSchema(tokenStylingSchemaId, tokenClassificationRegistry.getTokenStylingSchema());
const delayer = ( new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStylingSchemaId), 200));
tokenClassificationRegistry.onDidChangeSchema(() => {
    if (!delayer.isScheduled()) {
        delayer.schedule();
    }
});

export { SemanticTokenRule, TokenStyle, getTokenClassificationRegistry, parseClassifierString, tokenStylingSchemaId, typeAndModifierIdPattern };
