"use strict";

const isCustomPropertySet = require("../../utils/isCustomPropertySet");
const isStandardSyntaxRule = require("../../utils/isStandardSyntaxRule");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const styleSearch = require("style-search");
const validateOptions = require("../../utils/validateOptions");

const ruleName = "no-extra-semicolons";

const messages = ruleMessages(ruleName, {
  rejected: "Unexpected extra semicolon"
});

function getOffsetByNode(node) {
  const string = node.root().source.input.css;
  const nodeColumn = node.source.start.column;
  const nodeLine = node.source.start.line;
  let line = 1;
  let column = 1;
  let index = 0;

  for (let i = 0; i < string.length; i++) {
    if (column === nodeColumn && nodeLine === line) {
      index = i;
      break;
    }

    if (string[i] === "\n") {
      column = 1;
      line += 1;
    } else {
      column += 1;
    }
  }

  return index;
}

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

    const rawAfterRoot = root.raws.after;

    if (rawAfterRoot && rawAfterRoot.trim().length !== 0) {
      styleSearch({ source: rawAfterRoot, target: ";" }, match => {
        complain(
          root.toString().length - rawAfterRoot.length + match.startIndex
        );
      });
    }

    root.walk(node => {
      if (
        node.type === "rule" &&
        !isCustomPropertySet(node) &&
        !isStandardSyntaxRule(node)
      ) {
        return;
      }

      let rawBeforeNode = node.raws.before;

      if (rawBeforeNode && rawBeforeNode.trim().length !== 0) {
        let allowedSemi = 0;

        const next = node.next();

        // Ignore semicolon before comment if next node is custom properties sets or comment
        if (
          node.type === "comment" &&
          next &&
          isCustomPropertySet(next) &&
          node.parent.index(next) > 0
        ) {
          allowedSemi = 1;
        }

        const prev = node.prev();

        // Adding previous node string to custom properties set if previous node is comment
        if (
          isCustomPropertySet(node) &&
          node.parent.index(node) > 0 &&
          prev &&
          prev.type === "comment"
        ) {
          rawBeforeNode = prev.toString() + rawBeforeNode;
          allowedSemi = 0;
        }

        styleSearch({ source: rawBeforeNode, target: ";" }, (match, count) => {
          if (count === allowedSemi) {
            return;
          }

          complain(
            getOffsetByNode(node) - rawBeforeNode.length + match.startIndex
          );
        });
      }

      const rawAfterNode = node.raws.after;

      if (rawAfterNode && rawAfterNode.trim().length !== 0) {
        /**
         * If the last child is a Less mixin followed by more than one semicolon,
         * node.raws.after will be populated with that semicolon.
         * Since we ignore Less mixins, exit here
         */
        if (
          node.last &&
          node.last.type === "rule" &&
          !isCustomPropertySet(node.last) &&
          !isStandardSyntaxRule(node.last)
        ) {
          return;
        }

        styleSearch({ source: rawAfterNode, target: ";" }, match => {
          const index =
            getOffsetByNode(node) +
            node.toString().length -
            1 -
            rawAfterNode.length +
            match.startIndex;

          complain(index);
        });
      }

      const rawOwnSemicolon = node.raws.ownSemicolon;

      if (rawOwnSemicolon) {
        let allowedSemi = 0;

        if (isCustomPropertySet(node)) {
          allowedSemi = 1;
        }

        styleSearch(
          { source: rawOwnSemicolon, target: ";" },
          (match, count) => {
            if (count === allowedSemi) {
              return;
            }

            const index =
              getOffsetByNode(node) +
              node.toString().length -
              rawOwnSemicolon.length +
              match.startIndex;
            complain(index);
          }
        );
      }
    });

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

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