/**
 * Global options that may be used with every command
 */
export const globalOptions = {
    help: {
        keys: ['--help', '-h'],
        type: 'boolean',
        mandatory: false,
        multiple: false,
        helpLiteral: '',
        helpText: 'Print general CLI or command specific help',
        index: 0
    },
    verbose: {
        keys: ['--verbose', '-v'],
        type: 'boolean',
        mandatory: false,
        multiple: false,
        helpLiteral: '',
        helpText: 'Add tracing information to the output',
        index: 1
    },
    format: {
        keys: ['--output', '-o'],
        type: 'string',
        mandatory: false,
        multiple: false,
        helpLiteral: 'FORMAT',
        helpText: 'Output format. FORMAT is one of json|yaml',
        index: 2
    },
    root: {
        keys: ['--root'],
        type: 'boolean',
        mandatory: false,
        multiple: false,
        helpLiteral: '',
        helpText: 'Allow CLI to run with root user privileges',
        index: 3
    },
};

/**
 * Shell completion options
 */
export const completionOptions = {
    getCompletions: {
        keys: ['--get-completions'],
        type: 'boolean',
        stopParsing: true
    }
};

/**
 * Parse command line arguments, based on command
 * metadata options.
 *
 * @example
 * parse(
 *     ['run', '--arg0', 'val0', '-x', 'pos'],
 *     {
 *         cmd: { type: 'string', keys: [], index: 0 },
 *         arg0: { type: 'string', keys: ['--arg0', '-a'], multiple: true},
 *         x: { type: 'boolean', keys: ['-x'] }
 *     }
 * );
 * // returns {"cmd": "run", "arg0": ["val0"], "x": true, "_": ["pos"]}
 *
 * @param {Array.<string>} [args] - command line arguments
 * @param {Object} opts - command metadata options
 * @returns {Object} map of parsed arguments
 */
export const parse = (args = [], opts = {}) => {
    opts = Object.assign({}, globalOptions, opts);
    const index = {};
    const positional = [];
    for (const [key, val] of Object.entries(opts)) {
        if (val.keys?.length) {
            for (const k of val.keys) {
                index[k] = key;
            }
        } else {
            positional.push(key);
        }
    }
    positional.sort((a, b) => {
        const ai = opts[a].index ?? 0;
        const bi = opts[b].index ?? 0;
        if (ai < bi) {
            return -1;
        } else if (ai > bi) {
            return 1;
        }
        return 0;
    });
    return args.reduce((acc, cur) => {
        if (opts[acc.key]?.stopParsing) {
            acc.args['_'].push(cur);
        } else if (cur.startsWith('-')) {
            if (index[cur]) {
                const opt = opts[index[cur]];
                if (opt.multiple && !Array.isArray(acc.args[index[cur]])) {
                    acc.args[index[cur]] = [];
                }
                if (opt.type === 'boolean') {
                    if (opt.multiple) {
                        acc.args[index[cur]].push(true);
                    } else {
                        acc.args[index[cur]] = true;
                    }
                    if (opts[index[cur]]?.stopParsing) {
                        acc.key = index[cur];
                    } else {
                        acc.key = '_';
                    }
                } else if (opts[index[cur]].type === 'string') {
                    acc.key = index[cur];
                }
            } else {
                acc.key = cur;
                acc.args[cur] = [];
            }
        } else if ((acc.key === '_') && (positional.length > 0)) {
            const name = positional[0];
            if (opts[name].multiple) {
                if (!Array.isArray(acc.args[name])) {
                    acc.args[name] = [];
                }
                acc.args[name].push(cur);
            } else {
                acc.args[name] = cur;
                positional.shift();
            }
        } else {
            const opt = opts[acc.key] ?? { multiple: true };
            if (opt.multiple) {
                acc.args[acc.key].push(cur);
            } else {
                acc.args[acc.key] = cur;
            }
            acc.key = '_';
        }
        return acc;
    }, {
        args: { _: [] },
        key: '_'
    }).args;
};
