import moment from 'moment'
import React from 'react'

import Switch from '../Switch/Switch'
import DatePicker from '../DatePicker/DatePicker'
import FormLabel from '../FormLabel/FormLabel'
import { hasValue } from '../../services/HelperServiceTyped'
import ListLoading from '../Loaders/ListLoading'
import MultiSelect from '../MultiSelect/MultiSelect'
import type { SearchBarProps } from '../SearchBar/SearchBar'
import Select from '../Form/Select/Select'
import type { SelectProps } from '../Form/Select/Select'
import type { TooltipProps } from '../Tooltip/Tooltip'
import styles from './_filter.module.scss'
import { c } from '../../translations/LibraryTranslationService'

const TextInput = React.lazy(
  () => import(/* webpackChunkName: "textInput" */ '../Form/TextInput'),
)
const Picker = React.lazy(
  () => import(/* webpackChunkName: "picker" */ '../Picker/Picker'),
)

type FilterMenuPropsType<OptionType> = {
  /** An object containing an entry for each filter input component */
  states: FilterStatesType<OptionType>
  /** A callback for when the form closes */
  close: () => void
  /** A callback for when the form's inputs change */
  onChangeCallout?: (...params: unknown[]) => void
  /** A boolean specifying whether the filters are still loading or not */
  loading?: boolean
  /** A boolean specifying whether to show additional child components above or below those specified in states */
  topChildren?: boolean
  /** A boolean specifying whether the sidedrawer with the filter form is open */
  isOpen: boolean
  /** Anything which you would like to add in addition to Filter options- unique filter types, header/footer information, etc  */
  children?: (props: {
    isOpen?: boolean
    close?: () => void
  }) => React.ReactNode
}

type FormLabelProps = React.ComponentProps<typeof FormLabel>

type FilterStateOptionType = {
  type: 'select' | 'dates' | 'text' | 'toggle' | 'switch' | 'multi-select'
  stateName: string
  rightLabel?: string
  customClass?: string
  qaTestId?: string
}

type DatePickerProps = React.ComponentProps<typeof DatePicker>
type FilterDateType = moment.Moment | Date | string | undefined | null

interface DatesStatePropType extends FilterStateOptionType {
  type: 'dates'
  labelText: string
  disabled?: FormLabelProps['disabled']
  defaultValue: {
    start_date: FilterDateType
    end_date?: FilterDateType
  }
  dateRestrictions?: DatePickerProps['dateRestrictions']
  singleDate?: boolean
  position?: DatePickerProps['position']
}

type TextInputProps = React.ComponentProps<typeof TextInput>

interface TextStatePropType extends FilterStateOptionType {
  type: 'text'
  labelText: string
  defaultValue?: TextInputProps['value']
  onBlurCallout?: TextInputProps['onBlurCallout']
  inputType?: TextInputProps['type']
  clearCallout?: TextInputProps['clearCallout']
  onReturnCallout?: TextInputProps['onReturnCallout']
  numberProps?: Pick<
    TextInputProps,
    'min' | 'max' | 'step' | 'onlyWholeNumbers' | 'onlyPositiveNumbers'
  > & { suffix?: string }
  placeholder?: TextInputProps['placeholder']
  disabled?: TextInputProps['disabled']
  numberFormat?: TextInputProps['numberFormat']
  inputLabel?: TextInputProps['inputLabel']
  labelTooltip?: TextInputProps['labelTooltip']
  keyUp?: () => void
  shouldClose?: boolean
}

type PickerProps = React.ComponentProps<typeof Picker>

interface ToggleStatePropType extends FilterStateOptionType {
  type: 'toggle'
  labelText?: string
  options: PickerProps['options']
  state?: PickerProps['state']
  defaultValue?: PickerProps['selected']
  labelTooltip?: PickerProps['labelTooltip']
  disabled?: boolean
}

type MultiSelectProps = React.ComponentProps<typeof MultiSelect>

type MultiSelectStatePropType = FilterStateOptionType &
  Omit<MultiSelectProps, 'callout'> & {
    type: 'multi-select'
  }

type SelectStatePropType<OptionType> = FilterStateOptionType & {
  type: 'select'
  options: SelectProps<OptionType>['options']
  labelText: string
  loading?: SelectProps<OptionType>['loading']
  labelTooltip?: TooltipProps
  defaultValue: SelectProps<OptionType>['selectedItem']
  optionKeyName: string
  isError?: SelectProps<OptionType>['error']
  searchBar?: boolean
  required?: boolean
  disabled?: boolean
  onSearchChange?: SearchBarProps['onChange']
}

type SwitchProps = React.ComponentProps<typeof Switch>

interface SwitchStatePropType extends FilterStateOptionType {
  type: 'switch'
  labelText: string
  labelTooltip: FormLabelProps['tooltip']
  disabled?: FormLabelProps['disabled']
  defaultValue: SwitchProps['checked']
}

export type FilterStatesType<OptionType> = Record<
  string,
  | SelectStatePropType<OptionType>
  | DatesStatePropType
  | TextStatePropType
  | ToggleStatePropType
  | SwitchStatePropType
  | MultiSelectStatePropType
>

export const FilterMenu = <OptionType,>({
  states,
  close,
  onChangeCallout,
  loading,
  topChildren,
  isOpen,
  children,
}: FilterMenuPropsType<OptionType>): React.JSX.Element => {
  const updateFilters = (name: string, value: unknown) => {
    onChangeCallout?.(name, value)
  }
  const handleInput = (name: string, value: unknown, textInput?: string) => {
    if (!textInput) {
      hasValue(value) && updateFilters(name, value)
    } else {
      updateFilters(name, value)
    }
  }

  const clearInput = (name: string, clearCallout: (() => void) | undefined) => {
    handleInput(name, '', 'textInput')
    clearCallout?.()
  }

  const handleDates = ({
    start,
    end,
    stateName,
  }: {
    start: FilterDateType
    end?: FilterDateType
    stateName: string
  }) => {
    onChangeCallout?.(stateName ?? 'dates', {
      start_date: start,
      ...(end ? { end_date: end } : {}),
    })
  }

  const inputKeyUp = (callout?: () => void, shouldClose?: boolean) => {
    if (callout) {
      callout()
    }
    if (shouldClose) {
      close()
    }
  }

  return (
    <>
      {!loading ? (
        <div className={styles.filterMenuStyle}>
          {topChildren && children?.({ close, isOpen })}
          <React.Suspense fallback={<ListLoading />}>
            {Object.entries(states).map(([stateKey, s]) => {
              switch (s.type) {
                case 'select':
                  return (
                    <Select
                      key={stateKey}
                      labelProps={{
                        label: s.labelText,
                        tooltip: s?.labelTooltip,
                        rightLabel: s?.rightLabel,
                      }}
                      options={s.options}
                      selectedItem={s.defaultValue}
                      optionKeyName={
                        s.optionKeyName as SelectProps<OptionType>['optionKeyName']
                      }
                      labelKeyName={
                        s.optionKeyName as SelectProps<OptionType>['optionKeyName']
                      }
                      onChange={(option) => {
                        handleInput(s.stateName, option)
                      }}
                      required={s.required}
                      error={s.isError}
                      disabled={
                        s.disabled || !(s.options.length > 0) || s.isError
                      }
                      searchBarProps={{
                        showSearchBar: s.searchBar,
                        onChange: s.onSearchChange,
                      }}
                      qaTestId={s?.qaTestId}
                    />
                  )
                case 'dates':
                  return (
                    <div key={stateKey}>
                      <FormLabel label={s.labelText} disabled={s?.disabled} />
                      {s.singleDate ? (
                        <DatePicker
                          type='single'
                          selectedDate={
                            (states[s.stateName] as DatesStatePropType)
                              ?.defaultValue.start_date
                              ? moment(
                                  (states[s.stateName] as DatesStatePropType)
                                    ?.defaultValue.start_date,
                                ).toDate()
                              : undefined
                          }
                          onDateChange={(date) => {
                            const startDate = date as FilterDateType
                            if (date) {
                              handleDates({
                                start: startDate,
                                ...(!s.singleDate ? { end: undefined } : {}),
                                stateName: s.stateName,
                              })
                            } else {
                              handleDates({
                                start: undefined,
                                ...(!s.singleDate ? { end: undefined } : {}),
                                stateName: s.stateName,
                              })
                            }
                          }}
                          dateRestrictions={s.dateRestrictions}
                          position={s.position}
                          qaTestId={s?.qaTestId}
                        />
                      ) : (
                        <DatePicker
                          type='range'
                          selectedDateRange={{
                            from: (states[s.stateName] as DatesStatePropType)
                              ?.defaultValue.start_date
                              ? moment(
                                  (states[s.stateName] as DatesStatePropType)
                                    ?.defaultValue.start_date,
                                ).toDate()
                              : undefined,
                            to: (states[s.stateName] as DatesStatePropType)
                              ?.defaultValue.end_date
                              ? moment(
                                  (states[s.stateName] as DatesStatePropType)
                                    ?.defaultValue.end_date,
                                ).toDate()
                              : undefined,
                          }}
                          onDateChange={(date) => {
                            if (date && 'from' in date) {
                              const { from, to } = date
                              handleDates({
                                start: from,
                                ...(!s.singleDate ? { end: to } : {}),
                                stateName: s.stateName,
                              })
                            } else {
                              handleDates({
                                start: undefined,
                                ...(!s.singleDate ? { end: undefined } : {}),
                                stateName: s.stateName,
                              })
                            }
                          }}
                          dateRestrictions={s.dateRestrictions}
                          qaTestId={s?.qaTestId}
                        />
                      )}
                    </div>
                  )
                case 'text':
                  return (
                    <div key={stateKey}>
                      {s?.inputType === 'number' ? (
                        <TextInput
                          value={s.defaultValue || ''}
                          labelTooltip={s?.labelTooltip}
                          rightLabel={s?.rightLabel}
                          onBlurCallout={
                            s.onBlurCallout ? s.onBlurCallout : undefined
                          }
                          callout={(name, value) =>
                            handleInput(name as string, value, 'textInput')
                          }
                          onReturnCallout={() => {
                            s.onReturnCallout?.()
                            s.shouldClose && close()
                          }}
                          type={'number'}
                          stateName={s.stateName}
                          classType={`${s.customClass ? s.customClass : ''}`}
                          labelText={s.labelText}
                          keyUp={() => inputKeyUp(s.keyUp, s.shouldClose)}
                          clear={Boolean(s.clearCallout)}
                          clearCallout={() =>
                            clearInput(s.stateName, s.clearCallout)
                          }
                          placeholder={
                            s?.numberProps?.suffix
                              ? s.placeholder + s?.numberProps?.suffix
                              : s.placeholder
                          }
                          {...(s?.numberProps
                            ? {
                                min: s?.numberProps?.min,
                                max: s?.numberProps?.max,
                                step: s?.numberProps?.step,
                                maxLength: s?.numberProps?.max,
                                onlyWholeNumbers:
                                  s?.numberProps?.onlyWholeNumbers,
                                onlyPositiveNumbers:
                                  s?.numberProps?.onlyPositiveNumbers,
                              }
                            : {})}
                          numberFormat={s?.numberFormat}
                          inputLabel={s?.inputLabel}
                          qaTestId={s?.qaTestId}
                        />
                      ) : (
                        <TextInput
                          value={s.defaultValue || ''}
                          labelTooltip={s?.labelTooltip}
                          rightLabel={s?.rightLabel}
                          onBlurCallout={
                            s.onBlurCallout ? s.onBlurCallout : undefined
                          }
                          callout={(name, value) =>
                            handleInput(name as string, value, 'textInput')
                          }
                          onReturnCallout={() => {
                            s.onReturnCallout?.()
                            s.shouldClose && close()
                          }}
                          type={s?.inputType ? s?.inputType : 'text'}
                          stateName={s.stateName}
                          classType={`${s.customClass ? s.customClass : ''}`}
                          labelText={s.labelText}
                          keyUp={() => inputKeyUp(s.keyUp, s.shouldClose)}
                          clear={Boolean(s.clearCallout)}
                          clearCallout={() =>
                            clearInput(s.stateName, s.clearCallout)
                          }
                          placeholder={
                            s?.numberProps?.suffix
                              ? s.placeholder + s?.numberProps?.suffix
                              : s.placeholder
                          }
                          inputLabel={s?.inputLabel}
                          qaTestId={s?.qaTestId}
                        />
                      )}
                    </div>
                  )
                case 'toggle':
                  return (
                    <div key={stateKey}>
                      <Picker
                        options={s.options}
                        labelTooltip={s?.labelTooltip}
                        callout={(val) => handleInput(s?.stateName, val)}
                        stateName={s.stateName}
                        selected={s.defaultValue}
                        customClass={s.customClass}
                        rightLabel={s.rightLabel}
                        labelText={s.labelText}
                        disabled={s?.disabled}
                        qaTestId={s?.qaTestId}
                      />
                    </div>
                  )
                case 'multi-select':
                  return (
                    <MultiSelect
                      key={stateKey}
                      formLabelProps={s?.formLabelProps}
                      options={s?.options}
                      labelKey={s?.labelKey}
                      loading={s?.loading ?? false}
                      selectedOptions={s?.selectedOptions}
                      emptyStateProps={s?.emptyStateProps}
                      selectPlaceholder={
                        s.options.length > 0
                          ? s.selectPlaceholder || ''
                          : c('noOptionsAvailable')
                      }
                      searchBarProps={s?.searchBarProps}
                      nestedConfig={s?.nestedConfig}
                      dropdownState={s?.dropdownState}
                      callout={(selectedList) =>
                        updateFilters(
                          s?.stateName,
                          selectedList.map((item) => item[s?.labelKey]),
                        )
                      }
                      fetchData={s?.fetchData as () => void}
                      isFetching={s?.isFetching}
                      isReorderable={s?.isReorderable}
                      disabled={s?.disabled}
                      hideSelectAll={s?.hideSelectAll}
                      sortAlphabetically={s?.sortAlphabetically}
                      hasMore={s?.hasMore ?? false}
                      onFocus={s?.onFocus}
                      qaTestId={s?.qaTestId}
                    />
                  )
                case 'switch':
                  return (
                    <div key={stateKey}>
                      <FormLabel
                        label={s.labelText}
                        tooltip={s?.labelTooltip}
                        rightLabel={s?.rightLabel}
                        disabled={s?.disabled}
                      />
                      <Switch
                        checked={s.defaultValue}
                        callout={(value) => {
                          handleInput(s.stateName, value)
                        }}
                        qaTestId={s?.qaTestId}
                      />
                    </div>
                  )
                default:
                  return null
              }
            })}
          </React.Suspense>
          {!topChildren && children?.({ close, isOpen })}
        </div>
      ) : (
        <ListLoading />
      )}
    </>
  )
}
