const t = require('@babel/types')

const {
  VAR_ORIGINAL,
  VAR_INDEX,
  IDENTIFIER_FOR,
  METHOD_RENDER_LIST
} = require('../../constants')

const {
  getMapCallExpression
} = require('./statements')

const {
  hasOwn,
  genCode,
  traverseKey,
  processMemberExpression,
  getForIndexIdentifier,
  isSimpleObjectExpression,
  traverseFilter
} = require('../../util')

const getMemberExpr = require('./member-expr')

const origVisitor = {
  noScope: true,
  Identifier (path) {
    if (
      !path.node.$mpProcessed &&
      path.node.name === this.forItem &&
      path.isReferencedIdentifier()
    ) {
      const forItemIdentifier = t.identifier(this.forItem)
      forItemIdentifier.$mpProcessed = true
      path.replaceWith(
        t.memberExpression(forItemIdentifier, t.identifier(VAR_ORIGINAL))
      )
    }
  },
  FunctionExpression (path) {
    const callee = path.parentPath.node.callee
    if (t.isIdentifier(callee) && callee.name === METHOD_RENDER_LIST) {
      path.traverse(origVisitor, {
        forItem: this.forItem
      })
      path.skip()
    }
  }
}

function isRefrence (forItem, code) {
  if (forItem === code) {
    return true
  }
  return code.indexOf(forItem + '.') === 0
}

function replaceRefrence (forItem, code) {
  if (forItem === code) {
    return ''
  }
  return code.replace(forItem + '.', '')
}

function getForExtra (forItem, forIndex, path, state) {
  const arg0 = path.node.arguments[0]
  const isNumeric = t.isNumericLiteral(arg0)
  const isString = t.isStringLiteral(arg0)
  let forCode = genCode(processMemberExpression(arg0, state), true)

  const forKey = traverseKey(path.node)
  const origForKeyCode = t.isIdentifier(forKey) && forKey.name
  let forKeyCode = ''
  if (forKey) {
    forKeyCode = genCode(processMemberExpression(forKey, state), true)
    if (isRefrence(forItem, forKeyCode)) {
      forKeyCode = replaceRefrence(forItem, forKeyCode)
    }
  }
  const forExtraElements = []
  if (state.scoped.length) {
    const scoped = state.scoped.find(scoped => isRefrence(scoped.forItem, forCode))
    if (scoped) {
      forCode = replaceRefrence(scoped.forItem, forCode)
      forExtraElements.push(...scoped.forExtra)
    }
  }
  let forCodeElem = t.stringLiteral(forCode)
  if (isNumeric) {
    forCodeElem = t.numericLiteral(arg0.value)
  } else if (isString) {
    forCodeElem = t.stringLiteral('#s#' + forCode)
  }
  if (forItem === origForKeyCode) { // 以自身为 key，则依据 forIndex 查找 ['list','',__i0__],['list','',index]
    forExtraElements.push(
      t.arrayExpression(
        [
          forCodeElem,
          t.stringLiteral(''),
          t.identifier(forIndex)
        ]
      )
    )
  } else {
    forExtraElements.push(
      t.arrayExpression(
        [
          forCodeElem,
          t.stringLiteral(forIndex === forKeyCode ? '' : forKeyCode),
          forKey || t.identifier(forIndex)
        ]
      )
    )
  }
  return forExtraElements
}

module.exports = function traverseRenderList (path, state) {
  const functionExpression = path.get('arguments.1')
  const params = functionExpression.node.params
  const forItem = params[0].name
  let forIndex = params.length > 1 && params[1].name
  let forKey = params.length > 2 && params[2].name
  if (forKey) {
    [forKey, forIndex] = [forIndex, forKey]
  }

  if (!forIndex) {
    if (!hasOwn(state.options, '$forIndexId')) {
      state.options.$forIndexId = 0
    }
    forIndex = getForIndexIdentifier(state.options.$forIndexId++)
    params.push(t.identifier(forIndex))
  }

  const forStateScoped = {
    context: forItem,
    forItem,
    forKey,
    forIndex,
    forExtra: getForExtra(forItem, forIndex, path, state),
    propertyArray: [],
    declarationArray: [],
    renderSlotStatementArray: [],
    path
  }

  const forState = {
    inFor: true,
    context: state.context,
    options: state.options,
    errors: state.errors,
    tips: state.tips,
    scoped: [forStateScoped].concat(state.scoped),
    identifierArray: state.identifierArray,
    propertyArray: [],
    declarationArray: [],
    computedProperty: {},
    initExpressionStatementArray: state.initExpressionStatementArray,
    renderSlotStatementArray: state.renderSlotStatementArray,
    resolveSlotStatementArray: state.resolveSlotStatementArray
  }

  functionExpression.traverse(require('./visitor'), forState)

  const forPath = path.get('arguments.0')
  if (forStateScoped.propertyArray.length || forStateScoped.renderSlotStatementArray.length || forKey) {
    // for => map
    forPath.replaceWith(
      getMemberExpr(
        forPath,
        IDENTIFIER_FOR,
        getMapCallExpression(
          forPath.node,
          forStateScoped.propertyArray,
          forStateScoped.declarationArray,
          forStateScoped.renderSlotStatementArray,
          [], // eventPropertyArray
          forItem,
          forKey,
          forIndex,
          state
        ),
        forState
      )
    )

    functionExpression.traverse(origVisitor, {
      forItem
    })
    if (forKey) {
      functionExpression.traverse({
        Identifier (path) {
          if (
            !path.node.$mpProcessed &&
            path.node.name === this.forIndex &&
            path.isReferencedIdentifier()
          ) {
            const forItemIdentifier = t.identifier(VAR_INDEX)
            forItemIdentifier.$mpProcessed = true
            path.replaceWith(
              t.memberExpression(t.identifier(this.forItem), forItemIdentifier)
            )
          }
        }
      }, {
        forItem,
        forIndex
      })
    }
    const keys = Object.keys(forState.computedProperty)
    if (keys.length) {
      keys.forEach(key => {
        const property = forState.computedProperty[key]
        if (t.isMemberExpression(property) && property.object.name === forItem) {
          property.object = t.memberExpression(t.identifier(forItem), t.identifier(VAR_ORIGINAL))
          forState.options.replaceCodes[key] = `'+${genCode(property, true)}+'`
        }
      })
    }
  } else if ((forPath.isCallExpression() && !traverseFilter(forPath.node.callee, state)) || (forPath.isObjectExpression() && !isSimpleObjectExpression(forPath.node))) {
    forPath.replaceWith(getMemberExpr(forPath, IDENTIFIER_FOR, forPath.node, forState))
  } else {
    forPath.traverse(require('./visitor'), forState)
  }

  forState.propertyArray.forEach(property => {
    state.propertyArray.push(property)
  })

  forState.declarationArray.forEach(declaration => {
    state.declarationArray.push(declaration)
  })
}
