"use strict";

const isStandardSyntaxDeclaration = require("../../utils/isStandardSyntaxDeclaration");
const isStandardSyntaxProperty = require("../../utils/isStandardSyntaxProperty");
const postcss = require("postcss");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const validateOptions = require("../../utils/validateOptions");
const valueParser = require("postcss-value-parser");

const ruleName = "shorthand-property-no-redundant-values";

const messages = ruleMessages(ruleName, {
  rejected: (unexpected, expected) =>
    `Unexpected longhand value '${unexpected}' instead of '${expected}'`
});

// Only these shorthand properties can have values, that can be written in shorter form (remove repetitions)
const shorthandableProperties = new Set([
  "margin",
  "padding",
  "border-color",
  "border-radius",
  "border-style",
  "border-width",
  "grid-gap"
]);

const ignoredCharacters = [
  "+",
  "-",
  "*",
  "/",
  "(",
  ")",
  "$",
  "@",
  "--",
  "var("
];

function isIgnoredCharacters(value) {
  return ignoredCharacters.some(char => value.indexOf(char) !== -1);
}

function canCondense(top, right) {
  const bottom =
    arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
  const left =
    arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;

  const lowerTop = top.toLowerCase();
  const lowerRight = right.toLowerCase();
  const lowerBottom = bottom && bottom.toLowerCase();
  const lowerLeft = left && left.toLowerCase();

  if (canCondenseToOneValue(lowerTop, lowerRight, lowerBottom, lowerLeft)) {
    return [top];
  } else if (
    canCondenseToTwoValues(lowerTop, lowerRight, lowerBottom, lowerLeft)
  ) {
    return [top, right];
  } else if (
    canCondenseToThreeValues(lowerTop, lowerRight, lowerBottom, lowerLeft)
  ) {
    return [top, right, bottom];
  } else {
    return [top, right, bottom, left];
  }
}

function canCondenseToOneValue(top, right, bottom, left) {
  if (top !== right) {
    return false;
  }

  return (top === bottom && (bottom === left || !left)) || (!bottom && !left);
}

function canCondenseToTwoValues(top, right, bottom, left) {
  return (
    (top === bottom && right === left) ||
    (top === bottom && !left && top !== right)
  );
}

function canCondenseToThreeValues(top, right, bottom, left) {
  return right === left;
}

const rule = function(actual, secondary, context) {
  return (root, result) => {
    const validOptions = validateOptions(result, ruleName, { actual });
    if (!validOptions) {
      return;
    }

    root.walkDecls(decl => {
      if (
        !isStandardSyntaxDeclaration(decl) ||
        !isStandardSyntaxProperty(decl.prop)
      ) {
        return;
      }

      const prop = decl.prop,
        value = decl.value;

      const normalizedProp = postcss.vendor.unprefixed(prop.toLowerCase());

      // Ignore not shorthandable properties, and math operations
      if (
        isIgnoredCharacters(value) ||
        !shorthandableProperties.has(normalizedProp)
      ) {
        return;
      }

      const valuesToShorthand = [];

      valueParser(value).walk(valueNode => {
        if (valueNode.type !== "word") {
          return;
        }

        valuesToShorthand.push(valueParser.stringify(valueNode));
      });

      if (valuesToShorthand.length <= 1 || valuesToShorthand.length > 4) {
        return;
      }

      const shortestForm = canCondense.apply(undefined, valuesToShorthand);
      const shortestFormString = shortestForm
        .filter(value => {
          return value;
        })
        .join(" ");
      const valuesFormString = valuesToShorthand.join(" ");

      if (shortestFormString.toLowerCase() === valuesFormString.toLowerCase()) {
        return;
      }

      if (context.fix) {
        decl.value = decl.value.replace(value, shortestFormString);
      } else {
        report({
          message: messages.rejected(value, shortestFormString),
          node: decl,
          result,
          ruleName
        });
      }
    });
  };
};

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