/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/role-has-required-aria-props */
/* eslint-disable react/jsx-props-no-spreading */
import React, { Component, createRef } from 'react'
import classNames from 'classnames'
import { arrayOf, bool, func, number, oneOfType, shape, string } from 'prop-types'
import DemioIcon from '../Icon/Icon'
import './DemioSelect.less'

// TODO:
//  - remove extra props
//  - remove translation from component - it should get already translated texts from props
class DemioSelect extends Component {
  constructor(props) {
    super(props)
    this.state = {
      open: props.forceOpen || false,
      optionSelected: null,
      isEmpty: true,
      defaultValueSelected: false,
      fullOptionSelected: {},
    }

    this.selectElement = createRef()
  }

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

    this.setDefaultValue()

    if (!selectElement) return

    document.body.addEventListener('click', this.handleBodyClick)

    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 (selectedID !== value && selectedValue !== value) {
        // 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
        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) {
        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)
        }
      }
    }

  handleBodyClick = ({ target }) => {
    const { externalDropdownContainer, customValueContainer } = this.props
    const { open } = this.state
    if (!open) return

    // this is for when we use a dropdown such as UserSelectPopover
    const isExternalDropdown = target.closest(externalDropdownContainer)

    // handle focus/blur and selecting an option
    if (target.closest('.Demio-Select-options') && !isExternalDropdown) {
      const customOption = target.closest('.Demio-Select-custom-option')
      let { dataset } = target

      if (customOption) {
        dataset = customOption.dataset
      }

      const { id, name, defaultvalue = 'false', disabled = 'false' } = dataset || {}
      const isDefaultValue = defaultvalue === 'true'
      const isDisabled = disabled === 'true'
      this.handleSelectOption({ id, name, isDefaultValue, disabled: isDisabled })()
    } else {
      // this is to fix it closing and then opening again from the onClick
      if (target === this.selectElement.current || target.closest(customValueContainer)) return

      this.setState({
        open: false,
      })
    }
  }

  getSelected() {
    const { defaultValueSelected, isEmpty } = this.state
    const { value, options, name, placeholder, label } = this.props
    const selected = options.find(
      ({ id, value: optionValue }) => id === value || optionValue === value
    )
    const fallbackName = placeholder || label || 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, getTranslation, errorMessage } = this.props
    const { isEmpty, defaultValueSelected } = this.state
    const showMessage = hasError && !!required && (isEmpty || defaultValueSelected)
    const message = errorMessage || 'Please select an option.'

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

  renderLabel(label) {
    const { isEmpty, defaultValueSelected } = this.state
    const { required, useTranslations, showOptional, showLabel, getTranslation } = 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 { label, hasError, useTranslations, getTranslation, className, disabled, name } =
      this.props
    const { open } = this.state
    const labelText = useTranslations ? getTranslation(label || name || '') : label || name || ''

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

  renderSelectDropdown = () => {
    const {
      options,
      dropdownContainerProps,
      customOptionsRender,
      customOptionProps,
      hideDropdown,
      value,
      optionClassName,
    } = this.props
    const { open } = this.state
    const filteredOptions = options.filter((option) => option.id || option.value)

    if (!(open && options.length) || hideDropdown) return null

    return (
      <div
        className={classNames({
          'Demio-Select-options': true,
          '--custom-options': customOptionsRender,
        })}
        {...dropdownContainerProps}>
        {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 (
            <div
              role="option"
              tabIndex={0}
              key={id}
              data-id={id}
              data-name={name}
              data-defaultvalue={defaultValue}
              data-disabled={disabled}
              onClick={this.handleSelectOption({
                id,
                name,
                isDefaultValue: defaultValue,
                disabled,
              })}
              className={classNames({
                'Demio-Select-custom-option': !!customOptionsRender,
                'Demio-Select-option': !customOptionsRender,
                [optionClassName]: !!optionClassName,
              })}
              {...(customOptionProps ? customOptionProps(option) : {})}>
              {customOptionsRender ? customOptionsRender(option, value) : name}
            </div>
          )
        })}
      </div>
    )
  }

  renderSelect = () => {
    const { className, customValueRender, id, noArrow, useLabel, disabled, value, name } =
      this.props
    const { optionSelected, open, defaultValueSelected, fullOptionSelected } = this.state

    return (
      <div
        className={classNames({
          'Demio-Select--open': open,
          'Demio-Select': true,
          [className]: className && !useLabel,
          '--disabled': disabled,
        })}
        id={id || name}>
        <div
          className={classNames({
            'Demio-Select-selection': true,
            '--selected': optionSelected,
            '--no-arrow': noArrow,
            '--default-option-selected': defaultValueSelected || !value,
          })}
          onClick={!disabled ? this.toggleOpen : () => null}
          ref={this.selectElement}
          tabIndex={0}
          role="button">
          {customValueRender
            ? customValueRender(fullOptionSelected)
            : optionSelected || this.getSelected()}
          <DemioIcon type="UpArrow" width={16} className="Demio-Select-arrow" />
        </div>
        {this.renderSelectDropdown()}
      </div>
    )
  }

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

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

DemioSelect.propTypes = {
  externalDropdownContainer: string,
  customValueContainer: string,
  value: oneOfType([string, number]),
  options: arrayOf(
    shape({
      id: oneOfType([string, number]),
      value: oneOfType([string, number]),
      name: oneOfType([string, number]),
      text: oneOfType([string, number]),
      defaultValue: bool,
      disabled: bool,
    })
  ),
  onChange: func.isRequired,
  name: string.isRequired,
  placeholder: string,
  label: string,
  required: bool,
  hasError: bool,
  className: string,
  useLabel: bool,
  id: string,
  dropdownContainerProps: shape({}),
  customOptionProps: func,
  hideDropdown: bool,
  customValueRender: func,
  useTranslations: bool,
  customOptionsRender: func,
  onOpenChange: func,
  getTranslation: func,
  noArrow: bool,
  disabled: bool,
  showOptional: bool,
  showLabel: bool,
  forceOpen: bool,
  optionClassName: string,
  errorMessage: string,
}

DemioSelect.defaultProps = {
  externalDropdownContainer: null,
  customValueContainer: null,
  className: '',
  label: '',
  placeholder: '',
  id: '',
  value: '',
  noArrow: false,
  showLabel: false,
  disabled: false,
  forceOpen: false,
  hasError: false,
  required: false,
  useTranslations: false,
  options: [],
  getTranslation: (value) => value,
  customOptionProps: null,
  onOpenChange: null,
  customValueRender: null,
  customOptionsRender: null,
  dropdownContainerProps: {},
  showOptional: false,
  useLabel: true,
  hideDropdown: false,
  optionClassName: '',
  errorMessage: '',
}

export default DemioSelect
