"use strict";
// tslint:disable interface-over-type-literal
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const deps_1 = tslib_1.__importDefault(require("./deps"));
const m = deps_1.default()
    .add('errors', () => require('./errors'))
    .add('util', () => require('./util'));
let debug;
try {
    if (process.env.CLI_FLAGS_DEBUG !== '1')
        debug = () => { };
    else
        // tslint:disable-next-line no-implicit-dependencies
        debug = require('debug')('@oclif/parser');
}
catch (_a) {
    debug = () => { };
}
class Parser {
    constructor(input) {
        this.input = input;
        this.raw = [];
        const { pickBy } = m.util;
        this.context = input.context || {};
        this.argv = input.argv.slice(0);
        this._setNames();
        this.booleanFlags = pickBy(input.flags, f => f.type === 'boolean');
    }
    parse() {
        this._debugInput();
        const findLongFlag = (arg) => {
            const name = arg.slice(2);
            if (this.input.flags[name]) {
                return name;
            }
            if (arg.startsWith('--no-')) {
                const flag = this.booleanFlags[arg.slice(5)];
                if (flag && flag.allowNo)
                    return flag.name;
            }
        };
        const findShortFlag = (arg) => {
            return Object.keys(this.input.flags).find(k => this.input.flags[k].char === arg[1]);
        };
        const parseFlag = (arg) => {
            const long = arg.startsWith('--');
            const name = long ? findLongFlag(arg) : findShortFlag(arg);
            if (!name) {
                const i = arg.indexOf('=');
                if (i !== -1) {
                    const sliced = arg.slice(i + 1);
                    this.argv.unshift(sliced);
                    const equalsParsed = parseFlag(arg.slice(0, i));
                    if (!equalsParsed) {
                        this.argv.shift();
                    }
                    return equalsParsed;
                }
                return false;
            }
            const flag = this.input.flags[name];
            if (flag.type === 'option') {
                this.currentFlag = flag;
                let input;
                if (long || arg.length < 3) {
                    input = this.argv.shift();
                }
                else {
                    input = arg.slice(arg[2] === '=' ? 3 : 2);
                }
                if (typeof input !== 'string') {
                    throw new m.errors.CLIError(`Flag --${name} expects a value`);
                }
                this.raw.push({ type: 'flag', flag: flag.name, input });
            }
            else {
                this.raw.push({ type: 'flag', flag: flag.name, input: arg });
                // push the rest of the short characters back on the stack
                if (!long && arg.length > 2) {
                    this.argv.unshift(`-${arg.slice(2)}`);
                }
            }
            return true;
        };
        let parsingFlags = true;
        while (this.argv.length) {
            const input = this.argv.shift();
            if (parsingFlags && input.startsWith('-')) {
                // attempt to parse as arg
                if (this.input['--'] !== false && input === '--') {
                    parsingFlags = false;
                    continue;
                }
                if (parseFlag(input)) {
                    continue;
                }
                // not actually a flag if it reaches here so parse as an arg
            }
            if (parsingFlags && this.currentFlag && this.currentFlag.multiple) {
                this.raw.push({ type: 'flag', flag: this.currentFlag.name, input });
                continue;
            }
            // not a flag, parse as arg
            const arg = this.input.args[this._argTokens.length];
            if (arg)
                arg.input = input;
            this.raw.push({ type: 'arg', input });
        }
        const argv = this._argv();
        const args = this._args(argv);
        const flags = this._flags();
        this._debugOutput(argv, args, flags);
        return {
            args,
            argv,
            flags,
            raw: this.raw,
        };
    }
    _args(argv) {
        const args = {};
        for (let i = 0; i < this.input.args.length; i++) {
            const arg = this.input.args[i];
            args[arg.name] = argv[i];
        }
        return args;
    }
    _flags() {
        const flags = {};
        for (const token of this._flagTokens) {
            const flag = this.input.flags[token.flag];
            if (!flag)
                throw new m.errors.CLIError(`Unexpected flag ${token.flag}`);
            if (flag.type === 'boolean') {
                if (token.input === `--no-${flag.name}`) {
                    flags[token.flag] = false;
                }
                else {
                    flags[token.flag] = true;
                }
                flags[token.flag] = flag.parse(flags[token.flag], this.context);
            }
            else {
                const input = token.input;
                if (flag.options && !flag.options.includes(input)) {
                    throw new m.errors.FlagInvalidOptionError(flag, input);
                }
                const value = flag.parse ? flag.parse(input, this.context) : input;
                if (flag.multiple) {
                    flags[token.flag] = flags[token.flag] || [];
                    flags[token.flag].push(value);
                }
                else {
                    flags[token.flag] = value;
                }
            }
        }
        for (const k of Object.keys(this.input.flags)) {
            const flag = this.input.flags[k];
            if (flags[k])
                continue;
            if (flag.type === 'option' && flag.env) {
                let input = process.env[flag.env];
                if (input)
                    flags[k] = flag.parse(input, this.context);
            }
            if (!(k in flags) && flag.default !== undefined) {
                if (typeof flag.default === 'function') {
                    flags[k] = flag.default(Object.assign({ options: flag, flags }, this.context));
                }
                else {
                    flags[k] = flag.default;
                }
            }
        }
        return flags;
    }
    _argv() {
        const args = [];
        const tokens = this._argTokens;
        for (let i = 0; i < Math.max(this.input.args.length, tokens.length); i++) {
            const token = tokens[i];
            const arg = this.input.args[i];
            if (token) {
                if (arg) {
                    if (arg.options && !arg.options.includes(token.input)) {
                        throw new m.errors.ArgInvalidOptionError(arg, token.input);
                    }
                    args[i] = arg.parse(token.input);
                }
                else {
                    args[i] = token.input;
                }
            }
            else {
                if ('default' in arg) {
                    if (typeof arg.default === 'function') {
                        args[i] = arg.default();
                    }
                    else {
                        args[i] = arg.default;
                    }
                }
            }
        }
        return args;
    }
    _debugOutput(args, flags, argv) {
        if (argv.length) {
            debug('argv: %o', argv);
        }
        if (Object.keys(args).length) {
            debug('args: %o', args);
        }
        if (Object.keys(flags).length) {
            debug('flags: %o', flags);
        }
    }
    _debugInput() {
        debug('input: %s', this.argv.join(' '));
        if (this.input.args.length) {
            debug('available args: %s', this.input.args.map(a => a.name).join(' '));
        }
        if (!Object.keys(this.input.flags).length)
            return;
        debug('available flags: %s', Object.keys(this.input.flags)
            .map(f => `--${f}`)
            .join(' '));
    }
    get _argTokens() {
        return this.raw.filter(o => o.type === 'arg');
    }
    get _flagTokens() {
        return this.raw.filter(o => o.type === 'flag');
    }
    _setNames() {
        for (const k of Object.keys(this.input.flags)) {
            this.input.flags[k].name = k;
        }
    }
}
exports.Parser = Parser;
