'use strict'

var css = require('css')
var util = require('./lib/util')
var validateItem = require('./lib/validator').validate
var shorthandParser = require('./lib/shorthand-parser')

/**
 * mergeStyle
 * @param {*} object 
 * @param {*} classNames 
 * @param {*} preClassNames 
 * @param {*} ruleResult 
 * @param {*} prop 
 * @param {*} index 
 */
function mergeStyle (object, classNames, preClassNames, ruleResult, prop, index) {
  if (!process.env.UNI_USING_NVUE_STYLE_COMPILER) {
    object[classNames] = object[classNames] || {}
    object[classNames][prop] = ruleResult[prop]
    return
  }
  classNames = classNames.split('.').map(str => '.' + str).slice(1)
  var className = classNames.find(className => className in object) || classNames[0]
  // 假设选择器已经去重简化
  preClassNames += classNames.filter(str => str !== className).sort().join('')
  var rules = object[className] = object[className] || {}
  var style = rules[preClassNames] = rules[preClassNames] || {}
  // 增加其他权重信息
  style[prop] = [...ruleResult[prop], preClassNames.split('.').length - 1, index]
}

/**
 * Parse `<style>` code to a JSON Object and log errors & warnings
 *
 * @param {string} code
 * @param {function} done which will be called with
 * - err:Error
 * - data.jsonStyle{}: `classname.propname.value`-like object
 * - data.log[{line, column, reason}]
 */
function parse (code, done) {
  var ast, err, jsonStyle = {}, log = []

  // css parse
  ast = css.parse(code, { silent: true })

  // catch syntax error
  if (ast.stylesheet.parsingErrors && ast.stylesheet.parsingErrors.length) {
    err = ast.stylesheet.parsingErrors
    err.forEach(function (error) {
      log.push({ line: error.line, column: error.column, reason: error.toString().replace('Error', 'ERROR') })
    })
  }

  // walk all
  /* istanbul ignore else */
  if (ast && ast.type === 'stylesheet' && ast.stylesheet &&
    ast.stylesheet.rules && ast.stylesheet.rules.length) {
    ast.stylesheet.rules.forEach(function (rule, index) {
      var type = rule.type
      var ruleResult = {}
      var ruleLog = []

      if (type === 'rule') {
        if (rule.declarations && rule.declarations.length) {
          rule.declarations = shorthandParser(rule.declarations)

          rule.declarations.forEach(function (declaration) {
            var subType = declaration.type
            var name, value, line, column, subResult, camelCasedName

            /* istanbul ignore if */
            if (subType !== 'declaration') {
              return
            }

            name = declaration.property
            value = declaration.value
            var newValue = value.replace(/\s*!important/g, '')
            var importantWeight = Number(value !== newValue)
            value = newValue

            // validate declarations and collect them to result
            camelCasedName = util.hyphenedToCamelCase(name)
            subResult = validateItem(camelCasedName, value)

            /* istanbul ignore else */
            if (typeof subResult.value === 'number' || typeof subResult.value === 'string') {
              if (process.env.UNI_USING_NVUE_STYLE_COMPILER) {
                var oldValue = ruleResult[camelCasedName]
                // 增加 important 权重信息
                ruleResult[camelCasedName] = Array.isArray(oldValue) && oldValue[1] > importantWeight ? oldValue : [subResult.value, importantWeight]
              } else {
                ruleResult[camelCasedName] = subResult.value
              }
            }
            if (subResult.log) {
              subResult.log.line = declaration.position.start.line
              subResult.log.column = declaration.position.start.column
              ruleLog.push(subResult.log)
            }
          })

          rule.selectors.forEach(function (selector) {
            selector = selector.replace(/\s*([\+\~\>])\s*/g, '$1').replace(/\s+/, ' ')
            // 支持组合选择器
            const res = selector.match(process.env.UNI_USING_NVUE_STYLE_COMPILER ? /^((?:(?:\.[A-Za-z0-9_\-]+)+[\+\~\> ])*)((?:\.[A-Za-z0-9_\-\:]+)+)$/ : /^(\.)([A-Za-z0-9_\-:]+)$/)
            if (res) {
              var preClassNames = res[1]
              var classNames = res[2]

              // handle pseudo class
              var pseudoIndex = classNames.indexOf(':')
              if (pseudoIndex > -1) {
                var pseudoCls = classNames.slice(pseudoIndex)
                classNames = classNames.slice(0, pseudoIndex)
                var pseudoRuleResult = {}
                Object.keys(ruleResult).forEach(function (prop) {
                  pseudoRuleResult[prop + pseudoCls] = ruleResult[prop]
                })
                ruleResult = pseudoRuleResult
              }

              // merge style
              Object.keys(ruleResult).forEach(function (prop) {
                // // handle transition
                // if (prop.indexOf('transition') === 0 && prop !== 'transition') {
                //   var realProp = prop.replace('transition', '')
                //   realProp = realProp[0].toLowerCase() + realProp.slice(1)
                //   var object = jsonStyle['@TRANSITION'] = jsonStyle['@TRANSITION'] || {}
                //   mergeStyle(object, classNames, preClassNames, ruleResult, prop, index)
                // }

                mergeStyle(jsonStyle, classNames, preClassNames, ruleResult, prop, index)
              })
            }
            else {
              log.push({
                line: rule.position.start.line,
                column: rule.position.start.column,
                reason: 'ERROR: Selector `' + selector + '` is not supported. Weex only support classname selector'
              })
            }
          })
          log = log.concat(ruleLog)
        }
      }
      /* istanbul ignore else */
      else if (type === 'font-face') {
        /* istanbul ignore else */
        if (rule.declarations && rule.declarations.length) {
          rule.declarations.forEach(function (declaration) {
            /* istanbul ignore if */
            if (declaration.type !== 'declaration') {
              return
            }
            var name = util.hyphenedToCamelCase(declaration.property)
            var value = declaration.value
            if (name === 'fontFamily' && '\"\''.indexOf(value[0]) > -1) { // FIXME: delete leading and trailing quotes
              value = value.slice(1, value.length - 1)
            }
            ruleResult[name] = value
          })
          if (!jsonStyle['@FONT-FACE']) {
            jsonStyle['@FONT-FACE'] = []
          }
          jsonStyle['@FONT-FACE'].push(ruleResult)
        }
      }
    })
  }

  jsonStyle['@VERSION'] = 2

  done(err, { jsonStyle: jsonStyle, log: log })
}

/**
 * Validate a JSON Object and log errors & warnings
 *
 * @param {object} json
 * @param {function} done which will be called with
 * - err:Error
 * - data.jsonStyle{}: `classname.propname.value`-like object
 * - data.log[{reason}]
 */
function validate (json, done) {
  var log = []
  var err

  try {
    json = JSON.parse(JSON.stringify(json))
  }
  catch (e) {
    err = e
    json = {}
  }

  Object.keys(json).forEach(function (selector) {
    var declarations = json[selector]

    Object.keys(declarations).forEach(function (name) {
      var value = declarations[name]
      var result = validateItem(name, value)

      if (typeof result.value === 'number' || typeof result.value === 'string') {
        declarations[name] = result.value
      }
      else {
        delete declarations[name]
      }

      if (result.log) {
        log.push(result.log)
      }
    })
  })

  done(err, {
    jsonStyle: json,
    log: log
  })
}

module.exports = {
  parse: parse,
  validate: validate,
  validateItem: validateItem,
  util: util
}
