/* @flow */
"use strict"
const _ = require("lodash")

const COMMAND_PREFIX = "stylelint-"
const disableCommand = COMMAND_PREFIX + "disable"
const enableCommand = COMMAND_PREFIX + "enable"
const disableLineCommand = COMMAND_PREFIX + "disable-line"
const disableNextLineCommand = COMMAND_PREFIX + "disable-next-line"
const ALL_RULES = "all"

/*:: type disabledRangeObject = {
  [ruleName: string]: Array<{
    start: number,
    end?: number,
  }>
}*/

// Run it like a plugin ...
module.exports = function (
  root/*: Object*/,
  result/*: Object*/
) {
  result.stylelint = result.stylelint || {}

  // Most of the functions below work via side effects mutating
  // this object
  const disabledRanges/*: disabledRangeObject*/ = {
    all: [],
  }
  result.stylelint.disabledRanges = disabledRanges
  root.walkComments(checkComment)

  return result

  function processDisableLineCommand(comment/*: postcss$comment*/) {
    getCommandRules(disableLineCommand, comment.text).forEach(ruleName => {
      disableLine(comment.source.start.line, ruleName, comment)
    })
  }

  function processDisableNextLineCommand(comment/*: postcss$comment*/) {
    getCommandRules(disableNextLineCommand, comment.text).forEach(ruleName => {
      disableLine(comment.source.start.line + 1, ruleName, comment)
    })
  }

  function disableLine(
    line/*: number*/,
    ruleName/*: string*/,
    comment/*: postcss$comment*/
  ) {
    if (ruleIsDisabled(ALL_RULES)) {
      throw comment.error("All rules have already been disabled", { plugin: "stylelint" })
    }
    if (ruleIsDisabled(ruleName)) {
      throw comment.error(`"${ruleName}" has already been disabled`, { plugin: "stylelint" })
    }
    if (ruleName === ALL_RULES) {
      Object.keys(disabledRanges).forEach(disabledRuleName => {
        startDisabledRange(line, disabledRuleName)
        endDisabledRange(line, disabledRuleName)
      })
    } else {
      startDisabledRange(line, ruleName)
      endDisabledRange(line, ruleName)
    }
  }

  function processDisableCommand(comment/*: postcss$comment*/) {
    getCommandRules(disableCommand, comment.text).forEach(ruleToDisable => {
      if (ruleToDisable === ALL_RULES) {
        if (ruleIsDisabled(ALL_RULES)) {
          throw comment.error("All rules have already been disabled", { plugin: "stylelint" })
        }
        Object.keys(disabledRanges).forEach(ruleName => {
          startDisabledRange(comment.source.start.line, ruleName)
        })
        return
      }

      if (ruleIsDisabled(ruleToDisable)) {
        throw comment.error(`"${ruleToDisable}" has already been disabled`, { plugin: "stylelint" })
      }
      startDisabledRange(comment.source.start.line, ruleToDisable)
    })
  }

  function processEnableCommand(comment/*: postcss$comment*/) {
    getCommandRules(enableCommand, comment.text).forEach(ruleToEnable => {
      if (ruleToEnable === ALL_RULES) {
        if (_.values(disabledRanges).every(ranges => _.isEmpty(ranges) || !!_.last(ranges.end))) {
          throw comment.error("No rules have been disabled", { plugin: "stylelint" })
        }
        Object.keys(disabledRanges).forEach(ruleName => {
          if (!_.get(_.last(disabledRanges[ruleName]), "end")) {
            endDisabledRange(comment.source.end.line, ruleName)
          }
        })
        return
      }

      if (ruleIsDisabled(ALL_RULES) && disabledRanges[ruleToEnable] === undefined) {
        // Get a starting point from the where all rules were disabled
        if (!disabledRanges[ruleToEnable]) {
          disabledRanges[ruleToEnable] = _.cloneDeep(disabledRanges.all)
        } else {
          disabledRanges[ruleToEnable].push(_.clone(_.last(disabledRanges[ALL_RULES])))
        }
        endDisabledRange(comment.source.end.line, ruleToEnable)
        return
      }

      if (ruleIsDisabled(ruleToEnable)) {
        endDisabledRange(comment.source.end.line, ruleToEnable)
        return
      }

      throw comment.error(`"${ruleToEnable}" has not been disabled`, { plugin: "stylelint" })
    })
  }

  function checkComment(comment/*: postcss$comment*/) {
    const text = comment.text

    // Ignore comments that are not relevant commands

    if (text.indexOf(COMMAND_PREFIX) !== 0) {
      return result
    }

    if (text.indexOf(disableLineCommand) === 0) {
      processDisableLineCommand(comment)
    } else if (text.indexOf(disableNextLineCommand) === 0) {
      processDisableNextLineCommand(comment)
    } else if (text.indexOf(disableCommand) === 0) {
      processDisableCommand(comment)
    } else if (text.indexOf(enableCommand) === 0) {
      processEnableCommand(comment)
    }
  }

  function getCommandRules(
    command/*: string*/,
    fullText/*: string*/
  )/*: Array<string>*/ {
    const rules = _.compact(fullText.slice(command.length).split(",")).map(r => r.trim())
    if (_.isEmpty(rules)) {
      return [ALL_RULES]
    }
    return rules
  }

  function startDisabledRange(line/*: number*/, ruleName/*: string*/) {
    const rangeObj = { start: line }
    ensureRuleRanges(ruleName)
    disabledRanges[ruleName].push(rangeObj)
  }

  function endDisabledRange(line/*: number*/, ruleName/*: string*/) {
    const lastRangeForRule = _.last(disabledRanges[ruleName])
    if (!lastRangeForRule) {
      return
    }
    // Add an `end` prop to the last range of that rule
    lastRangeForRule.end = line
  }

  function ensureRuleRanges(ruleName/*: string*/) {
    if (!disabledRanges[ruleName]) {
      disabledRanges[ruleName] = _.cloneDeep(disabledRanges.all)
    }
  }

  function ruleIsDisabled(ruleName/*: string*/)/*: boolean*/ {
    if (disabledRanges[ruleName] === undefined) return false
    if (_.last(disabledRanges[ruleName]) === undefined) return false
    if (_.get(_.last(disabledRanges[ruleName]), "end") === undefined) return true
    return false
  }
}
