const t = require('@babel/types')
const babelTraverse = require('@babel/traverse').default

const {
  parseComponents
} = require('./util')

function handleObjectExpression (declaration, path, state) {
  if (state.options) { // name,inheritAttrs,props
    Object.keys(state.options).forEach(name => {
      const optionProperty = declaration.properties.filter(prop => {
        return t.isObjectProperty(prop) &&
          t.isIdentifier(prop.key) &&
          prop.key.name === name
      })[0]
      if (optionProperty) {
        if (name === 'props') {
          if (t.isArrayExpression(optionProperty.value)) {
            state.options[name] = JSON.stringify(optionProperty.value.elements.filter(element => t.isStringLiteral(element)).map(({ value }) => value))
          } else if (t.isObjectExpression(optionProperty.value)) {
            const props = []
            optionProperty.value.properties.forEach(({ key }) => {
              if (t.isIdentifier(key)) {
                props.push(key.name)
              } else if (t.isStringLiteral(key)) {
                props.push(key.value)
              }
            })
            state.options[name] = JSON.stringify(props)
          }
        } else if (t.isStringLiteral(optionProperty.value)) {
          state.options[name] = JSON.stringify(optionProperty.value.value)
        } else {
          state.options[name] = optionProperty.value.value
        }
      }
    })
  }

  const componentsProperty = declaration.properties.filter(prop => {
    return t.isObjectProperty(prop) &&
      t.isIdentifier(prop.key) &&
      prop.key.name === 'components'
  })[0]

  if (componentsProperty && t.isObjectExpression(componentsProperty.value)) {
    handleComponentsObjectExpression(componentsProperty.value, path, state)
  }
}

function handleComponentsObjectExpression (componentsObjExpr, path, state, prepend) {
  const properties = componentsObjExpr.properties
    .filter(prop => t.isObjectProperty(prop) && t.isIdentifier(prop.value))
  const components = parseComponents(properties.map(prop => {
    return {
      name: prop.key.name || prop.key.value,
      value: prop.value.name
    }
  }), path.scope.bindings, path)
  state.components = prepend ? components.concat(state.components) : components
}

function handleIdentifier ({ name }, path, state) {
  // 仅做有限查找
  for (let i = path.container.length; i > 0; i--) {
    const node = path.container[i - 1]
    let declarations = []
    if (t.isExpressionStatement(node)) {
      declarations = [node]
    } else if (t.isVariableDeclaration(node)) {
      declarations = node.declarations
    }
    for (let i = declarations.length; i > 0; i--) {
      let declaration = declarations[i - 1]
      let identifier
      if (t.isVariableDeclarator(declaration)) {
        identifier = declaration.id
        declaration = declaration.init
      } else if (t.isExpressionStatement(declaration)) {
        identifier = declaration.expression.left
        declaration = declaration.expression.right
      }
      if (identifier.name === name) {
        if (t.isCallExpression(declaration) &&
          t.isMemberExpression(declaration.callee) &&
          declaration.arguments.length === 1) {
          declaration = declaration.arguments[0]
        }
        if (t.isObjectExpression(declaration)) {
          handleObjectExpression(declaration, path, state)
        }
        return
      }
    }
  }
}

module.exports = function (ast, state = {
  type: 'Component',
  components: [],
  options: {}
}) {
  babelTraverse(ast, {
    AssignmentExpression (path) {
      const leftExpression = path.node.left
      const rightExpression = path.node.right

      if ( // global['__wxVueOptions'] = {'van-button':VanButton}
        t.isMemberExpression(leftExpression) &&
        t.isObjectExpression(rightExpression) &&
        leftExpression.object.name === 'global' &&
        leftExpression.property.value === '__wxVueOptions'
      ) {
        handleObjectExpression(rightExpression, path, state)
      }

      if ( // exports.default.components = Object.assign({'van-button': VanButton}, exports.default.components || {})
        t.isMemberExpression(leftExpression) &&
        t.isCallExpression(rightExpression) &&
        leftExpression.property.name === 'components' &&
        t.isMemberExpression(leftExpression.object) &&
        leftExpression.object.object.name === 'exports' &&
        leftExpression.object.property.name === 'default' &&
        rightExpression.arguments.length === 2 &&
        t.isObjectExpression(rightExpression.arguments[0])
      ) {
        handleComponentsObjectExpression(rightExpression.arguments[0], path, state, true)
      }
    },
    ExportDefaultDeclaration (path) {
      const declaration = path.node.declaration
      if (t.isObjectExpression(declaration)) { // export default {components:{}}
        handleObjectExpression(declaration, path, state)
      } else if (t.isIdentifier(declaration)) {
        handleIdentifier(declaration, path, state)
      } else if (t.isCallExpression(declaration) &&
        t.isMemberExpression(declaration.callee) &&
        declaration.arguments.length === 1) { // export default Vue.extend({components:{}})
        if (declaration.callee.object.name === 'Vue' && declaration.callee.property.name ===
          'extend') {
          const argument = declaration.arguments[0]
          if (t.isObjectExpression(argument)) {
            handleObjectExpression(argument, path, state)
          } else if (t.isIdentifier(argument)) {
            handleIdentifier(argument, path, state)
          }
        }
      } else if (t.isClassDeclaration(declaration) &&
        declaration.decorators &&
        declaration.decorators.length) { // export default @Component({components:{}}) class MyComponent extend Vue
        const componentDecorator = declaration.decorators[0]
        if (t.isCallExpression(componentDecorator.expression)) {
          const args = componentDecorator.expression.arguments
          if (args && args.length && t.isObjectExpression(args[0])) {
            handleObjectExpression(args[0], path, state)
          }
        }
      }
    }
  })
  return {
    ast,
    state
  }
}
