"use strict";

const _ = require("lodash");
const execall = require("execall");
const optionsMatches = require("../../utils/optionsMatches");
const report = require("../../utils/report");
const ruleMessages = require("../../utils/ruleMessages");
const styleSearch = require("style-search");
const validateOptions = require("../../utils/validateOptions");

const ruleName = "max-line-length";

const messages = ruleMessages(ruleName, {
  expected: max =>
    `Expected line length to be no more than ${max} ${
      max === 1 ? "character" : "characters"
    }`
});

const rule = function(maxLength, options) {
  return (root, result) => {
    const validOptions = validateOptions(
      result,
      ruleName,
      {
        actual: maxLength,
        possible: _.isNumber
      },
      {
        actual: options,
        possible: {
          ignore: ["non-comments", "comments"],
          ignorePattern: [_.isString]
        },
        optional: true
      }
    );
    if (!validOptions) {
      return;
    }

    const rootString = root.source.input.css;

    const ignoreNonComments = optionsMatches(options, "ignore", "non-comments");
    const ignoreComments = optionsMatches(options, "ignore", "comments");

    // Check first line
    checkNewline({ endIndex: 0 });

    // Check subsequent lines
    styleSearch(
      { source: rootString, target: ["\n"], comments: "check" },
      checkNewline
    );

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

    function checkNewline(match) {
      let nextNewlineIndex = rootString.indexOf("\n", match.endIndex);
      if (rootString[nextNewlineIndex - 1] === "\r") {
        nextNewlineIndex -= 1;
      }

      // Accommodate last line
      if (nextNewlineIndex === -1) {
        nextNewlineIndex = rootString.length;
      }

      const rawLineLength = nextNewlineIndex - match.endIndex;
      const lineText = rootString.slice(match.endIndex, nextNewlineIndex);

      // Case sensitive ignorePattern match
      if (optionsMatches(options, "ignorePattern", lineText)) {
        return;
      }

      const urlArgumentsLength = execall(/url\((.*)\)/gi, lineText).reduce(
        (result, match) => {
          return result + _.get(match, "sub[0].length", 0);
        },
        0
      );

      const importUrlsLength = execall(
        /@import\s+(['"].*['"])/gi,
        lineText
      ).reduce((result, match) => {
        return result + _.get(match, "sub[0].length", 0);
      }, 0);

      // If the line's length is less than or equal to the specified
      // max, ignore it ... So anything below is liable to be complained about.
      // **Note that the length of any url arguments or import urls
      // are excluded from the calculation.**
      if (rawLineLength - urlArgumentsLength - importUrlsLength <= maxLength) {
        return;
      }

      const complaintIndex = nextNewlineIndex - 1;

      if (ignoreComments) {
        if (match.insideComment) {
          return;
        }

        // This trimming business is to notice when the line starts a
        // comment but that comment is indented, e.g.
        //       /* something here */
        const nextTwoChars = rootString
          .slice(match.endIndex)
          .trim()
          .slice(0, 2);
        if (nextTwoChars === "/*" || nextTwoChars === "//") {
          return;
        }
      }

      if (ignoreNonComments) {
        if (match.insideComment) {
          return complain(complaintIndex);
        }

        // This trimming business is to notice when the line starts a
        // comment but that comment is indented, e.g.
        //       /* something here */
        const nextTwoChars = rootString
          .slice(match.endIndex)
          .trim()
          .slice(0, 2);
        if (nextTwoChars !== "/*" && nextTwoChars !== "//") {
          return;
        }
        return complain(complaintIndex);
      }

      // If there are no spaces besides initial (indent) spaces, ignore it
      const lineString = rootString.slice(match.endIndex, nextNewlineIndex);
      if (lineString.replace(/^\s+/, "").indexOf(" ") === -1) {
        return;
      }

      return complain(complaintIndex);
    }
  };
};

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