#!/usr/bin/env node
/* @flow */
"use strict"
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")

const minimistOptions = {
  default: {
    config: false,
    f: "string",
    q: false,
  },
  alias: {
    f: "formatter",
    h: "help",
    i: "ignore-path",
    id: "ignore-disables",
    q: "quiet",
    rd: "report-needless-disables",
    s: "syntax",
    v: "version",
    aei: "allow-empty-input",
  },
  boolean: [
    "allow-empty-input",
    "color",
    "help",
    "ignore-disables",
    "no-color",
    "quiet",
    "version",
  ],
}

const meowOptions = {
  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, 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().

      --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.

      --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.

      --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.
  `,
  pkg: "../package.json",
}

const cli = meow(meowOptions, minimistOptions)

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 = require(customFormatter)
}

const optionsBase/*: Object*/ = {
  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(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.ignoreDisables) {
  optionsBase.ignoreDisables = cli.flags.ignoreDisables
}

if (cli.flags.allowEmptyInput) {
  optionsBase.allowEmptyInput = cli.flags.allowEmptyInput
}

const reportNeedlessDisables = cli.flags.reportNeedlessDisables

if (reportNeedlessDisables) {
  optionsBase.reportNeedlessDisables = reportNeedlessDisables
}

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) {
    process.stdout.write(needlessDisablesStringFormatter(linted.needlessDisables))
    if (reportNeedlessDisables === "error") {
      process.exitCode = 2
    }
    return
  }

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