const fs = require('fs-extra');
const each = require('lodash/each');
const path = require('path');
const abbrev = require('abbrev');
const createDebug = require('debug');
const Command = require('./command');
const findExtensions = require('./utils/find-extensions');

const debug = createDebug('ghost-cli:bootstrap');

// Extra alises for commands
const extraAliases = {
    '--version': 'version',
    '-v': 'version',
    busters: 'buster',
    intsall: 'install',
    insall: 'install',
    status: 'ls',
    upgrade: 'update',
    udpate: 'update',
    latest: 'check-update',
    'check-updates': 'check-update'
};

// Ensure if any promises aren't handled correctly then they get logged
process.on('unhandledRejection', (reason, promise) => {
    console.warn('A promise was rejected but did not have a .catch() handler:');
    console.warn(reason && reason.stack || reason || promise);
    throw reason;
});

const bootstrap = {
    /**
     * Discovers any commands inside of an extension folder
     * If a command name conflict occurs, commands will be namespaced according to
     * the extension name, e.g. `ghost exname:commandname`
     *
     * @param {Object} commands Current commands hash
     * @param {string} dir Directory to look for commands in
     * @param {string} extensionName Name of extension
     * @return {Object} Object hash with any discovered commands added
     */
    discoverCommands: function discoverCommands(commands, dir, extensionName) {
        const commandsDir = path.join(dir, 'commands');

        // No commands here if commands dir doesn't exist
        if (!fs.existsSync(commandsDir)) {
            return commands;
        }

        // Read the directory and find the commands
        fs.readdirSync(commandsDir).filter(
            // Don't treat non-js files as commands, but also make sure any commands that are folders
            // with index.js files in them are loaded
            command => path.extname(command) === '.js' || fs.existsSync(path.join(commandsDir, command, 'index.js'))
        ).forEach((command) => {
            const basename = path.basename(command, '.js');
            const commandName = commands[basename] ? `${extensionName}:${basename}` : basename;
            commands[commandName] = path.resolve(commandsDir, basename);
        });

        return commands;
    },

    /**
     * Configure a single command
     *
     * @param {String} commandName name of command
     * @param {String} commandPath path to command file
     * @param {Yargs} yargs Yargs instance
     * @param {Array<String>} array of string aliases for command
     * @param {Array<Object>} array of extensions
     */
    loadCommand: function loadCommand(commandName, commandPath, yargs, aliases, extensions) {
        const CommandClass = require(commandPath);

        if (!(CommandClass.prototype instanceof Command)) {
            // This should only be thrown by extensions, not internal CLI commands,
            // but needs to be here nonetheless
            console.error(`Command class for ${commandName} does not inherit from Ghost-CLI's command class.`);
            return;
        }

        // Configure the command
        CommandClass.configure(commandName, aliases, yargs, extensions);
    },

    /**
     * Runs the CLI!
     * This is where the magic happens
     *
     * @param {Array} Array of string arguments (taken from process.argv)
     * @param Yargs yargs instance
     */
    run: function run(argv, yargs) {
        debug('Starting bootstrap process');
        // Look for any available extensions, including ones built-in to the CLI itself
        const extensions = findExtensions();
        debug(`Found ${extensions.length} extensions: ${extensions.map(({pkg}) => pkg.name).join(', ')}`);

        debug('loading built-in commands');
        // Load built-in commands first
        let commands = bootstrap.discoverCommands({}, __dirname);

        debug('loading commands from extensions');
        // Then load commands from extensions
        extensions.forEach((extension) => {
            commands = bootstrap.discoverCommands(commands, extension.dir, extension.name);
        });

        debug(`discovered commands: ${Object.keys(commands).join(', ')}`);
        // Use abbrev to handle shortnened command names
        let abbreviations = abbrev(Object.keys(commands));
        // Get the first argument so we can not load all the commands at once
        const firstArg = argv.shift();

        // Apply extra aliases
        abbreviations = Object.assign(abbreviations, extraAliases);

        // Special-case `help` because we only want to configure all the commands
        // for this one case
        if (firstArg === 'help' || firstArg === '--help') {
            debug('running help command, requiring and configuring every command');

            each(commands, (commandPath, commandName) => {
                // Don't fetch aliases for help commands to keep the output clean.
                bootstrap.loadCommand(commandName, commandPath, yargs, [], extensions);
            });
            argv.unshift('help');
        } else if (abbreviations[firstArg]) {
            // Command found, instantiate/configure it and run!
            const commandName = abbreviations[firstArg];
            const commandPath = commands[commandName];
            // Get aliases of the command (so it will show up in yargs help)
            const aliases = Object.keys(abbreviations).filter(key => abbreviations[key] === commandName);
            bootstrap.loadCommand(commandName, commandPath, yargs, aliases, extensions);
            // Put the first arg back into the array
            argv.unshift(commandName);
        } else {
            // Command not found :( Error and exit
            console.error(`Unrecognized command: '${firstArg}'. Run \`ghost help\` for usage.`);
            process.exit(1);
        }

        // Yargs magic
        yargs.wrap(Math.min(150, yargs.terminalWidth()))
            .epilogue('For more information, see our docs at https://ghost.org/docs/ghost-cli/')
            .group('help', 'Global Options:')
            .option('d', {
                alias: 'dir',
                describe: 'Folder to run command in',
                group: 'Global Options:'
            }).option('D', {
                alias: 'development',
                describe: 'Run in development mode',
                type: 'boolean',
                group: 'Global Options:'
            })
            .option('V', {
                alias: 'verbose',
                describe: 'Enable verbose output',
                type: 'boolean',
                group: 'Global Options:'
            })
            .options('prompt', {
                describe: '[--no-prompt] Allow/Disallow UI prompting',
                type: 'boolean',
                default: true,
                group: 'Global Options:'
            })
            .option('color', {
                describe: '[--no-color] Allow/Disallow colorful logging',
                type: 'boolean',
                default: true,
                group: 'Global Options:'
            })
            .option('auto', {
                describe: 'Automatically run as much as possible',
                type: 'boolean',
                default: false,
                group: 'Global Options:'
            })
            .version(false)
            .parse(argv);
    }
};

module.exports = bootstrap;
