'use strict';

const _ = require('lodash');
const chalk = require('chalk');
const chokidar = require('chokidar');
const minimist = require('minimist');
const Vorpal = require('vorpal');
const Console = require('./console');
const Notifier = require('./notifier');
const requireAll = require('require-all');
const Log = require('../core/log');
const mix = require('../core/mixins/mix');
const Configurable = require('../core/mixins/configurable');
const Emitter = require('../core/mixins/emitter');
const utils = require('../core/utils');

class Cli extends mix(Configurable, Emitter) {

    constructor(app) {
        super(app);
        this.config(app.get('cli'));

        this._app = app;
        this._commands = new Set();
        this._vorpal = new Vorpal();
        this._defaultsLoaded = false;
        this._interactive = false;
        this._configPath = null;
        this._scope = 'project';
        this._cliPackage = {};
        this._env = {};
        this._commandsDir = `${__dirname}/commands`;

        this.console = new Console(this._vorpal);
        this.console.debugMode(app.debug);

        this.notify = new Notifier(this.console, this._interactive);

        for (const method of ['log', 'error', 'warn', 'debug', 'success']) {
            this[method] = function () {
                this.console[method].apply(this.console, Array.from(arguments));
            };
            Log.on(method, (msg, data) => this[method](msg, data));
        }
    }

    has(command) {
        return !! this._vorpal.find(command);
    }

    get(command) {
        return this._vorpal.find(command);
    }

    isInteractive() {
        return this._interactive;
    }

    command(command, action, config) {
        const console = this.console;
        const vorpal = this._vorpal;
        const app = this._app;

        action = action || function () {};
        config = config || {};
        if (_.isString(config)) {
            config = {
                description: config,
            };
        }

        const commandScope = config.scope ? [].concat(config.scope) : ['project'];

        if (!_.includes(commandScope, this._scope)) {
            // command not available in this scope
            const cmd = vorpal.command(command.replace(/\</g, '[').replace(/\>/g, ']'), config.description || ' ');
            cmd.action((args, done) => {
                console.error('No Fractal CLI configuration file found. Are you running this from the root directory of your project?');
                done();
            }).hidden().__scope = commandScope;
            cmd.action = undefined; // prevent this from being overridden now it is bound
            return;
        }

        const cmd = this._vorpal.command(command, config.description || ' ');

        cmd.action(function (args, done) {
            this.console = console;
            this.fractal = app;
            return action.bind(this)(args, done);
        });
        cmd.action = undefined; // prevent this from being overridden now it is bound

        (config.options || []).forEach(opt => {
            opt = _.castArray(opt);
            cmd.option.apply(cmd, opt);
        });
        if (config.hidden) {
            cmd.hidden();
        }
        if (config.alias) {
            cmd.alias(config.alias);
        }
        cmd.__scope = commandScope;
        return cmd;
    }

    exec() {
        _.forEach(requireAll(this._commandsDir), c => this.command(c.command, c.action, c.config || {}));
        return arguments.length ? this._execFromString.apply(this, Array.from(arguments)) : this._execFromArgv();
    }

    theme(theme) {
        if (_.isString(theme)) {
            theme = require(theme);
        }
        this.console.theme = theme;
        return this;
    }

    init(scope, configPath, env, cliPackage) {
        this._scope = scope;
        this._configPath = configPath;
        this._env = env;
        this._cliPackage = cliPackage;
        return this;
    }

    get scope() {
        return this._scope;
    }

    get configPath() {
        return this._configPath;
    }

    get env() {
        return this._env;
    }

    get cliPackage() {
        return this._cliPackage;
    }

    /**
     * Run a command specified by string
     * @param  {String} command The command line string to process
     * @param  {Function} onStdout Output handler
     * @return {Promise}
     */

    _execFromString(command, onStdout) {
        const vorpal = this._vorpal;
        const app = this._app;

        if (typeof onStdout === 'Function') {
            vorpal.pipe(function (output) {
                if (output) {
                    output = output[0];
                    const ret = onStdout(output);
                    if (ret) {
                        return ret;
                    }
                    return '';
                }
            });
        }

        return app.load().then(function () {
            return vorpal.execSync(command);
        });
    }

    /**
     * Run a command by parsing argv
     * @return {Promise}
     */

    _execFromArgv() {
        const input = utils.parseArgv();
        const console = this.console;
        const vorpal = this._vorpal;
        const app = this._app;

        if (input.command) {
            // non-interactive mode
            //
            if (this._scope === 'global') {
                vorpal.parse(process.argv);
                return;
            }

            return app.load().then(() => {
                vorpal.parse(process.argv);
            });
        } else {
            // interactive mode

            if (input.command && ! vorpal.find(input.command)) {
                console.error(`The ${input.command} command is not recognised.`);
                return;
            }

            if (this._scope == 'project') {
                this._interactive = true;

                console.slog().log('Initialising Fractal....');

                return app.load().then(() => {
                    app.watch();
                    this._watchConfigFile();

                    vorpal.delimiter(console.theme.delimiter());
                    vorpal.history('fractal');

                    console.box(
                        'Fractal interactive CLI',
                        `- Use the ${chalk.magenta('help')} command to see all available commands.\n- Use the ${chalk.magenta('exit')} command to exit the app.`,
                        `Powered by Fractal v${app.version}`
                    ).unslog().br();

                    return vorpal.show();
                });
            } else {
                console.box(
                    'Fractal CLI',
                    `No local Fractal configuration found.
You can use the ${chalk.magenta('fractal new')} command to create a new project.`,
                    `Powered by Fractal v${app.version}`
                ).unslog();

                return;
            }
        }
    }

    _watchConfigFile() {
        if (this._scope === 'project' && this._configPath) {
            const monitor = chokidar.watch(this._configPath);
            monitor.on('change', path => {
                this.warn('Your configuration file has changed.');
                this.warn('Exit & restart the current process to see your changes take effect.');
                monitor.close();
            });
        }
    }

}

module.exports = Cli;

@@ -5313,16 +5313,17 @@
 ve mode%0A
+%0A
         
@@ -5326,18 +5326,89 @@
         
-//
+vorpal.ui.attach = () =%3E %7B%7D; // fix for vorpal bug in 1.11.4%0A            
 %0A       

Put in temporary fix for .parse bug in vorpal 1.11.4