"use strict"

const findFontFamily = require("../../utils/findFontFamily")
const isStandardSyntaxValue = require("../../utils/isStandardSyntaxValue")
const isVariable = require("../../utils/isVariable")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const keywordSets = require("../../reference/keywordSets")

const ruleName = "font-family-name-quotes"

const messages = ruleMessages(ruleName, {
  expected: family => `Expected quotes around "${family}"`,
  rejected: family => `Unexpected quotes around "${family}"`,
})

function isSystemFontKeyword(font) {
  if (font.indexOf("-apple-") === 0) {
    return true
  }
  if (font === "BlinkMacSystemFont") {
    return true
  }
  return false
}

// "To avoid mistakes in escaping, it is recommended to quote font family names
// that contain white space, digits, or punctuation characters other than hyphens"
// (https://www.w3.org/TR/CSS2/fonts.html#font-family-prop)
function quotesRecommended(family) {
  return !/^[-a-zA-Z]+$/.test(family)
}

// Quotes are required if the family is not a valid CSS identifier
// (regexes from https://mathiasbynens.be/notes/unquoted-font-family)
function quotesRequired(family) {
  return family.split(/\s+/).some(word => {
    return (/^(-?\d|--)/.test(word) || !/^[-_a-zA-Z0-9\u00A0-\u10FFFF]+$/.test(word)
    )
  })
}

const rule = function (expectation) {
  return (root, result) => {
    const validOptions = validateOptions(result, ruleName, {
      actual: expectation,
      possible: [
        "always-where-required",
        "always-where-recommended",
        "always-unless-keyword",
      ],
    })
    if (!validOptions) {
      return
    }

    root.walkDecls(/^font(-family)?$/i, decl => {
      const fontFamilies = findFontFamily(decl.value)

      if (fontFamilies.length === 0) {
        return
      }

      fontFamilies.forEach(fontFamilyNode => {
        let rawFamily = fontFamilyNode.value

        if (fontFamilyNode.quote) {
          rawFamily = fontFamilyNode.quote + rawFamily + fontFamilyNode.quote
        }

        checkFamilyName(rawFamily, decl)
      })
    })

    function checkFamilyName(rawFamily, decl) {
      if (!isStandardSyntaxValue(rawFamily)) {
        return
      }
      if (isVariable(rawFamily)) {
        return
      }

      const hasQuotes = rawFamily[0] === "'" || rawFamily[0] === "\""

      // Clean the family of its quotes
      const family = rawFamily.replace(/^['"]|['"]$/g, "")

      // Disallow quotes around (case-insensitive) keywords
      // and system font keywords in all cases
      if (keywordSets.fontFamilyKeywords.has(family.toLowerCase()) || isSystemFontKeyword(family)) {
        if (hasQuotes) {
          return complain(messages.rejected(family), family, decl)
        }
        return
      }

      const required = quotesRequired(family)
      const recommended = quotesRecommended(family)

      switch (expectation) {
        case "always-unless-keyword":
          if (!hasQuotes) {
            return complain(messages.expected(family), family, decl)
          }
          return

        case "always-where-recommended":
          if (!recommended && hasQuotes) {
            return complain(messages.rejected(family), family, decl)
          }
          if (recommended && !hasQuotes) {
            return complain(messages.expected(family), family, decl)
          }
          return

        case "always-where-required":
          if (!required && hasQuotes) {
            return complain(messages.rejected(family), family, decl)
          }
          if (required && !hasQuotes) {
            return complain(messages.expected(family), family, decl)
          }
          return
      }
    }

    function complain(message, family, decl) {
      report({
        result,
        ruleName,
        message,
        node: decl,
        word: family,
      })
    }
  }
}

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