"use strict";
/*
  Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
  SPDX-FileCopyrightText: Ansible Project
  SPDX-License-Identifier: BSD-2-Clause
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = exports.parseString = exports.composeCommandRE = exports.composeCommandMap = void 0;
const ansible_1 = require("./ansible");
const parser_impl_1 = require("./parser-impl");
const dom_1 = require("./dom");
const IGNORE_MARKER = 'ignore:';
function parseOptionLike(text, opts, type, source) {
    let value;
    const eq = text.indexOf('=');
    if (eq >= 0) {
        value = text.substring(eq + 1, text.length);
        text = text.substring(0, eq);
    }
    const m = /^([^.]+\.[^.]+\.[^#]+)#([^:]+):(.*)$/.exec(text);
    let plugin;
    let entrypoint;
    if (m) {
        const pluginFqcn = m[1];
        const pluginType = m[2];
        if (!(0, ansible_1.isFQCN)(pluginFqcn)) {
            throw Error(`Plugin name "${pluginFqcn}" is not a FQCN`);
        }
        if (!(0, ansible_1.isPluginType)(pluginType)) {
            throw Error(`Plugin type "${pluginType}" is not valid`);
        }
        plugin = { fqcn: pluginFqcn, type: pluginType };
        text = m[3];
    }
    else if (text.startsWith(IGNORE_MARKER)) {
        plugin = undefined;
        text = text.substring(IGNORE_MARKER.length, text.length);
    }
    else {
        plugin = opts.currentPlugin;
        entrypoint = opts.roleEntrypoint;
    }
    if ((plugin === null || plugin === void 0 ? void 0 : plugin.type) === 'role') {
        const idx = text.indexOf(':');
        if (idx >= 0) {
            entrypoint = text.substr(0, idx);
            text = text.substr(idx + 1);
        }
        if (entrypoint === undefined) {
            throw Error('Role reference is missing entrypoint');
        }
    }
    if (/[:#]/.test(text)) {
        throw Error(`Invalid option/return value name "${text}"`);
    }
    return {
        type: type,
        plugin: plugin,
        entrypoint: entrypoint,
        link: text.replace(/\[([^\]]*)\]/g, '').split('.'),
        name: text,
        value: value,
        source: source,
    };
}
const PARSER = [
    // Classic Ansible docs markup:
    {
        command: 'I',
        parameters: 1,
        old_markup: true,
        process: (args, _, source) => {
            const text = args[0];
            return { type: dom_1.PartType.ITALIC, text: text, source: source };
        },
    },
    {
        command: 'B',
        parameters: 1,
        old_markup: true,
        process: (args, _, source) => {
            const text = args[0];
            return { type: dom_1.PartType.BOLD, text: text, source: source };
        },
    },
    {
        command: 'M',
        parameters: 1,
        old_markup: true,
        process: (args, _, source) => {
            const fqcn = args[0];
            if (!(0, ansible_1.isFQCN)(fqcn)) {
                throw Error(`Module name "${fqcn}" is not a FQCN`);
            }
            return { type: dom_1.PartType.MODULE, fqcn: fqcn, source: source };
        },
    },
    {
        command: 'U',
        parameters: 1,
        old_markup: true,
        process: (args, _, source) => {
            const url = args[0];
            return { type: dom_1.PartType.URL, url: url, source: source };
        },
    },
    {
        command: 'L',
        parameters: 2,
        old_markup: true,
        process: (args, _, source) => {
            const text = args[0];
            const url = args[1];
            return { type: dom_1.PartType.LINK, text: text, url: url, source: source };
        },
    },
    {
        command: 'R',
        parameters: 2,
        old_markup: true,
        process: (args, _, source) => {
            const text = args[0];
            const ref = args[1];
            return { type: dom_1.PartType.RST_REF, text: text, ref: ref, source: source };
        },
    },
    {
        command: 'C',
        parameters: 1,
        old_markup: true,
        process: (args, _, source) => {
            const text = args[0];
            return { type: dom_1.PartType.CODE, text: text, source: source };
        },
    },
    {
        command: 'HORIZONTALLINE',
        parameters: 0,
        old_markup: true,
        process: (_, __, source) => {
            return { type: dom_1.PartType.HORIZONTAL_LINE, source: source };
        },
    },
    // Semantic Ansible docs markup:
    {
        command: 'P',
        parameters: 1,
        escapedArguments: true,
        process: (args, _, source) => {
            const m = /^([^#]*)#(.*)$/.exec(args[0]);
            if (!m) {
                throw Error(`Parameter "${args[0]}" is not of the form FQCN#type`);
            }
            const fqcn = m[1];
            if (!(0, ansible_1.isFQCN)(fqcn)) {
                throw Error(`Plugin name "${fqcn}" is not a FQCN`);
            }
            const type = m[2];
            if (!(0, ansible_1.isPluginType)(type)) {
                throw Error(`Plugin type "${type}" is not valid`);
            }
            return { type: dom_1.PartType.PLUGIN, plugin: { fqcn: fqcn, type: type }, source: source };
        },
    },
    {
        command: 'E',
        parameters: 1,
        escapedArguments: true,
        process: (args, _, source) => {
            const env = args[0];
            return { type: dom_1.PartType.ENV_VARIABLE, name: env, source: source };
        },
    },
    {
        command: 'V',
        parameters: 1,
        escapedArguments: true,
        process: (args, _, source) => {
            const value = args[0];
            return { type: dom_1.PartType.OPTION_VALUE, value: value, source: source };
        },
    },
    {
        command: 'O',
        parameters: 1,
        escapedArguments: true,
        process: (args, opts, source) => {
            const value = args[0];
            return parseOptionLike(value, opts, dom_1.PartType.OPTION_NAME, source);
        },
    },
    {
        command: 'RV',
        parameters: 1,
        escapedArguments: true,
        process: (args, opts, source) => {
            const value = args[0];
            return parseOptionLike(value, opts, dom_1.PartType.RETURN_VALUE, source);
        },
    },
];
function composeCommandMap(commands) {
    const result = new Map();
    commands.forEach((cmd) => result.set(cmd.command, cmd));
    return result;
}
exports.composeCommandMap = composeCommandMap;
function commandRE(command) {
    return '\\b' + command.command + (command.parameters === 0 ? '\\b' : '\\(');
}
function composeCommandRE(commands) {
    return new RegExp('(' + commands.map(commandRE).join('|') + ')', 'g');
}
exports.composeCommandRE = composeCommandRE;
const PARSER_COMMANDS = composeCommandMap(PARSER);
const COMMAND_RE = composeCommandRE(PARSER);
const CLASSIC_COMMAND_RE = composeCommandRE(PARSER.filter((cmd) => cmd.old_markup));
function parseString(input, commandRE, commands, opts, where) {
    var _a;
    const result = [];
    const length = input.length;
    let index = 0;
    while (index < length) {
        commandRE.lastIndex = index;
        const match = commandRE.exec(input);
        if (!match) {
            if (index < length) {
                const text = input.slice(index);
                result.push({
                    type: dom_1.PartType.TEXT,
                    text: text,
                    source: opts.addSource ? text : undefined,
                });
            }
            break;
        }
        if (match.index > index) {
            const text = input.slice(index, match.index);
            result.push({
                type: dom_1.PartType.TEXT,
                text: text,
                source: opts.addSource ? text : undefined,
            });
        }
        index = match.index;
        let cmd = match[0];
        let endIndex = index + cmd.length;
        if (cmd.endsWith('(')) {
            cmd = cmd.slice(0, cmd.length - 1);
        }
        const command = commands.get(cmd);
        if (!command) {
            throw Error(`Internal error: unknown command "${cmd}"`);
        }
        let args;
        let error;
        if (command.parameters === 0) {
            args = [];
        }
        else if (command.escapedArguments) {
            [args, endIndex, error] = (0, parser_impl_1.parseEscapedArgs)(input, endIndex, command.parameters);
        }
        else {
            [args, endIndex, error] = (0, parser_impl_1.parseUnescapedArgs)(input, endIndex, command.parameters);
        }
        const source = opts.addSource ? input.slice(index, endIndex) : undefined;
        if (error === undefined) {
            try {
                result.push(command.process(args, opts, source));
                /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
            }
            catch (exc) {
                error = `${exc}`;
                if ((exc === null || exc === void 0 ? void 0 : exc.message) !== undefined) {
                    error = `${exc.message}`;
                }
            }
        }
        if (error !== undefined) {
            const errorSource = ((_a = opts.helpfulErrors) !== null && _a !== void 0 ? _a : true)
                ? `"${input.slice(index, endIndex)}"`
                : `${cmd}${command.parameters > 0 ? '()' : ''}`;
            error = `While parsing ${errorSource} at index ${match.index + 1}${where}: ${error}`;
            switch (opts.errors || 'message') {
                case 'ignore':
                    break;
                case 'message':
                    result.push({
                        type: dom_1.PartType.ERROR,
                        message: error,
                        source: source,
                    });
                    break;
                case 'exception':
                    throw Error(error);
            }
        }
        index = endIndex;
    }
    return result;
}
exports.parseString = parseString;
/**
  Parses a string or a list of strings to a list of paragraphs.
 */
function parse(input, opts) {
    let hasParagraphs = true;
    if (!Array.isArray(input)) {
        input = input ? [input] : [];
        hasParagraphs = false;
    }
    const opts_ = opts || {};
    const commandRE = opts_.onlyClassicMarkup ? CLASSIC_COMMAND_RE : COMMAND_RE;
    return input.map((par, index) => parseString('' + par, commandRE, PARSER_COMMANDS, opts_, hasParagraphs ? ` of paragraph ${index + 1}` : ''));
}
exports.parse = parse;
//# sourceMappingURL=parser.js.map