import React, { Component, Fragment, createRef } from 'react'
import { bool, func, number, oneOfType, shape, string, array } from 'prop-types'
import classNames from 'classnames'
import './DemioAutocomplete.less'
import { Select } from 'antd'
import DemioIcon from '../Icon/Icon'

const { Option } = Select

const propTypes = {
  value: oneOfType([string, number]),
  options: array,
  onChange: func,
  name: string,
  required: bool,
  hasError: bool,
  className: string,
  useLabel: bool,
  id: string,
  dropdownContainerProps: shape({}),
  customOptionProps: func,
  customValueRender: func,
  customOptionsRender: func,
  noArrow: bool,
  disabled: bool,
}

class DemioAutocomplete extends Component {
  selectElement = createRef()

  state = {
    open: this.props.forceOpen || false,
    optionSelected: null,
    isEmpty: true,
    isFocused: false,
    defaultValueSelected: false,
    fullOptionSelected: {},
  }

  componentDidMount() {
    const { open } = this.state
    const { forceOpen } = this.props
    const { current: selectElement } = this.selectElement || {}

    this.setDefaultValue()

    if (!selectElement) return

    if (forceOpen && !open) this.toggleOpen()
  }

  componentDidUpdate(prevProps) {
    const {
      open,
      fullOptionSelected: { id: selectedID = null, value: selectedValue = null } = {},
    } = this.state
    const { value, forceOpen } = this.props

    if (prevProps.value !== value) {
      if (!value) {
        this.setState(
          {
            optionSelected: null,
            isEmpty: true,
          },
          this.setDefaultValue
        )
      } else {
        // If value is changed from props and not from click, set to that value
        // We check that the new value isn't the current value, so we don't do it twice
        if (selectedID !== value && selectedValue !== value) {
          this.setChangedValue()
        }
      }
    }

    if (prevProps.forceOpen !== forceOpen && forceOpen && !open) {
      this.toggleOpen()
    }
  }

  setDefaultValue = () => {
    const { value, options, placeholder } = this.props
    if (!(value && options.length)) return
    const selectedOptionData = options.find(
      ({ value: optionValue, id: optionID }) => value === optionValue || value === optionID
    )
    const { name = null, text = null, isDefaultValue = false } = selectedOptionData || {}

    // if value isn't in the options, we consider it the default option
    const defaultValueSelected = isDefaultValue || !selectedOptionData || !value
    const selectedOption = isDefaultValue ? placeholder : name || text

    this.handleSelectOption({
      name: selectedOption,
      isDefaultValue: defaultValueSelected,
    })()
  }

  setChangedValue = () => {
    const { value, options } = this.props
    if (!(value && options.length)) return
    const selectedOptionData = options.find(
      ({ value: optionValue, id: optionID }) => value === optionValue || value === optionID
    )
    const { name = null, text = null } = selectedOptionData || {}

    const selectedOption = name || text

    this.handleSelectOption({ name: selectedOption })()
  }

  handleSelectOption =
    ({ id, name, isDefaultValue = false, disabled = false }) =>
    () => {
      if (disabled) return null
      const { onChange, options } = this.props
      const fullOptionSelected =
        options.find(
          ({ id: optionID, name: optionName }) => optionID === id || optionName === name
        ) || {}

      this.setState({
        optionSelected: name,
        open: false,
        isEmpty: false,
        defaultValueSelected: isDefaultValue,
        fullOptionSelected,
      })
      if (onChange && id) onChange(id)
    }

  getSelected() {
    const { defaultValueSelected, isEmpty } = this.state
    const { value, options, name, placeholder } = this.props
    const selected = options.find(
      ({ id, value: optionValue }) => id === value || optionValue === value
    )
    const fallbackName = placeholder || name || 'No Selected'

    return selected && !(defaultValueSelected || isEmpty) ? selected.name : fallbackName
  }

  toggleOpen = () => {
    const { open } = this.state
    const { onOpenChange } = this.props

    this.setState(
      {
        open: !open,
      },
      () => {
        if (onOpenChange) onOpenChange(!open)
      }
    )
  }

  renderError() {
    const { hasError, required, useTranslations } = this.props
    const { isEmpty, defaultValueSelected } = this.state
    const showMessage = hasError && !!required && (isEmpty || defaultValueSelected)
    const message = 'Please select an option'

    return (
      showMessage && (
        <div className="Demio-Autocomplete-field-error">
          {showMessage && (useTranslations ? getTranslation(`${message}.`) : `${message}.`)}
        </div>
      )
    )
  }

  renderLabel({ label }) {
    const { isEmpty, defaultValueSelected } = this.state
    const { required, useTranslations, showOptional, showLabel } = this.props

    const optional = (
      <span className="Demio-Select-label-optional">
        {useTranslations ? getTranslation('Optional') : 'Optional'}
      </span>
    )
    return (
      <>
        {(!isEmpty || showLabel) && !defaultValueSelected && (
          <span className="Demio-Select-label">{label}</span>
        )}
        {!required && showOptional && optional}
      </>
    )
  }

  renderWithLabel(component) {
    const { name, hasError, useTranslations, className, disabled } = this.props
    const { open } = this.state
    const label = useTranslations ? getTranslation(name || '') : name || ''

    return (
      <div
        className={classNames({
          'Demio-Autocomplete-field': true,
          'Demio-Autocomplete-field-invalid': hasError,
          'Demio-Autocomplete-field-hint': open,
          [className]: className,
        })}>
        <label>
          {!disabled && this.renderLabel({ label })}
          {component}
        </label>
        {hasError && this.renderError()}
      </div>
    )
  }

  renderWithoutLabel(component) {
    const { name, hasError, useTranslations, className } = this.props
    const { open } = this.state
    const label = useTranslations ? getTranslation(name || '') : name || ''

    return (
      <div
        className={classNames({
          'Demio-Autocomplete-field': true,
          'Demio-Autocomplete-field-invalid': hasError,
          'Demio-Autocomplete-field-hint': open,
          [className]: className,
        })}>
        <label>{component}</label>
        {hasError && this.renderError()}
      </div>
    )
  }

  renderSelectDropdown = () => {
    const { options, customOptionsRender, customOptionProps, placeholder, name } = this.props
    const filteredOptions = options.filter(({ id, value }) => id || value)
    const { optionSelected, open } = this.state
    const fallbackName = placeholder || name || 'No Selected'
    return (
      <div>
        <Select
          showSearch
          open={open}
          autoFocus={open}
          showAction={['focus', 'click']}
          ref={this.selectElement}
          onFocus={() => {
            this.setState({ open: true })
            this.selectElement.current.rcSelect.inputRef.focus()
          }}
          onBlur={() => {
            this.setState({ open: false })
          }}
          placement="bottomLeft"
          getPopupContainer={(trigger) => trigger.parentElement}
          onSelect={() => {
            this.setState({ open: false })
            this.selectElement.current.blur()
          }}
          dropdownAlign={{
            offset: [0, -4],
            overflow: {
              adjustX: 0,
              adjustY: 0, // do not auto flip in y-axis
            },
          }}
          placeholder={fallbackName}
          value={optionSelected ? this.getSelected() : undefined}
          suffixIcon={<DemioIcon type="UpArrow" width={16} className="Demio-Select-arrow" />}
          filterOption={(input, option) =>
            option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
          }>
          {filteredOptions.map((option) => {
            const id = option.id || option.value
            const name = option.name || option.text
            const defaultValue = option.defaultValue || false
            const disabled = option.disabled || false
            return (
              <Option
                tabIndex={0}
                key={id}
                data-id={id}
                data-name={name}
                value={option.value}
                data-defaultvalue={defaultValue}
                data-disabled={disabled}
                onClick={this.handleSelectOption({
                  id,
                  name,
                  isDefaultValue: defaultValue,
                  disabled,
                })}
                className={
                  customOptionsRender
                    ? 'Demio-Autocomplete-custom-option'
                    : 'Demio-Autocomplete-option'
                }
                {...(customOptionProps ? customOptionProps(option) : {})}>
                {customOptionsRender ? customOptionsRender(option) : name}
              </Option>
            )
          })}
        </Select>
      </div>
    )
  }

  renderSelect = () => this.renderSelectDropdown()

  render() {
    const { useLabel } = this.props

    return useLabel
      ? this.renderWithLabel(this.renderSelect())
      : this.renderWithoutLabel(this.renderSelect())
  }
}

DemioAutocomplete.defaultProps = {
  useTranslations: false,
  options: [],
  dropdownContainerProps: {},
  showOptional: false,
  useLabel: true,
  hideDropdown: false,
}

DemioAutocomplete.propTypes = propTypes

export default DemioAutocomplete
