"use strict";

const _ = require("lodash");
const atRuleParamIndex = require("../../utils/atRuleParamIndex");
const isCustomSelector = require("../../utils/isCustomSelector");
const isStandardSyntaxAtRule = require("../../utils/isStandardSyntaxAtRule");
const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule");
const isStandardSyntaxSelector = require("../../utils/isStandardSyntaxSelector");
const keywordSets = require("../../reference/keywordSets");
const optionsMatches = require("../../utils/optionsMatches");
const parseSelector = require("../../utils/parseSelector");
const postcss = require("postcss");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const validateOptions = require("../../utils/validateOptions");

const ruleName = "selector-pseudo-class-no-unknown";

const messages = ruleMessages(ruleName, {
  rejected: selector => `Unexpected unknown pseudo-class selector "${selector}"`
});

const rule = function(actual, options) {
  return (root, result) => {
    const validOptions = validateOptions(
      result,
      ruleName,
      { actual },
      {
        actual: options,
        possible: {
          ignorePseudoClasses: [_.isString]
        },
        optional: true
      }
    );
    if (!validOptions) {
      return;
    }

    function check(selector, result, node) {
      parseSelector(selector, result, node, selectorTree => {
        selectorTree.walkPseudos(pseudoNode => {
          const value = pseudoNode.value;

          if (!isStandardSyntaxSelector(value)) {
            return;
          }

          if (isCustomSelector(value)) {
            return;
          }

          // Ignore pseudo-elements
          if (value.slice(0, 2) === "::") {
            return;
          }

          if (
            optionsMatches(
              options,
              "ignorePseudoClasses",
              pseudoNode.value.slice(1)
            )
          ) {
            return;
          }

          let index = null;
          const name = value.slice(1).toLowerCase();

          if (node.type === "atrule" && node.name === "page") {
            if (keywordSets.atRulePagePseudoClasses.has(name)) {
              return;
            }

            index = atRuleParamIndex(node) + pseudoNode.sourceIndex;
          } else {
            if (
              postcss.vendor.prefix(name) ||
              keywordSets.pseudoClasses.has(name) ||
              keywordSets.pseudoElements.has(name)
            ) {
              return;
            }

            const firstSelectorNode =
              pseudoNode.parent && pseudoNode.parent.nodes
                ? pseudoNode.parent.nodes[0]
                : null;

            if (
              firstSelectorNode &&
              firstSelectorNode.value.slice(0, 2) === "::"
            ) {
              const prevPseudoNodeValue = postcss.vendor.unprefixed(
                firstSelectorNode.value.toLowerCase().slice(2)
              );

              if (
                keywordSets.webkitProprietaryPseudoElements.has(
                  prevPseudoNodeValue
                ) &&
                keywordSets.webkitProprietaryPseudoClasses.has(name)
              ) {
                return;
              }
            }

            index = pseudoNode.sourceIndex;
          }

          report({
            message: messages.rejected(value),
            node,
            index,
            ruleName,
            result
          });
        });
      });
    }

    root.walk(node => {
      let selector = null;

      if (node.type === "rule") {
        if (!isStandardSyntaxRule(node)) {
          return;
        }

        selector = node.selector;
      } else if (
        node.type === "atrule" &&
        node.name === "page" &&
        node.params
      ) {
        if (!isStandardSyntaxAtRule(node)) {
          return;
        }

        selector = node.params;
      }

      // Return if selector empty, it is meaning node type is not a rule or a at-rule

      if (!selector) {
        return;
      }

      // Return early before parse if no pseudos for performance

      if (selector.indexOf(":") === -1) {
        return;
      }

      check(selector, result, node);
    });
  };
};

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