import React, { ReactElement } from 'react'
import { Observer } from 'mobx-react'
import { DebouncedState, IState } from 'formstate-x'
import * as base from 'react-icecream'

import { InputWrapper } from './FormItem'

interface CommonInputProps<V> {
  value?: V
}

interface CommonCheckboxProps {
  checked?: boolean
  onChange?: (checked: boolean) => void
}

/** 从 Input 控件的 props 类型中取出 value 的类型 */
type ValueOf<P> = P extends CommonInputProps<infer V> ? V : never

export type WithState<P extends CommonInputProps<any>> = Omit<P, 'value' | 'onChange' | 'defaultValue' | 'state'> & {
  /** 当前字段所对应的状态 */
  state: IState<ValueOf<P>>
}

function isDebouncedState<V>(state: IState<V>): state is DebouncedState<IState<V>, V> {
  return state instanceof DebouncedState
}

/** Bind state to normal input components, such as `TextInput`, `Select`, ... */
export function bindInput<V>(state: IState<V>) {
  const uiState = isDebouncedState(state) ? state.$ : state
  return {
    value: uiState.value,
    onChange(value: V) {
      uiState.onChange(value)
    }
  }
}

function useFieldState<P extends CommonInputProps<any>>(
  { state, ...restProps }: WithState<P>,
  render: (props: P) => ReactElement<any, any> | null
) {
  return (
    <InputWrapper state={state}>
      <Observer render={() => {
        const inputBindings = bindInput(state)
        const inputProps = { ...restProps, ...inputBindings } as unknown as P
        return render(inputProps)
      }} />
    </InputWrapper>
  )
}

// 用于对 common checkbox（即 Checkbox & Radio）的适配
export type WithStateForCommonCheckbox<P extends CommonCheckboxProps> = Omit<P, 'checked' | 'onChange' | 'defaultChecked' | 'state'> & {
  /** 当前字段所对应的状态 */
  state?: IState<boolean>
}

/** Bind state to input components which use `checked` as value, such as `Checkbox`, `Radio`, `Switch` */
export function bindCheckedInput(state: IState<boolean>) {
  const { value, onChange } = bindInput(state)
  return { checked: value, onChange }
}

function useFieldStateForCommonCheckbox<P extends CommonCheckboxProps>(
  { state, ...restProps }: WithStateForCommonCheckbox<P>,
  render: (props: P) => ReactElement<any, any> | null
) {
  return (
    <InputWrapper state={state}>
      <Observer render={() => {
        const inputBindings = state != null ? bindCheckedInput(state) : undefined
        const inputProps = { ...restProps, ...inputBindings } as P
        return render(inputProps)
      }} />
    </InputWrapper>
  )
}

export type TextInputProps = WithState<base.TextInputProps>

export function TextInput(props: TextInputProps) {
  return useFieldState<base.TextInputProps>(props, inputProps => (
    <base.TextInput {...inputProps} />
  ))
}

export type PasswordInputProps = WithState<base.PasswordInputProps>

export function PasswordInput(props: PasswordInputProps) {
  return useFieldState<base.PasswordInputProps>(props, inputProps => (
    <base.PasswordInput {...inputProps} />
  ))
}

export type NumberInputProps = WithState<base.NumberInputProps>

export function NumberInput(props: NumberInputProps) {
  return useFieldState<base.NumberInputProps>(props, inputProps => (
    <base.NumberInput {...inputProps} />
  ))
}

export type TextAreaProps = WithState<base.TextAreaProps>

export function TextArea(props: TextAreaProps) {
  return useFieldState<base.TextAreaProps>(props, inputProps => (
    <base.TextArea {...inputProps} />
  ))
}

export type DatePickerProps = WithState<base.DatePickerProps>

export function DatePicker(props: DatePickerProps) {
  return useFieldState<base.DatePickerProps>(props, inputProps => (
    <base.DatePicker {...inputProps} />
  ))
}

export type RangeDatePickerProps = WithState<base.RangeDatePickerProps>

export function RangeDatePicker(props: RangeDatePickerProps) {
  return useFieldState<base.RangeDatePickerProps>(props, inputProps => (
    <base.RangeDatePicker {...inputProps} />
  ))
}

export type SelectProps<T extends base.SelectValue> = WithState<base.SelectProps<T>>

export function Select<T extends base.SelectValue>(props: SelectProps<T>) {
  return useFieldState<base.SelectProps<T>>(props, inputProps => (
    <base.Select {...inputProps} />
  ))
}

export type MultiSelectProps<T extends base.SelectValue> = WithState<base.MultiSelectProps<T>>

export function MultiSelect<T extends base.SelectValue>(props: MultiSelectProps<T>) {
  return useFieldState<base.MultiSelectProps<T>>(props, inputProps => (
    <base.MultiSelect {...inputProps} />
  ))
}

export type CheckboxGroupProps<
  T extends base.CheckboxValue = base.CheckboxValue
> = WithState<base.CheckboxGroupProps<T>>

export function CheckboxGroup<T extends base.CheckboxValue = base.CheckboxValue>(
  props: CheckboxGroupProps<T>
) {
  return useFieldState<base.CheckboxGroupProps<T>>(props, inputProps => (
    <base.CheckboxGroup {...inputProps} />
  ))
}

export type CheckboxProps<
  T extends base.CheckboxValue = base.CheckboxValue
> = WithStateForCommonCheckbox<base.CheckboxProps<T>>

export function Checkbox<T extends base.CheckboxValue = base.CheckboxValue>(
  props: CheckboxProps<T>
) {
  return useFieldStateForCommonCheckbox(props, inputProps => (
    <base.Checkbox {...inputProps} />
  ))
}

export type RadioGroupProps<T extends base.RadioValue = base.RadioValue> = WithState<base.RadioGroupProps<T>>

export function RadioGroup<T extends base.RadioValue = base.RadioValue>(props: RadioGroupProps<T>) {
  return useFieldState<base.RadioGroupProps<T>>(props, inputProps => (
    <base.RadioGroup {...inputProps} />
  ))
}

export type RadioProps<
  T extends base.RadioValue = base.RadioValue
> = WithStateForCommonCheckbox<base.RadioProps<T>>

export function Radio<T extends base.RadioValue = base.RadioValue>(
  props: RadioProps<T>
) {
  return useFieldStateForCommonCheckbox(props, inputProps => (
    <base.Radio {...inputProps} />
  ))
}

export type SwitchProps = Omit<base.SwitchProps, 'checked' | 'onChange' | 'defaultChecked' | 'state'> & {
  /** 当前字段所对应的状态 */
  state: IState<boolean>
}

function useFieldStateForSwitch<P extends base.SwitchProps>(
  { state, ...restProps }: SwitchProps,
  render: (props: P) => ReactElement<any, any> | null
) {
  return (
    <InputWrapper state={state}>
      <Observer render={() => {
        const inputBindings = bindCheckedInput(state)
        const inputProps = { ...restProps, ...inputBindings } as P
        return render(inputProps)
      }} />
    </InputWrapper>
  )
}

export function Switch(props: SwitchProps) {
  return useFieldStateForSwitch<base.SwitchProps>(props, inputProps => (
    <base.Switch {...inputProps} />
  ))
}

export type CascaderProps<T extends base.CascaderValue> = WithState<base.CascaderProps<T>>

export function Cascader<T extends base.CascaderValue>(props: CascaderProps<T>) {
  return useFieldState<base.CascaderProps<T>>(props, inputProps => (
    <base.Cascader {...inputProps} />
  ))
}

export type MultiCascaderProps<T extends base.CascaderValue> = WithState<base.MultiCascaderProps<T>>

export function MultiCascader<T extends base.CascaderValue>(props: MultiCascaderProps<T>) {
  return useFieldState<base.MultiCascaderProps<T>>(props, inputProps => (
    <base.MultiCascader {...inputProps} />
  ))
}

export type AutoCompleteProps = WithState<base.AutoCompleteProps>

export function AutoComplete(props: AutoCompleteProps) {
  return useFieldState<base.AutoCompleteProps>(props, inputProps => (
    <base.AutoComplete {...inputProps} />
  ))
}

export type TagsInputProps = WithState<base.TagsInputProps>

export function TagsInput(props: TagsInputProps) {
  return useFieldState<base.TagsInputProps>(props, inputProps => (
    <base.TagsInput {...inputProps} />
  ))
}

export type SliderProps = WithState<base.SliderProps>

export function Slider(props: SliderProps) {
  return useFieldState<base.SliderProps>(props, inputProps => (
    <base.Slider {...inputProps} />
  ))
}

export type UploadProps = WithState<base.UploadProps>

export function Upload(props: UploadProps) {
  return useFieldState<base.UploadProps>(props, inputProps => (
    <base.Upload {...inputProps} />
  ))
}

export type DragUploadProps = WithState<base.DragUploadProps>

export function DragUpload(props: DragUploadProps) {
  return useFieldState<base.DragUploadProps>(props, inputProps => (
    <base.DragUpload {...inputProps} />
  ))
}
