"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.doSemanticTokens = exports.tokenModifiers = exports.tokenTypes = void 0;
const vscode_languageserver_1 = require("vscode-languageserver");
const yaml_1 = require("yaml");
const ansible_1 = require("../utils/ansible");
const yaml_2 = require("../utils/yaml");
exports.tokenTypes = [
    vscode_languageserver_1.SemanticTokenTypes.method,
    vscode_languageserver_1.SemanticTokenTypes.class,
    vscode_languageserver_1.SemanticTokenTypes.keyword,
    vscode_languageserver_1.SemanticTokenTypes.property,
];
const tokenTypesLegend = new Map(exports.tokenTypes.map((value, index) => [value, index]));
exports.tokenModifiers = [vscode_languageserver_1.SemanticTokenModifiers.definition];
const tokenModifiersLegend = new Map(exports.tokenModifiers.map((value, index) => [value, index]));
function doSemanticTokens(document, docsLibrary) {
    return __awaiter(this, void 0, void 0, function* () {
        const builder = new vscode_languageserver_1.SemanticTokensBuilder();
        const yDocuments = (0, yaml_2.parseAllDocuments)(document.getText());
        for (const yDoc of yDocuments) {
            if (yDoc.contents) {
                yield markSemanticTokens([yDoc.contents], builder, document, docsLibrary);
            }
        }
        return builder.build();
    });
}
exports.doSemanticTokens = doSemanticTokens;
function markSemanticTokens(path, builder, document, docsLibrary) {
    var _a, _b;
    return __awaiter(this, void 0, void 0, function* () {
        const node = path[path.length - 1];
        if ((0, yaml_1.isMap)(node)) {
            for (const pair of node.items) {
                if ((0, yaml_1.isScalar)(pair.key)) {
                    const keyPath = path.concat(pair, pair.key);
                    if ((0, yaml_2.isPlayParam)(keyPath)) {
                        if (ansible_1.playKeywords.has(pair.key.value.toString()))
                            markKeyword(pair.key, builder, document);
                        else
                            markOrdinaryKey(pair.key, builder, document);
                    }
                    else if ((0, yaml_2.isBlockParam)(keyPath)) {
                        if (ansible_1.blockKeywords.has(pair.key.value.toString()))
                            markKeyword(pair.key, builder, document);
                        else
                            markOrdinaryKey(pair.key, builder, document);
                    }
                    else if ((0, yaml_2.isRoleParam)(keyPath)) {
                        if (ansible_1.roleKeywords.has(pair.key.value.toString()))
                            markKeyword(pair.key, builder, document);
                        else
                            markOrdinaryKey(pair.key, builder, document);
                    }
                    else if ((0, yaml_2.isTaskParam)(keyPath)) {
                        if ((0, ansible_1.isTaskKeyword)(pair.key.value.toString())) {
                            markKeyword(pair.key, builder, document);
                            if (pair.key.value === "args") {
                                const module = yield (0, yaml_2.findProvidedModule)(path.concat(pair, pair.key), document, docsLibrary);
                                if (module && (0, yaml_1.isMap)(pair.value)) {
                                    // highlight module parameters
                                    markModuleParameters(pair.value, (_a = module.documentation) === null || _a === void 0 ? void 0 : _a.options, builder, document);
                                }
                            }
                        }
                        else {
                            const [module] = yield docsLibrary.findModule(pair.key.value.toString(), keyPath, document.uri);
                            if (module) {
                                // highlight module name
                                markNode(pair.key, vscode_languageserver_1.SemanticTokenTypes.class, [], builder, document);
                                if ((0, yaml_1.isMap)(pair.value)) {
                                    // highlight module parameters
                                    markModuleParameters(pair.value, (_b = module.documentation) === null || _b === void 0 ? void 0 : _b.options, builder, document);
                                }
                            }
                            else {
                                markAllNestedKeysAsOrdinary(pair, builder, document);
                            }
                        }
                        // this pair has been completely processed
                        // tasks don't have any deeper structure
                        continue;
                    }
                    else {
                        markAllNestedKeysAsOrdinary(pair, builder, document);
                        // this pair has been completely processed
                        continue;
                    }
                }
                if ((0, yaml_1.isNode)(pair.value)) {
                    yield markSemanticTokens(path.concat(pair, pair.value), builder, document, docsLibrary);
                }
            }
        }
        else if ((0, yaml_1.isSeq)(node)) {
            for (const item of node.items) {
                if ((0, yaml_1.isNode)(item)) {
                    // the builder does not support out-of-order inserts yet, hence awaiting
                    // on each individual promise instead of using Promise.all
                    yield markSemanticTokens(path.concat(item), builder, document, docsLibrary);
                }
            }
        }
    });
}
function markModuleParameters(moduleParamMap, options, builder, document) {
    for (const moduleParamPair of moduleParamMap.items) {
        if ((0, yaml_1.isScalar)(moduleParamPair.key)) {
            const option = options === null || options === void 0 ? void 0 : options.get(moduleParamPair.key.value.toString());
            if (option) {
                markNode(moduleParamPair.key, vscode_languageserver_1.SemanticTokenTypes.method, [], builder, document);
                if (option.type === "dict" && (0, yaml_1.isMap)(moduleParamPair.value)) {
                    // highlight sub-parameters
                    markModuleParameters(moduleParamPair.value, option.suboptions, builder, document);
                }
                else if (option.type === "list" && (0, yaml_1.isSeq)(moduleParamPair.value)) {
                    // highlight list of sub-parameters
                    for (const item of moduleParamPair.value.items) {
                        if ((0, yaml_1.isMap)(item)) {
                            markModuleParameters(item, option.suboptions, builder, document);
                        }
                        else {
                            markAllNestedKeysAsOrdinary(item, builder, document);
                        }
                    }
                }
                else {
                    markAllNestedKeysAsOrdinary(moduleParamPair.value, builder, document);
                }
            }
            else {
                markAllNestedKeysAsOrdinary(moduleParamPair.value, builder, document);
            }
        }
        else if ((0, yaml_1.isNode)(moduleParamPair.value)) {
            markAllNestedKeysAsOrdinary(moduleParamPair.value, builder, document);
        }
    }
}
function markAllNestedKeysAsOrdinary(node, builder, document) {
    if ((0, yaml_1.isPair)(node)) {
        if ((0, yaml_1.isScalar)(node.key)) {
            markOrdinaryKey(node.key, builder, document);
        }
        if ((0, yaml_1.isNode)(node.value)) {
            markAllNestedKeysAsOrdinary(node.value, builder, document);
        }
    }
    else if ((0, yaml_1.isMap)(node)) {
        for (const pair of node.items) {
            markAllNestedKeysAsOrdinary(pair, builder, document);
        }
    }
    else if ((0, yaml_1.isSeq)(node)) {
        for (const item of node.items) {
            if ((0, yaml_1.isNode)(item)) {
                markAllNestedKeysAsOrdinary(item, builder, document);
            }
        }
    }
}
function markKeyword(node, builder, document) {
    markNode(node, vscode_languageserver_1.SemanticTokenTypes.keyword, [], builder, document);
}
function markOrdinaryKey(node, builder, document) {
    markNode(node, vscode_languageserver_1.SemanticTokenTypes.property, [vscode_languageserver_1.SemanticTokenModifiers.definition], builder, document);
}
function markNode(node, tokenType, tokenModifiers, builder, document) {
    const range = (0, yaml_2.getOrigRange)(node);
    if (range) {
        const startPosition = document.positionAt(range[0]);
        const length = range[1] - range[0];
        builder.push(startPosition.line, startPosition.character, length, encodeTokenType(tokenType), encodeTokenModifiers(tokenModifiers));
    }
}
function encodeTokenType(tokenType) {
    const tokenTypeIndex = tokenTypesLegend.get(tokenType);
    if (tokenTypeIndex === undefined) {
        throw new Error(`The '${tokenType}' token type is not in legend`);
    }
    return tokenTypeIndex;
}
function encodeTokenModifiers(tokenModifiers) {
    let encodedModifiers = 0;
    for (const tokenModifier of tokenModifiers) {
        const tokenModifierIndex = tokenModifiersLegend.get(tokenModifier);
        if (tokenModifierIndex === undefined) {
            throw new Error(`The '${tokenModifier}' token modifier is not in legend`);
        }
        encodedModifiers |= (1 << tokenModifierIndex) >>> 0;
    }
    return encodedModifiers;
}
//# sourceMappingURL=semanticTokenProvider.js.map