"use strict";

const addEmptyLineBefore = require("../../utils/addEmptyLineBefore");
const hasEmptyLine = require("../../utils/hasEmptyLine");
const isAfterComment = require("../../utils/isAfterComment");
const isFirstNested = require("../../utils/isFirstNested");
const isFirstNodeOfRoot = require("../../utils/isFirstNodeOfRoot");
const isSharedLineComment = require("../../utils/isSharedLineComment");
const optionsMatches = require("../../utils/optionsMatches");
const removeEmptyLinesBefore = require("../../utils/removeEmptyLinesBefore");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const validateOptions = require("../../utils/validateOptions");

const ruleName = "comment-empty-line-before";

const messages = ruleMessages(ruleName, {
  expected: "Expected empty line before comment",
  rejected: "Unexpected empty line before comment"
});

const stylelintCommandPrefix = "stylelint-";

const rule = function(expectation, options, context) {
  return (root, result) => {
    const validOptions = validateOptions(
      result,
      ruleName,
      {
        actual: expectation,
        possible: ["always", "never"]
      },
      {
        actual: options,
        possible: {
          except: ["first-nested"],
          ignore: ["stylelint-commands", "after-comment"]
        },
        optional: true
      }
    );
    if (!validOptions) {
      return;
    }

    root.walkComments(comment => {
      // Ignore the first node
      if (isFirstNodeOfRoot(comment)) {
        return;
      }

      // Optionally ignore stylelint commands
      if (
        comment.text.indexOf(stylelintCommandPrefix) === 0 &&
        optionsMatches(options, "ignore", "stylelint-commands")
      ) {
        return;
      }

      // Optionally ignore newlines between comments
      if (
        optionsMatches(options, "ignore", "after-comment") &&
        isAfterComment(comment)
      ) {
        return;
      }

      // Ignore shared-line comments
      if (isSharedLineComment(comment)) {
        return;
      }

      // Ignore SCSS comments
      if (comment.raws.inline || comment.inline) {
        return;
      }

      const expectEmptyLineBefore = (() => {
        if (
          optionsMatches(options, "except", "first-nested") &&
          isFirstNested(comment)
        ) {
          return false;
        }
        return expectation === "always";
      })();

      const before = comment.raws.before || "";
      const hasEmptyLineBefore = hasEmptyLine(before);

      // Return if the expectation is met
      if (expectEmptyLineBefore === hasEmptyLineBefore) {
        return;
      }

      // Fix
      if (context.fix) {
        if (expectEmptyLineBefore) {
          addEmptyLineBefore(comment, context.newline);
        } else {
          removeEmptyLinesBefore(comment, context.newline);
        }

        return;
      }

      const message = expectEmptyLineBefore
        ? messages.expected
        : messages.rejected;

      report({
        message,
        node: comment,
        result,
        ruleName
      });
    });
  };
};

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