text: 'use strict';
  
  const BbPromise = require('bluebird');
  const path = require('path');
  const fse = require('fs-extra');
  const _ = require('lodash');
  const untildify = require('untildify');
  
  const ServerlessError = require('../../classes/Error').ServerlessError;
  const createFromTemplate = require('../../utils/createFromTemplate');
  const userStats = require('../../utils/userStats');
  const download = require('../../utils/downloadTemplateFromRepo');
  const renameService = require('../../utils/renameService').renameService;
  const copyDirContentsSync = require('../../utils/fs/copyDirContentsSync');
  const dirExistsSync = require('../../utils/fs/dirExistsSync');
  
  // class wide constants
  const validTemplates = [
    'aws-clojure-gradle',
    'aws-clojurescript-gradle',
    'aws-nodejs',
    'aws-nodejs-typescript',
    'aws-alexa-typescript',
    'aws-nodejs-ecma-script',
    'aws-python',
    'aws-python3',
    'aws-groovy-gradle',
    'aws-java-maven',
    'aws-java-gradle',
    'aws-kotlin-jvm-maven',
    'aws-kotlin-jvm-gradle',
    'aws-kotlin-nodejs-gradle',
    'aws-scala-sbt',
    'aws-csharp',
    'aws-fsharp',
    'aws-go',
    'aws-go-dep',
    'aws-go-mod',
    'aws-ruby',
    'aws-provided',
    'azure-nodejs',
    'cloudflare-workers',
    'cloudflare-workers-enterprise',
    'cloudflare-workers-rust',
    'fn-nodejs',
    'fn-go',
    'google-nodejs',
    'google-python',
    'google-go',
    'kubeless-python',
    'kubeless-nodejs',
    'openwhisk-java-maven',
    'openwhisk-nodejs',
    'openwhisk-php',
    'openwhisk-python',
    'openwhisk-ruby',
    'openwhisk-swift',
    'spotinst-nodejs',
    'spotinst-python',
    'spotinst-ruby',
    'spotinst-java8',
    'twilio-nodejs',
    'plugin',
  
    // this template is used to streamline the onboarding process
    // it uses the Node.js runtime and AWS provider
    'hello-world',
  ];
  
  const humanReadableTemplateList = `${validTemplates
    .slice(0, -1)
    .map(template => `"${template}"`)
    .join(', ')} and "${validTemplates.slice(-1)}"`;
  
  const handleServiceCreationError = error => {
    if (error.code !== 'EACCESS') throw error;
    const errorMessage = [
      'Error unable to create a service in this directory. ',
      'Please check that you have the required permissions to write to the directory',
    ].join('');
  
    throw new this.serverless.classes.Error(errorMessage);
  };
  
  class Create {
    constructor(serverless, options) {
      this.serverless = serverless;
      this.options = options;
  
      this.commands = {
        create: {
          usage: 'Create new Serverless service',
          lifecycleEvents: ['create'],
          options: {
            'template': {
              usage: `Template for the service. Available templates: ${humanReadableTemplateList}`,
              shortcut: 't',
            },
            'template-url': {
              usage: 'Template URL for the service. Supports: GitHub, BitBucket',
              shortcut: 'u',
            },
            'template-path': {
              usage: 'Template local path for the service.',
            },
            'path': {
              usage: 'The path where the service should be created (e.g. --path my-service)',
              shortcut: 'p',
            },
            'name': {
              usage: 'Name for the service. Overwrites the default name of the created service.',
              shortcut: 'n',
            },
          },
        },
      };
  
      this.hooks = {
        'create:create': () => BbPromise.bind(this).then(this.create),
      };
    }
  
    create() {
      this.serverless.cli.log('Generating boilerplate...');
  
      if ('template' in this.options) {
        return this.createFromTemplate();
      } else if ('template-url' in this.options) {
        return download
          .downloadTemplateFromRepo(
            this.options['template-url'],
            this.options.name,
            this.options.path
          )
          .then(serviceName => {
            const message = [
              `Successfully installed "${serviceName}" `,
              `${
                this.options.name && this.options.name !== serviceName
                  ? `as "${this.options.name}"`
                  : ''
              }`,
            ].join('');
  
            this.serverless.cli.log(message);
  
            userStats.track('service_created', {
              template: this.options.template,
              serviceName: this.options.name,
            });
          })
          .catch(err => {
            throw new this.serverless.classes.Error(err);
          });
      } else if ('template-path' in this.options) {
        // Copying template from a local directory
        const servicePath = this.options.path
          ? untildify(this.options.path)
          : path.join(process.cwd(), this.options.name);
        if (dirExistsSync(servicePath)) {
          const errorMessage = `A folder named "${servicePath}" already exists.`;
          throw new ServerlessError(errorMessage);
        }
        copyDirContentsSync(untildify(this.options['template-path']), servicePath, {
          noLinks: true,
        });
        if (this.options.name) {
          renameService(this.options.name, servicePath);
        }
      } else {
        const errorMessage = [
          'You must either pass a template name (--template), ',
          'a URL (--template-url) or a local path (--template-path).',
        ].join('');
        throw new this.serverless.classes.Error(errorMessage);
      }
      return BbPromise.resolve();
    }
  
    createFromTemplate() {
      const notPlugin = this.options.template !== 'plugin';
  
      if (validTemplates.indexOf(this.options.template) === -1) {
        const errorMessage = [
          `Template "${this.options.template}" is not supported.`,
          ` Supported templates are: ${humanReadableTemplateList}.`,
        ].join('');
        throw new this.serverless.classes.Error(errorMessage);
      }
  
      // store the custom options for the service if given
      const boilerplatePath = _.toString(this.options.path);
      const serviceName = _.toString(this.options.name);
      const templateSrcDir = path.join(
        this.serverless.config.serverlessPath,
        'plugins',
        'create',
        'templates',
        this.options.template
      );
  
      // create (if not yet present) and chdir into the directory for the service
      if (boilerplatePath) {
        const newPath = path.join(process.cwd(), boilerplatePath);
  
        if (this.serverless.utils.dirExistsSync(newPath)) {
          const errorMessage = [
            `The directory "${newPath}" already exists, and serverless will not overwrite it. `,
            'Rename or move the directory and try again if you want serverless to create it"',
          ].join('');
  
          throw new this.serverless.classes.Error(errorMessage);
        }
  
        this.serverless.cli.log(`Generating boilerplate in "${newPath}"`);
  
        fse.mkdirsSync(newPath);
        process.chdir(newPath);
      } else {
        // ensure no template file already exists in cwd that we may overwrite
        const templateFullFilePaths = this.serverless.utils.walkDirSync(templateSrcDir);
  
        templateFullFilePaths.forEach(ffp => {
          const filename = path.basename(ffp);
          if (this.serverless.utils.fileExistsSync(path.join(process.cwd(), filename))) {
            const errorMessage = [
              `The file "${filename}" already exists, and serverless will not overwrite it. `,
              `Move the file and try again if you want serverless to write a new "${filename}"`,
            ].join('');
  
            throw new this.serverless.classes.Error(errorMessage);
          }
        });
      }
  
      if (!notPlugin) {
        return BbPromise.try(() => {
          try {
            fse.copySync(
              path.join(__dirname, '../../../lib/plugins/create/templates', this.options.template),
              process.cwd()
            );
          } catch (error) {
            handleServiceCreationError(error);
          }
        });
      }
  
      this.serverless.config.update({ servicePath: process.cwd() });
  
      return createFromTemplate(this.options.template, process.cwd(), { name: serviceName }).then(
        () => {
          userStats.track('service_created', {
            template: this.options.template,
            serviceName,
          });
  
          this.serverless.cli.asciiGreeting();
          this.serverless.cli.log(
            `Successfully generated boilerplate for template: "${this.options.template}"`
          );
  
          if (!(boilerplatePath || serviceName) && notPlugin) {
            this.serverless.cli.log(
              'NOTE: Please update the "service" property in serverless.yml with your service name'
            );
          }
        },
        handleServiceCreationError
      );
    }
  }
  
  module.exports = Create;
  