/* eslint-disable max-classes-per-file */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'

import {
  ActivityIndicator,
  Keyboard,
  StyleSheet,
  StyleProp,
  TouchableHighlight,
  View,
  ViewStyle,
} from 'react-native'

import { events, bindingTypes, actionTypes } from '@adalo/constants'

// utils
import { getBindingValue } from 'utils/dependencies'
import { executeAction, actionContextTypes } from 'utils/actions'
import { evaluateCondition } from 'utils/conditions'

// types
import { IAction, IObject, IReduxState } from 'interfaces'

export interface ActionWrapperProps {
  object: IObject
  bindingData?: {}
  bindingComparison?: string
  hitSlop?: number
  style?: StyleProp<ViewStyle>

  getBinding?: () => null
  getParams?: () => object
  dispatch?: () => void
  disabled?: boolean
}

export interface ActionWrapperState {
  active?: boolean
}

class ActionWrapper<T extends ActionWrapperProps> extends Component<
  T,
  ActionWrapperState
> {
  static contextTypes = {
    ...actionContextTypes,
  }

  state = {
    active: false,
  }

  getVisible = () => {
    const { object, bindingData, bindingComparison } = this.props
    const binding = object.dataBinding

    if (!binding) {
      return !object.attributes?.hidden
    }

    if (
      binding &&
      binding.source &&
      binding.bindingType === bindingTypes.VISIBILITY
    ) {
      if (bindingData === undefined) {
        return false
      }

      return evaluateCondition(
        bindingData,
        binding.comparator,
        binding.source.dataType,
        bindingComparison
      )
    }

    return !object.attributes?.hidden
  }

  getAffectLayout() {
    const { object } = this.props
    const binding = object.dataBinding

    if (
      binding &&
      binding.options &&
      binding.bindingType === bindingTypes.VISIBILITY
    ) {
      return !!binding.options.affectLayout
    }

    return false
  }

  getLink() {
    const { object } = this.props
    return object.link
  }

  hasLink() {
    const link = this.getLink()
    return !!link && Object.keys(link).length > 0
  }

  getActions(): object {
    const { object } = this.props
    return object.actions || {}
  }

  hasActions() {
    const actions = this.getActions()

    return Object.keys(actions).length > 0
  }

  handlePress = () => {
    const { getBindings, navigate } = this.context

    const { active } = this.state
    const { dispatch, disabled } = this.props

    if (active || disabled) {
      return
    }

    const link = this.getLink()
    const actions = this.getActions()

    const actionPromises: Promise<any>[] = []

    this.setState({ active: true })

    let hasLinkAction = false

    Object.keys(actions)
      .filter(id => actions[id].eventType === events.TAP)
      .forEach(id => {
        const linkActions = actions[id].actions.filter((a: IAction) => {
          return a.actionType === actionTypes.NAVIGATE
        })

        if (linkActions.length > 0) {
          hasLinkAction = true
        }

        actionPromises.push(
          executeAction(actions[id], { ...this.context, dispatch })
        )
      })

    if (actionPromises.length > 0) {
      Promise.all(actionPromises).then(() => {
        const { setDirty } = this.context

        setDirty()

        this.setState({ active: false })

        if (hasLinkAction) {
          Keyboard.dismiss()
        }
      })
    }

    if (link) {
      const params = getBindings?.() ?? {}

      navigate({ ...link, params })
    }
  }

  render() {
    const { active } = this.state
    const { children, style, hitSlop = 10 } = this.props
    const visible = this.getVisible()
    const affectLayout = this.getAffectLayout()

    if (!visible) {
      if (affectLayout) {
        return null
      }
    }

    const hasAction = this.hasActions() || this.hasLink()
    const opacity = active ? 0.4 : 1

    if (hasAction && visible) {
      return (
        <TouchableHighlight
          style={style}
          underlayColor="transparent"
          activeOpacity={0.7}
          onPress={this.handlePress}
          hitSlop={{
            top: hitSlop,
            bottom: hitSlop,
            left: hitSlop,
            right: hitSlop,
          }}
        >
          <View style={{ flexDirection: 'column' }}>
            <View style={{ flexDirection: 'column', opacity }}>{children}</View>
            {active ? (
              <View style={styles.loaderWrapper}>
                <ActivityIndicator color="#999999" />
              </View>
            ) : null}
          </View>
        </TouchableHighlight>
      )
    }

    const wrapperOpacity = !visible ? { opacity: 0 } : {}

    return (
      <View
        pointerEvents={visible ? 'auto' : 'none'}
        style={[style, wrapperOpacity]}
      >
        {children}
      </View>
    )
  }
}

const mapStateToProps = (state: IReduxState, props: ActionWrapperProps) => {
  const { object, getBinding, getParams } = props
  let bindingComparison

  if (object.dataBinding && object.dataBinding.comparison) {
    bindingComparison = getBindingValue(object.dataBinding.comparison, {
      state,
      getParams,
      getBinding,
    })
  }

  return {
    bindingComparison,
  }
}

const ConnectedActionWrapper: any = connect(mapStateToProps)(
  ActionWrapper as any
)

export default class WrappedActionWrapper<
  T extends ActionWrapperProps
> extends Component<T> {
  static contextTypes = {
    getBinding: PropTypes.func,
    getParams: PropTypes.func,
  }

  render() {
    const { getBinding, getParams } = this.context

    return (
      <ConnectedActionWrapper
        {...this.props}
        getBinding={getBinding}
        getParams={getParams}
      />
    )
  }
}

const styles = StyleSheet.create({
  loaderWrapper: {
    position: 'absolute',
    left: 0,
    top: 0,
    right: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center',
  },
})
