"use strict";

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

const ruleName = "declaration-block-no-duplicate-properties";

const messages = ruleMessages(ruleName, {
  rejected: property => `Unexpected duplicate "${property}"`
});

const rule = function(on, options) {
  return (root, result) => {
    const validOptions = validateOptions(
      result,
      ruleName,
      { actual: on },
      {
        actual: options,
        possible: {
          ignore: [
            "consecutive-duplicates",
            "consecutive-duplicates-with-different-values"
          ],
          ignoreProperties: [_.isString]
        },
        optional: true
      }
    );
    if (!validOptions) {
      return;
    }

    // In order to accommodate nested blocks (postcss-nested),
    // we need to run a shallow loop (instead of eachDecl() or eachRule(),
    // which loop recursively) and allow each nested block to accumulate
    // its own list of properties -- so that a property in a nested rule
    // does not conflict with the same property in the parent rule
    root.each(node => {
      if (node.type === "rule" || node.type === "atrule") {
        checkRulesInNode(node);
      }
    });

    function checkRulesInNode(node) {
      const decls = [];
      const values = [];

      node.each(child => {
        if (child.nodes && child.nodes.length) {
          checkRulesInNode(child);
        }

        if (child.type !== "decl") {
          return;
        }

        const prop = child.prop;
        const value = child.value;

        if (!isStandardSyntaxProperty(prop)) {
          return;
        }
        if (isCustomProperty(prop)) {
          return;
        }

        // Return early if the property is to be ignored
        if (optionsMatches(options, "ignoreProperties", prop)) {
          return;
        }

        // Ignore the src property as commonly duplicated in at-fontface
        if (prop.toLowerCase() === "src") {
          return;
        }

        const indexDuplicate = decls.indexOf(prop.toLowerCase());

        if (indexDuplicate !== -1) {
          if (
            optionsMatches(
              options,
              "ignore",
              "consecutive-duplicates-with-different-values"
            )
          ) {
            // if duplicates are not consecutive
            if (indexDuplicate !== decls.length - 1) {
              report({
                message: messages.rejected(prop),
                node: child,
                result,
                ruleName
              });
              return;
            }
            // if values of consecutive duplicates are equal
            if (value === values[indexDuplicate]) {
              report({
                message: messages.rejected(value),
                node: child,
                result,
                ruleName
              });
              return;
            }
            return;
          }

          if (
            optionsMatches(options, "ignore", "consecutive-duplicates") &&
            indexDuplicate === decls.length - 1
          ) {
            return;
          }

          report({
            message: messages.rejected(prop),
            node: child,
            result,
            ruleName
          });
        }

        decls.push(prop.toLowerCase());
        values.push(value.toLowerCase());
      });
    }
  };
};

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