import React, { useCallback, useContext, useEffect, useMemo } from 'react'
import { useTranslation } from 'next-i18next'
import {
  IOptions,
  IRule,
  IRuleAttribute,
  ITreeItem,
  RuleAttributeType,
  RuleType,
  RuleValueType,
  flatTree,
  formatFloatValue,
  formatIntegerValue,
  getAttributeRuleValueType,
  isAttributeRule,
  isAttributeRuleValueMultiple,
  isCombinationRule,
  ruleArrayValueSeparator,
  ruleValueNumberMultipleTypes,
  ruleValueNumberTypes,
} from '@elastic-suite/gally-admin-shared'

import { ruleOptionsContext } from '../../../contexts'

import IonIcon from '../IonIcon/IonIcon'
import { Close, CustomCombination, Root } from './Rule.styled'
import DropDown from '../form/DropDown'
import TreeSelector from '../form/TreeSelector'
import InputText from '../form/InputText'
import DatePickerError from '../form/DatePicker'
import InputNumber, { createAdditionalValidator } from '../form/InputNumber'
import TextFieldTags from '../form/TextFieldTags'

interface IProps {
  catalogId: number
  localizedCatalogId: number
  onChange?: (rule: IRule) => void
  onDelete?: () => void
  rule: IRule
  small?: boolean
  showError?: boolean
}

type Formatter = (item: string) => string

const ruleTypeFormatterMap: Partial<Record<RuleValueType, Formatter>> = {
  [RuleValueType.FLOAT_MULTIPLE]: formatFloatValue,
  [RuleValueType.INT_MULTIPLE]: formatIntegerValue,
}

function formatMultipleValue(
  values: string,
  valueType: RuleValueType
): string[] {
  const formatter: Formatter =
    ruleTypeFormatterMap[valueType] ?? ((x: string): string => x)
  return values
    .split(ruleArrayValueSeparator)
    .map((item) => formatter(item.trim()))
    .filter((item) => item !== '')
}

function Rule(props: IProps): JSX.Element {
  const {
    catalogId,
    showError,
    localizedCatalogId,
    onChange,
    onDelete,
    rule,
    small,
  } = props
  const {
    getAttributeOperatorOptions,
    getAttributeType,
    loadAttributeValueOptions,
    operatorsValueType,
    options,
  } = useContext(ruleOptionsContext)
  const { t } = useTranslation('rules')

  useEffect(() => {
    if (isAttributeRule(rule)) {
      loadAttributeValueOptions(rule.field)
    }
  }, [loadAttributeValueOptions, rule])

  const categories = useMemo(
    () =>
      (options.get(
        `type-${RuleAttributeType.CATEGORY}-${catalogId}-${localizedCatalogId}`
      ) ?? []) as ITreeItem[],
    [catalogId, localizedCatalogId, options]
  )
  const flatCategories: ITreeItem[] = useMemo(() => {
    const flat: ITreeItem[] = []
    flatTree(categories, flat)
    return flat
  }, [categories])

  function handleChange(property: string) {
    return (value: unknown): void => onChange({ ...rule, [property]: value })
  }

  function handleCategoryChange(value: ITreeItem | ITreeItem[]): void {
    if (value instanceof Array) {
      onChange({
        ...rule,
        value: value.map((category) => category.id as string),
      })
    } else {
      onChange({ ...rule, value: value.id })
    }
  }

  const handleInputChange = useCallback(
    (multiple: boolean, valueType: RuleValueType) => {
      return (value: string): void => {
        if (multiple) {
          onChange({
            ...rule,
            value: formatMultipleValue(value, valueType),
          })
        } else {
          onChange({ ...rule, value })
        }
      }
    },
    [rule, onChange]
  )

  function handleFieldChange(value: string): void {
    onChange({
      ...rule,
      attribute_type: getAttributeType(value) ?? '',
      field: value ?? '',
      value: [],
    } as IRuleAttribute)
  }

  function handleOperatorChange(operator: string): void {
    const attributeRule = rule as IRuleAttribute
    const newRule: IRuleAttribute = {
      ...attributeRule,
      operator,
    }
    const prevType = getAttributeRuleValueType(
      attributeRule,
      operatorsValueType
    )
    const newType = getAttributeRuleValueType(newRule, operatorsValueType)
    onChange({
      ...newRule,
      value:
        prevType === newType
          ? rule.value
          : isAttributeRuleValueMultiple(newType)
          ? []
          : '',
    })
  }

  const handleMultipleTagsChange = useCallback(
    (tags: string[], valueType: RuleValueType): void => {
      handleInputChange(true, valueType)(tags.join(ruleArrayValueSeparator))
    },
    [handleInputChange]
  )

  function getAttributeValueComponent(rule: IRuleAttribute): JSX.Element {
    const { attribute_type, field, value } = rule
    const valueType = getAttributeRuleValueType(rule, operatorsValueType)
    const multiple = isAttributeRuleValueMultiple(valueType)

    if (valueType === RuleValueType.BOOLEAN) {
      return (
        <DropDown
          showError={showError}
          onChange={handleChange('value')}
          options={
            (options.get(`type-${RuleValueType.BOOLEAN}`) ??
              []) as IOptions<boolean>
          }
          required
          small={small}
          value={value}
        />
      )
    } else if (attribute_type === RuleAttributeType.SELECT) {
      return (
        <DropDown
          showError={showError}
          multiple={multiple}
          onChange={handleChange('value')}
          options={
            (options.get(`${field}-${localizedCatalogId}`) ??
              []) as IOptions<unknown>
          }
          required
          small={small}
          value={value}
        />
      )
    } else if (attribute_type === RuleAttributeType.CATEGORY) {
      const treeSelectorValue =
        value instanceof Array
          ? flatCategories.filter((category) =>
              (value as string[]).includes(category.id as string)
            )
          : flatCategories.find((category) => value === category.id) ?? null
      return (
        <TreeSelector
          showError={showError}
          data={categories}
          multiple={multiple}
          onChange={handleCategoryChange}
          required
          small={small}
          value={treeSelectorValue}
        />
      )
    } else if (attribute_type === RuleAttributeType.DATE) {
      // We ensure to set an empty string (not null) in case of an empty date
      // Because rule engine validator explicitly test the empty string case
      // to check the existence of a value
      const datePickerValue =
        (typeof value === 'string' && value) || value instanceof Date
          ? new Date(value)
          : ''

      return (
        <DatePickerError
          showError={showError}
          value={datePickerValue}
          required
          onChange={handleChange('value')}
        />
      )
    } else if (ruleValueNumberTypes.includes(valueType) && !multiple) {
      return (
        <InputNumber
          showError={showError}
          onChange={handleInputChange(false, valueType)}
          required
          small={small}
          numberType={valueType === RuleValueType.INT ? 'integer' : 'float'}
          value={value as string}
        />
      )
    } else if (multiple) {
      let additionalValidator = undefined
      if (ruleValueNumberMultipleTypes.includes(valueType)) {
        additionalValidator = (values: string[]): string => {
          for (const value of values) {
            const error = createAdditionalValidator(
              valueType === RuleValueType.INT_MULTIPLE
            )(value)
            if (error !== '') {
              return error
            }
          }
          return ''
        }
      }

      const displayValue = formatMultipleValue(
        (value as string[]).join(ruleArrayValueSeparator),
        valueType
      )

      return (
        <TextFieldTags
          showError={showError}
          required
          withCleanButton
          onChange={(tags: string[]): void => {
            handleMultipleTagsChange(tags, valueType)
          }}
          value={displayValue}
          additionalValidator={additionalValidator}
        />
      )
    }

    return (
      <InputText
        showError={showError}
        onChange={handleInputChange(false, valueType)}
        required
        small={small}
        type="text"
        value={value as string}
      />
    )
  }

  let content
  if (isCombinationRule(rule)) {
    const { operator, value } = rule
    content = (
      <>
        <DropDown
          showError={showError}
          onChange={handleChange('operator')}
          options={
            (options.get(`${RuleType.COMBINATION}-operator`) ??
              []) as IOptions<string>
          }
          required
          small={small}
          value={operator}
        />
        <CustomCombination>{t('conditionsAre')}</CustomCombination>
        <DropDown
          showError={showError}
          onChange={handleChange('value')}
          options={
            (options.get(`type-${RuleValueType.BOOLEAN}`) ??
              []) as IOptions<boolean>
          }
          required
          small={small}
          value={value}
        />
      </>
    )
  } else if (isAttributeRule(rule)) {
    const { field, operator } = rule
    content = (
      <>
        <DropDown
          showError={showError}
          onChange={handleFieldChange}
          options={
            (options.get(`${RuleType.ATTRIBUTE}-field`) ??
              []) as IOptions<string>
          }
          required
          value={field}
          small={small}
        />
        <DropDown
          showError={showError}
          onChange={handleOperatorChange}
          options={getAttributeOperatorOptions(field)}
          required
          small={small}
          transparent
          value={operator}
        />
        {getAttributeValueComponent(rule)}
      </>
    )
  }

  return (
    <Root>
      {content}
      <Close onClick={onDelete}>
        <IonIcon name="close" style={{ fontSize: '17.85px' }} />
      </Close>
    </Root>
  )
}

export default Rule
