"use strict";
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]));
async function doSemanticTokens(document, docsLibrary) {
    const builder = new vscode_languageserver_1.SemanticTokensBuilder();
    const yDocuments = (0, yaml_2.parseAllDocuments)(document.getText());
    for (const yDoc of yDocuments) {
        if (yDoc.contents) {
            await markSemanticTokens([yDoc.contents], builder, document, docsLibrary);
        }
    }
    return builder.build();
}
exports.doSemanticTokens = doSemanticTokens;
async function markSemanticTokens(path, builder, document, docsLibrary) {
    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(String(pair.key.value)))
                        markKeyword(pair.key, builder, document);
                    else
                        markOrdinaryKey(pair.key, builder, document);
                }
                else if ((0, yaml_2.isBlockParam)(keyPath)) {
                    if (ansible_1.blockKeywords.has(String(pair.key.value)))
                        markKeyword(pair.key, builder, document);
                    else
                        markOrdinaryKey(pair.key, builder, document);
                }
                else if ((0, yaml_2.isRoleParam)(keyPath)) {
                    if (ansible_1.roleKeywords.has(String(pair.key.value)))
                        markKeyword(pair.key, builder, document);
                    else
                        markOrdinaryKey(pair.key, builder, document);
                }
                else if ((0, yaml_2.isTaskParam)(keyPath)) {
                    if ((0, ansible_1.isTaskKeyword)(String(pair.key.value))) {
                        markKeyword(pair.key, builder, document);
                        if (pair.key.value === "args") {
                            const module = await (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, module.documentation?.options, builder, document);
                            }
                        }
                    }
                    else {
                        const [module] = await docsLibrary.findModule(String(pair.key.value), 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, module.documentation?.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)) {
                await 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
                await 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?.get(String(moduleParamPair.key.value));
            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;
}
