/* @flow */
"use strict";
const dynamicRequire = require("./dynamicRequire");
const getModulePath = require("./utils/getModulePath");
const getStdin = require("get-stdin");
const meow = require("meow");
const needlessDisablesStringFormatter = require("./formatters/needlessDisablesStringFormatter");
const path = require("path");
const resolveFrom = require("resolve-from");
const standalone = require("./standalone");

/*:: type meowOptionsType = {
  autoHelp: boolean,
  autoVersion: boolean,
  help: string,
  flags: {
    "allow-empty-input": {
      alias: string,
      type: string
    },
    cache: {
      type: string
    },
    "cache-location": {
      type: string
    },
    config: {
      default: boolean,
      type: string
    },
    "config-basedir": {
      type: string
    },
    color: {
      type: string
    },
    "custom-formatter": {
      type: string
    },
    "custom-syntax": {
      type: string
    },
    "disable-default-ignores": {
      alias: string,
      type: string
    },
    fix: {
      type: string
    },
    formatter: {
      alias: string,
      default: "string",
      type: string
    },
    help: {
      alias: string,
      type: string
    },
    "ignore-disables": {
      alias: string,
      type: string
    },
    "ignore-path": {
      alias: string
    },
    "ignore-pattern": {
      alias: string
    },
    "no-color": {
      type: string
    },
    "report-needless-disables": {
      alias: string
    },
    "stdin-filename": {
      type: string
    },
    quiet: {
      alias: string,
      type: string,
      default: boolean
    },
    syntax: {
      alias: string
    },
    version: {
      alias: string,
      type: string
    }
  },
  pkg: string,
} */

/*:: type cliType = {
  flags: {
    allowEmptyInput: any,
    cache: any,
    cacheLocation: any,
    config: any,
    configBasedir: any,
    customFormatter: any,
    customSyntax: any,
    fix: any,
    formatter: any,
    ignoreDisables: any,
    ignorePath: string,
    quiet: any,
    reportNeedlessDisables: any,
    stdinFilename: any,
    syntax: any,
  },
  input: any,
  help: any,
  pkg: any,
  showHelp: Function,
  showVersion: Function
}*/

/*:: type optionBaseType = {
  allowEmptyInput?: any,
  formater?: any,
  cache?: boolean,
  cacheLocation?: any,
  codeFilename?: any,
  configBasedir?: any,
  configFile?: any,
  configOverrides: {
    quiet?: any,
  },
  customSyntax?: any,
  fix?: any,
  ignoreDisables?: any,
  ignorePath?: any,
  reportNeedlessDisables?: any,
  syntax?: any,
  disableDefaultIgnores?: any,
  ignorePattern?: any
}*/

const meowOptions /*: meowOptionsType*/ = {
  autoHelp: false,
  autoVersion: false,
  help: `
    Usage: stylelint [input] [options]

    Input: Files(s), glob(s), or nothing to use stdin.

      If an input argument is wrapped in quotation marks, it will be passed to
      node-glob for cross-platform glob support. node_modules and
      bower_components are always ignored. You can also pass no input and use
      stdin, instead.

    Options:

      --config

        Path to a specific configuration file (JSON, YAML, or CommonJS), or the
        name of a module in node_modules that points to one. If no --config
        argument is provided, stylelint will search for configuration files in
        the following places, in this order:
          - a stylelint property in package.json
          - a .stylelintrc file (with or without filename extension:
            .json, .yaml, .yml, and .js are available)
          - a stylelint.config.js file exporting a JS object
        The search will begin in the working directory and move up the directory
        tree until a configuration file is found.

      --config-basedir

        An absolute path to the directory that relative paths defining "extends"
        and "plugins" are *relative to*. Only necessary if these values are
        relative paths.

      --ignore-path, -i

        Path to a file containing patterns that describe files to ignore. The
        path can be absolute or relative to process.cwd(). By default, stylelint
        looks for .stylelintignore in process.cwd().

      --ignore-pattern, -ip

        Pattern of files to ignore (in addition to those in .stylelintignore)

      --syntax, -s

        Specify a non-standard syntax. Options: "scss", "less", "sugarss".
        If you do not specify a syntax, non-standard syntaxes will be
        automatically inferred by the file extensions .scss, .less, and .sss.

      --fix

        Automatically fix violations of certain rules.

      --custom-syntax

        Module name or path to a JS file exporting a PostCSS-compatible syntax.

      --stdin-filename

        A filename to assign stdin input.

      --ignore-disables, --id

        Ignore styleline-disable comments.

      --disable-default-ignores, --di

        Allow linting of node_modules and bower_components.

      --cache                       [default: false]

        Store the info about processed files in order to only operate on the
        changed ones the next time you run stylelint. By default, the cache
        is stored in "./.stylelintcache". To adjust this, use --cache-location.

      --cache-location              [default: '.stylelintcache']

        Path to a file or directory to be used for the cache location.
        Default is "./.stylelintcache". If a directory is specified, a cache
        file will be created inside the specified folder, with a name derived
        from a hash of the current working directory.

        If the directory for the cache does not exist, make sure you add a trailing "/"
        on *nix systems or "\\" on Windows. Otherwise the path will be assumed to be a file.

      --formatter, -f               [default: "string"]

        The output formatter: "json", "string" or "verbose".

      --custom-formatter

        Path to a JS file exporting a custom formatting function.

      --quiet, -q

        Only register warnings for rules with an "error"-level severity (ignore
        "warning"-level).

      --color
      --no-color

        Force enabling/disabling of color.

      --allow-empty-input, --aei

        If no files match glob pattern, exits without throwing an error.

      --report-needless-disables, --rd

        Report stylelint-disable comments that are not blocking a lint warning.
        If you provide the argument "error", the process will exit with code 2
        if needless disables are found.

      --version, -v

        Show the currently installed version of stylelint.
  `,
  flags: {
    "allow-empty-input": {
      alias: "aei",
      type: "boolean"
    },
    cache: {
      type: "boolean"
    },
    "cache-location": {
      type: "string"
    },
    config: {
      default: false,
      type: "string"
    },
    "config-basedir": {
      type: "string"
    },
    color: {
      type: "boolean"
    },
    "custom-formatter": {
      type: "string"
    },
    "custom-syntax": {
      type: "string"
    },
    "disable-default-ignores": {
      alias: "di",
      type: "boolean"
    },
    fix: {
      type: "boolean"
    },
    formatter: {
      alias: "f",
      default: "string",
      type: "string"
    },
    help: {
      alias: "h",
      type: "boolean"
    },
    "ignore-disables": {
      alias: "id",
      type: "boolean"
    },
    "ignore-path": {
      alias: "i"
    },
    "ignore-pattern": {
      alias: "ip"
    },
    "no-color": {
      type: "boolean"
    },
    "report-needless-disables": {
      alias: "rd"
    },
    "stdin-filename": {
      type: "string"
    },
    quiet: {
      alias: "q",
      type: "boolean",
      default: false
    },
    syntax: {
      alias: "s"
    },
    version: {
      alias: "v",
      type: "boolean"
    }
  },
  pkg: require("../package.json")
};

const cli /*: cliType*/ = meow(meowOptions);

let formatter = cli.flags.formatter;
if (cli.flags.customFormatter) {
  const customFormatter = path.isAbsolute(cli.flags.customFormatter)
    ? cli.flags.customFormatter
    : path.join(process.cwd(), cli.flags.customFormatter);
  formatter = dynamicRequire(customFormatter);
}

const optionsBase /*: optionBaseType*/ = {
  formatter,
  configOverrides: {}
};

if (cli.flags.quiet) {
  optionsBase.configOverrides.quiet = cli.flags.quiet;
}

if (cli.flags.syntax) {
  optionsBase.syntax = cli.flags.syntax;
}

if (cli.flags.customSyntax) {
  optionsBase.customSyntax = getModulePath(
    process.cwd(),
    cli.flags.customSyntax
  );
}

if (cli.flags.config) {
  // Should check these possibilities:
  //   a. name of a node_module
  //   b. absolute path
  //   c. relative path relative to `process.cwd()`.
  // If none of the above work, we'll try a relative path starting
  // in `process.cwd()`.
  optionsBase.configFile =
    resolveFrom.silent(process.cwd(), cli.flags.config) ||
    path.join(process.cwd(), cli.flags.config);
}

if (cli.flags.configBasedir) {
  optionsBase.configBasedir = path.isAbsolute(cli.flags.configBasedir)
    ? cli.flags.configBasedir
    : path.resolve(process.cwd(), cli.flags.configBasedir);
}

if (cli.flags.stdinFilename) {
  optionsBase.codeFilename = cli.flags.stdinFilename;
}

if (cli.flags.ignorePath) {
  optionsBase.ignorePath = cli.flags.ignorePath;
}

if (cli.flags.ignorePattern) {
  optionsBase.ignorePattern = cli.flags.ignorePattern;
}

if (cli.flags.ignoreDisables) {
  optionsBase.ignoreDisables = cli.flags.ignoreDisables;
}

if (cli.flags.disableDefaultIgnores) {
  optionsBase.disableDefaultIgnores = cli.flags.disableDefaultIgnores;
}

if (cli.flags.cache) {
  optionsBase.cache = true;
}

if (cli.flags.cacheLocation) {
  optionsBase.cacheLocation = cli.flags.cacheLocation;
}

if (cli.flags.fix) {
  optionsBase.fix = cli.flags.fix;
}

const reportNeedlessDisables = cli.flags.reportNeedlessDisables;

if (reportNeedlessDisables) {
  optionsBase.reportNeedlessDisables = reportNeedlessDisables;
}

if (cli.flags.help || cli.flags.h) {
  cli.showHelp();
}

if (cli.flags.version || cli.flags.v) {
  cli.showVersion();
}

Promise.resolve()
  .then(() => {
    // Add input/code into options
    if (cli.input.length) {
      return Object.assign({}, optionsBase, {
        files: cli.input
      });
    }
    return getStdin().then(stdin =>
      Object.assign({}, optionsBase, {
        code: stdin
      })
    );
  })
  .then(options => {
    if (!options.files && !options.code) {
      cli.showHelp();
    }

    return standalone(options);
  })
  .then(linted => {
    if (reportNeedlessDisables) {
      const hasReportNeedlessDisable =
        !!linted.needlessDisables &&
        linted.needlessDisables.some(sourceReport => {
          if (!sourceReport.ranges || sourceReport.ranges.length === 0) {
            return false;
          }

          return true;
        });

      if (hasReportNeedlessDisable) {
        process.stdout.write(
          needlessDisablesStringFormatter(linted.needlessDisables)
        );
        process.exitCode = 2;
      }

      return;
    }

    if (!linted.output) {
      return;
    }
    process.stdout.write(linted.output);
    if (linted.errored) {
      process.exitCode = 2;
    }
    return;
  })
  .catch((err /*: { stack: any, code: any }*/) => {
    console.log(err.stack); // eslint-disable-line no-console
    const exitCode = typeof err.code === "number" ? err.code : 1;
    process.exit(exitCode); // eslint-disable-line no-process-exit
  });
