"use strict";

const _ = require("lodash");
const hasBlock = require("../../utils/hasBlock");
const optionsMatches = require("../../utils/optionsMatches");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const validateOptions = require("../../utils/validateOptions");

const ruleName = "max-nesting-depth";

const messages = ruleMessages(ruleName, {
  expected: depth => `Expected nesting depth to be no more than ${depth}`
});

const rule = function(max, options) {
  const isIgnoreAtRule = node =>
    node.type === "atrule" &&
    optionsMatches(options, "ignoreAtRules", node.name);

  return (root, result) => {
    validateOptions(
      result,
      ruleName,
      {
        actual: max,
        possible: [_.isNumber]
      },
      {
        optional: true,
        actual: options,
        possible: {
          ignore: ["blockless-at-rules"],
          ignoreAtRules: [_.isString]
        }
      }
    );

    root.walkRules(checkStatement);
    root.walkAtRules(checkStatement);

    function checkStatement(statement) {
      if (isIgnoreAtRule(statement)) {
        return;
      }
      if (!hasBlock(statement)) {
        return;
      }
      const depth = nestingDepth(statement);
      if (depth > max) {
        report({
          ruleName,
          result,
          node: statement,
          message: messages.expected(max)
        });
      }
    }
  };

  function nestingDepth(node, level) {
    level = level || 0;
    const parent = node.parent;

    if (isIgnoreAtRule(parent)) {
      return 0;
    }

    // The nesting depth level's computation has finished
    // when this function, recursively called, receives
    // a node that is not nested -- a direct child of the
    // root node
    if (
      parent.type === "root" ||
      (parent.type === "atrule" && parent.parent.type === "root")
    ) {
      return level;
    }

    if (
      optionsMatches(options, "ignore", "blockless-at-rules") &&
      node.type === "atrule" &&
      node.every(child => child.type !== "decl")
    ) {
      return nestingDepth(parent, level);
    }

    // Unless any of the conditions above apply, we want to
    // add 1 to the nesting depth level and then check the parent,
    // continuing to add and move up the hierarchy
    // until we hit the root node
    return nestingDepth(parent, level + 1);
  }
};

rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;
