import { computed, defineComponent, ref, toRef, watch, watchEffect } from 'vue'

import type { PickerPanelProps } from '.'
import type { ChangeEvent, FocusEventHandler, MouseEventHandler } from '../_util/EventInterface'
import classNames from '../_util/classNames'
import { useElementSize } from '../_util/hooks/_vueuse/useElementSize'
import useMergedState from '../_util/hooks/useMergedState'
import useState from '../_util/hooks/useState'
import type { VueNode } from '../_util/type'
import { warning } from '../vc-util/warning'
import type { ContextOperationRefProps } from './PanelContext'
import { useProvidePanel } from './PanelContext'
import type { PickerBaseProps, PickerDateProps, PickerTimeProps } from './Picker'
import PickerPanel from './PickerPanel'
import PickerTrigger from './PickerTrigger'
import PresetPanel from './PresetPanel'
import { RangeContextProvider } from './RangeContext'
import type { GenerateConfig } from './generate'
import useHoverValue from './hooks/useHoverValue'
import usePickerInput from './hooks/usePickerInput'
import usePresets from './hooks/usePresets'
import useRangeDisabled from './hooks/useRangeDisabled'
import useRangeViewDates from './hooks/useRangeViewDates'
import useTextValueMapping from './hooks/useTextValueMapping'
import useValueTexts from './hooks/useValueTexts'
import type {
  DisabledTimes,
  EventValue,
  PanelMode,
  PickerMode,
  PresetDate,
  RangeValue,
} from './interface'
import type { DateRender } from './panels/DatePanel/DateBody'
import type { SharedTimeProps } from './panels/TimePanel'
import {
  formatValue,
  getClosingViewDate,
  isEqual,
  isSameDate,
  isSameQuarter,
  isSameWeek,
  parseValue,
} from './utils/dateUtil'
import getExtraFooter from './utils/getExtraFooter'
import getRanges from './utils/getRanges'
import getDataOrAriaProps, { getValue, toArray, updateValues } from './utils/miscUtil'
import { elementsContains, getDefaultFormat, getInputSize } from './utils/uiUtil'
import { legacyPropsWarning } from './utils/warnUtil'

function reorderValues<DateType>(
  values: RangeValue<DateType>,
  generateConfig: GenerateConfig<DateType>,
): RangeValue<DateType> {
  if (values && values[0] && values[1] && generateConfig.isAfter(values[0], values[1])) {
    return [values[1], values[0]]
  }

  return values
}

function canValueTrigger<DateType>(
  value: EventValue<DateType>,
  index: number,
  disabled: [boolean, boolean],
  allowEmpty?: [boolean, boolean] | null,
): boolean {
  if (value) {
    return true
  }

  if (allowEmpty && allowEmpty[index]) {
    return true
  }

  if (disabled[(index + 1) % 2]) {
    return true
  }

  return false
}

export type RangeType = 'start' | 'end'

export interface RangeInfo {
  range: RangeType
}

export type RangeDateRender<DateType> = (props: {
  current: DateType
  today: DateType
  info: RangeInfo
}) => VueNode

export interface RangePickerSharedProps<DateType> {
  id?: string
  value?: RangeValue<DateType>
  defaultValue?: RangeValue<DateType>
  defaultPickerValue?: [DateType, DateType]
  placeholder?: [string, string]
  disabled?: boolean | [boolean, boolean]
  disabledTime?: (date: EventValue<DateType>, type: RangeType) => DisabledTimes
  presets?: PresetDate<RangeValue<DateType>>[]
  /** @deprecated Please use `presets` instead */
  ranges?: Record<
    string,
    Exclude<RangeValue<DateType>, null> | (() => Exclude<RangeValue<DateType>, null>)
  >
  separator?: VueNode
  allowEmpty?: [boolean, boolean]
  mode?: [PanelMode, PanelMode]
  onChange?: (values: RangeValue<DateType>, formatString: [string, string]) => void
  onCalendarChange?: (
    values: RangeValue<DateType>,
    formatString: [string, string],
    info: RangeInfo,
  ) => void
  onPanelChange?: (values: RangeValue<DateType>, modes: [PanelMode, PanelMode]) => void
  onFocus?: FocusEventHandler
  onBlur?: FocusEventHandler
  onMousedown?: MouseEventHandler
  onMouseup?: MouseEventHandler
  onMouseenter?: MouseEventHandler
  onMouseleave?: MouseEventHandler
  onClick?: MouseEventHandler
  onOk?: (dates: RangeValue<DateType>) => void
  direction?: 'ltr' | 'rtl'
  autocomplete?: string
  activePickerIndex?: 0 | 1
  dateRender?: RangeDateRender<DateType>
  panelRender?: (originPanel: VueNode) => VueNode
  prevIcon?: VueNode
  nextIcon?: VueNode
  superPrevIcon?: VueNode
  superNextIcon?: VueNode
}

type OmitPickerProps<Props> = Omit<
  Props,
  | 'value'
  | 'defaultValue'
  | 'defaultPickerValue'
  | 'placeholder'
  | 'disabled'
  | 'disabledTime'
  | 'showToday'
  | 'showTime'
  | 'mode'
  | 'onChange'
  | 'onSelect'
  | 'onPanelChange'
  | 'pickerValue'
  | 'onPickerValueChange'
  | 'onOk'
  | 'dateRender'
  | 'presets'
>

type RangeShowTimeObject<DateType> = Omit<SharedTimeProps<DateType>, 'defaultValue'> & {
  defaultValue?: DateType[]
}

export type RangePickerBaseProps<DateType> = {} & RangePickerSharedProps<DateType> &
  OmitPickerProps<PickerBaseProps<DateType>>

export type RangePickerDateProps<DateType> = {
  showTime?: boolean | RangeShowTimeObject<DateType>
} & RangePickerSharedProps<DateType> &
  OmitPickerProps<PickerDateProps<DateType>>

export type RangePickerTimeProps<DateType> = {
  order?: boolean
} & RangePickerSharedProps<DateType> &
  OmitPickerProps<PickerTimeProps<DateType>>

export type RangePickerProps<DateType> =
  | RangePickerBaseProps<DateType>
  | RangePickerDateProps<DateType>
  | RangePickerTimeProps<DateType>

// TMP type to fit for ts 3.9.2
type OmitType<DateType> = Omit<RangePickerBaseProps<DateType>, 'picker'> &
  Omit<RangePickerDateProps<DateType>, 'picker'> &
  Omit<RangePickerTimeProps<DateType>, 'picker'>

type MergedRangePickerProps<DateType> = {
  picker?: PickerMode
} & OmitType<DateType>
function RangerPicker<DateType>() {
  return defineComponent<MergedRangePickerProps<DateType>>({
    name: 'RangerPicker',
    inheritAttrs: false,
    props: [
      'prefixCls',
      'id',
      'popupStyle',
      'dropdownClassName',
      'transitionName',
      'dropdownAlign',
      'getPopupContainer',
      'generateConfig',
      'locale',
      'placeholder',
      'autofocus',
      'disabled',
      'format',
      'picker',
      'showTime',
      'showNow',
      'showHour',
      'showMinute',
      'showSecond',
      'use12Hours',
      'separator',
      'value',
      'defaultValue',
      'defaultPickerValue',
      'open',
      'defaultOpen',
      'disabledDate',
      'disabledTime',
      'dateRender',
      'panelRender',
      'ranges',
      'allowEmpty',
      'allowClear',
      'suffixIcon',
      'clearIcon',
      'pickerRef',
      'inputReadOnly',
      'mode',
      'renderExtraFooter',
      'onChange',
      'onOpenChange',
      'onPanelChange',
      'onCalendarChange',
      'onFocus',
      'onBlur',
      'onMousedown',
      'onMouseup',
      'onMouseenter',
      'onMouseleave',
      'onClick',
      'onOk',
      'onKeydown',
      'components',
      'order',
      'direction',
      'activePickerIndex',
      'autocomplete',
      'minuteStep',
      'hourStep',
      'secondStep',
      'hideDisabledOptions',
      'disabledMinutes',
      'presets',
      'prevIcon',
      'nextIcon',
      'superPrevIcon',
      'superNextIcon',
    ] as any,
    setup(props, { attrs, expose }) {
      const needConfirmButton = computed(
        () => (props.picker === 'date' && !!props.showTime) || props.picker === 'time',
      )
      const presets = computed(() => props.presets)
      const ranges = computed(() => props.ranges)
      const presetList = usePresets(presets, ranges)
      // We record oqqpened status here in case repeat open with picker
      const openRecordsRef = ref<Record<number, boolean>>({})

      const containerRef = ref<HTMLDivElement>(null)
      const panelDivRef = ref<HTMLDivElement>(null)
      const startInputDivRef = ref<HTMLDivElement>(null)
      const endInputDivRef = ref<HTMLDivElement>(null)
      const separatorRef = ref<HTMLDivElement>(null)
      const startInputRef = ref<HTMLInputElement>(null)
      const endInputRef = ref<HTMLInputElement>(null)
      const arrowRef = ref<HTMLDivElement>(null)

      // ============================ Warning ============================
      if (process.env.NODE_ENV !== 'production') {
        legacyPropsWarning(props)
      }

      // ============================= Misc ==============================
      const formatList = computed(() =>
        toArray(
          getDefaultFormat<DateType>(props.format, props.picker, props.showTime, props.use12Hours),
        ),
      )

      // Active picker
      const [mergedActivePickerIndex, setMergedActivePickerIndex] = useMergedState<0 | 1>(0, {
        value: toRef(props, 'activePickerIndex'),
      })

      // Operation ref
      const operationRef = ref<ContextOperationRefProps>(null)

      const mergedDisabled = computed<[boolean, boolean]>(() => {
        const { disabled } = props
        if (Array.isArray(disabled)) {
          return disabled
        }

        return [disabled || false, disabled || false]
      })

      // ============================= Value =============================
      const [mergedValue, setInnerValue] = useMergedState<RangeValue<DateType>>(null, {
        value: toRef(props, 'value'),
        defaultValue: props.defaultValue,
        postState: (values) =>
          props.picker === 'time' && !props.order
            ? values
            : reorderValues(values, props.generateConfig),
      })

      // =========================== View Date ===========================
      // Config view panel
      const [startViewDate, endViewDate, setViewDate] = useRangeViewDates({
        values: mergedValue,
        picker: toRef(props, 'picker'),
        defaultDates: props.defaultPickerValue,
        generateConfig: toRef(props, 'generateConfig'),
      })

      // ========================= Select Values =========================
      const [selectedValue, setSelectedValue] = useMergedState(mergedValue.value, {
        postState: (values) => {
          let postValues = values

          if (mergedDisabled.value[0] && mergedDisabled.value[1]) {
            return postValues
          }

          // Fill disabled unit
          for (let i = 0; i < 2; i += 1) {
            if (
              mergedDisabled.value[i] &&
              !getValue(postValues, i) &&
              !getValue(props.allowEmpty, i)
            ) {
              postValues = updateValues(postValues, props.generateConfig.getNow(), i)
            }
          }
          return postValues
        },
      })

      // ============================= Modes =============================
      const [mergedModes, setInnerModes] = useMergedState<[PanelMode, PanelMode]>(
        [props.picker, props.picker],
        {
          value: toRef(props, 'mode'),
        },
      )

      watch(
        () => props.picker,
        () => {
          setInnerModes([props.picker, props.picker])
        },
      )

      const triggerModesChange = (modes: [PanelMode, PanelMode], values: RangeValue<DateType>) => {
        setInnerModes(modes)
        props.onPanelChange?.(values, modes)
      }

      // ========================= Disable Date ==========================
      const [disabledStartDate, disabledEndDate] = useRangeDisabled(
        {
          picker: toRef(props, 'picker'),
          selectedValue,
          locale: toRef(props, 'locale'),
          disabled: mergedDisabled,
          disabledDate: toRef(props, 'disabledDate'),
          generateConfig: toRef(props, 'generateConfig'),
        },
        openRecordsRef,
      )

      // ============================= Open ==============================
      const [mergedOpen, triggerInnerOpen] = useMergedState(false, {
        value: toRef(props, 'open'),
        defaultValue: props.defaultOpen,
        postState: (postOpen) =>
          mergedDisabled.value[mergedActivePickerIndex.value] ? false : postOpen,
        onChange: (newOpen) => {
          props.onOpenChange?.(newOpen)

          if (!newOpen && operationRef.value && operationRef.value.onClose) {
            operationRef.value.onClose()
          }
        },
      })

      const startOpen = computed(() => mergedOpen.value && mergedActivePickerIndex.value === 0)
      const endOpen = computed(() => mergedOpen.value && mergedActivePickerIndex.value === 1)
      const panelLeft = ref(0)
      const arrowLeft = ref(0)
      // ============================= Popup =============================
      // Popup min width
      const popupMinWidth = ref(0)
      const { width: containerWidth } = useElementSize(containerRef)
      watch([mergedOpen, containerWidth], () => {
        if (!mergedOpen.value && containerRef.value) {
          popupMinWidth.value = containerWidth.value
        }
      })
      const { width: panelDivWidth } = useElementSize(panelDivRef)
      const { width: arrowWidth } = useElementSize(arrowRef)
      const { width: startInputDivWidth } = useElementSize(startInputDivRef)
      const { width: separatorWidth } = useElementSize(separatorRef)
      watch(
        [
          mergedActivePickerIndex,
          mergedOpen,
          panelDivWidth,
          arrowWidth,
          startInputDivWidth,
          separatorWidth,
          () => props.direction,
        ],
        () => {
          arrowLeft.value = 0
          if (mergedActivePickerIndex.value) {
            if (startInputDivRef.value && separatorRef.value) {
              arrowLeft.value = startInputDivWidth.value + separatorWidth.value
              if (
                panelDivWidth.value &&
                arrowWidth.value &&
                arrowLeft.value >
                  panelDivWidth.value -
                    arrowWidth.value -
                    (props.direction === 'rtl' || arrowRef.value.offsetLeft > arrowLeft.value
                      ? 0
                      : arrowRef.value.offsetLeft)
              ) {
                panelLeft.value = arrowLeft.value
              }
            }
          } else if (mergedActivePickerIndex.value === 0) {
            panelLeft.value = 0
          }
        },
        { immediate: true },
      )

      // ============================ Trigger ============================
      const triggerRef = ref<any>()

      function triggerOpen(newOpen: boolean, index: 0 | 1) {
        if (newOpen) {
          clearTimeout(triggerRef.value)
          openRecordsRef.value[index] = true

          setMergedActivePickerIndex(index)
          triggerInnerOpen(newOpen)

          // Open to reset view date
          if (!mergedOpen.value) {
            setViewDate(null, index)
          }
        } else if (mergedActivePickerIndex.value === index) {
          triggerInnerOpen(newOpen)

          // Clean up async
          // This makes ref not quick refresh in case user open another input with blur trigger
          const openRecords = openRecordsRef.value
          triggerRef.value = setTimeout(() => {
            if (openRecords === openRecordsRef.value) {
              openRecordsRef.value = {}
            }
          })
        }
      }

      function triggerOpenAndFocus(index: 0 | 1) {
        triggerOpen(true, index)
        // Use setTimeout to make sure panel DOM exists
        setTimeout(() => {
          const inputRef = [startInputRef, endInputRef][index]
          if (inputRef.value) {
            inputRef.value.focus()
          }
        }, 0)
      }

      function triggerChange(newValue: RangeValue<DateType>, sourceIndex: 0 | 1) {
        let values = newValue
        let startValue = getValue(values, 0)
        let endValue = getValue(values, 1)
        const {
          generateConfig,
          locale,
          picker,
          order,
          onCalendarChange,
          allowEmpty,
          onChange,
          showTime,
        } = props

        // >>>>> Format start & end values
        if (startValue && endValue && generateConfig.isAfter(startValue, endValue)) {
          if (
            // WeekPicker only compare week
            (picker === 'week' &&
              !isSameWeek(generateConfig, locale.locale, startValue, endValue)) ||
            // QuotaPicker only compare week
            (picker === 'quarter' && !isSameQuarter(generateConfig, startValue, endValue)) ||
            // Other non-TimePicker compare date
            (picker !== 'week' &&
              picker !== 'quarter' &&
              picker !== 'time' &&
              !(showTime
                ? isEqual(generateConfig, startValue, endValue)
                : isSameDate(generateConfig, startValue, endValue)))
          ) {
            // Clean up end date when start date is after end date
            if (sourceIndex === 0) {
              values = [startValue, null]
              endValue = null
            } else {
              startValue = null
              values = [null, endValue]
            }

            // Clean up cache since invalidate
            openRecordsRef.value = {
              [sourceIndex]: true,
            }
          } else if (picker !== 'time' || order !== false) {
            // Reorder when in same date
            values = reorderValues(values, generateConfig)
          }
        }

        setSelectedValue(values)

        const startStr =
          values && values[0]
            ? formatValue(values[0], { generateConfig, locale, format: formatList.value[0] })
            : ''
        const endStr =
          values && values[1]
            ? formatValue(values[1], { generateConfig, locale, format: formatList.value[0] })
            : ''
        if (onCalendarChange) {
          const info: RangeInfo = { range: sourceIndex === 0 ? 'start' : 'end' }

          onCalendarChange(values, [startStr, endStr], info)
        }

        // >>>>> Trigger `onChange` event
        const canStartValueTrigger = canValueTrigger(
          startValue,
          0,
          mergedDisabled.value,
          allowEmpty,
        )
        const canEndValueTrigger = canValueTrigger(endValue, 1, mergedDisabled.value, allowEmpty)

        const canTrigger = values === null || (canStartValueTrigger && canEndValueTrigger)

        if (canTrigger) {
          // Trigger onChange only when value is validate
          setInnerValue(values)

          if (
            onChange &&
            (!isEqual(generateConfig, getValue(mergedValue.value, 0), startValue) ||
              !isEqual(generateConfig, getValue(mergedValue.value, 1), endValue))
          ) {
            onChange(values, [startStr, endStr])
          }
        }

        // >>>>> Open picker when

        // Always open another picker if possible
        let nextOpenIndex: 0 | 1 = null
        if (sourceIndex === 0 && !mergedDisabled.value[1]) {
          nextOpenIndex = 1
        } else if (sourceIndex === 1 && !mergedDisabled.value[0]) {
          nextOpenIndex = 0
        }

        if (
          nextOpenIndex !== null &&
          nextOpenIndex !== mergedActivePickerIndex.value &&
          (!openRecordsRef.value[nextOpenIndex] || !getValue(values, nextOpenIndex)) &&
          getValue(values, sourceIndex)
        ) {
          // Delay to focus to avoid input blur trigger expired selectedValues
          triggerOpenAndFocus(nextOpenIndex)
        } else {
          triggerOpen(false, sourceIndex)
        }
      }

      const forwardKeydown = (e: KeyboardEvent) => {
        if (mergedOpen && operationRef.value && operationRef.value.onKeydown) {
          // Let popup panel handle keyboard
          return operationRef.value.onKeydown(e)
        }

        /* istanbul ignore next */
        /* eslint-disable no-lone-blocks */
        {
          warning(
            false,
            'Picker not correct forward Keydown operation. Please help to fire issue about this.',
          )
          return false
        }
      }

      // ============================= Text ==============================
      const sharedTextHooksProps = {
        formatList,
        generateConfig: toRef(props, 'generateConfig'),
        locale: toRef(props, 'locale'),
      }

      const [startValueTexts, firstStartValueText] = useValueTexts<DateType>(
        computed(() => getValue(selectedValue.value, 0)),
        sharedTextHooksProps,
      )

      const [endValueTexts, firstEndValueText] = useValueTexts<DateType>(
        computed(() => getValue(selectedValue.value, 1)),
        sharedTextHooksProps,
      )

      const onTextChange = (newText: string, index: 0 | 1) => {
        const inputDate = parseValue(newText, {
          locale: props.locale,
          formatList: formatList.value,
          generateConfig: props.generateConfig,
        })

        const disabledFunc = index === 0 ? disabledStartDate : disabledEndDate

        if (inputDate && !disabledFunc(inputDate)) {
          setSelectedValue(updateValues(selectedValue.value, inputDate, index))
          setViewDate(inputDate, index)
        }
      }

      const [startText, triggerStartTextChange, resetStartText] = useTextValueMapping({
        valueTexts: startValueTexts,
        onTextChange: (newText) => onTextChange(newText, 0),
      })

      const [endText, triggerEndTextChange, resetEndText] = useTextValueMapping({
        valueTexts: endValueTexts,
        onTextChange: (newText) => onTextChange(newText, 1),
      })

      const [rangeHoverValue, setRangeHoverValue] = useState<RangeValue<DateType>>(null)

      // ========================== Hover Range ==========================
      const [hoverRangedValue, setHoverRangedValue] = useState<RangeValue<DateType>>(null)

      const [startHoverValue, onStartEnter, onStartLeave] = useHoverValue(
        startText,
        sharedTextHooksProps,
      )

      const [endHoverValue, onEndEnter, onEndLeave] = useHoverValue(endText, sharedTextHooksProps)

      const onDateMouseenter = (date: DateType) => {
        setHoverRangedValue(updateValues(selectedValue.value, date, mergedActivePickerIndex.value))
        if (mergedActivePickerIndex.value === 0) {
          onStartEnter(date)
        } else {
          onEndEnter(date)
        }
      }

      const onDateMouseleave = () => {
        setHoverRangedValue(updateValues(selectedValue.value, null, mergedActivePickerIndex.value))
        if (mergedActivePickerIndex.value === 0) {
          onStartLeave()
        } else {
          onEndLeave()
        }
      }

      // ============================= Input =============================
      const getSharedInputHookProps = (index: 0 | 1, resetText: () => void) => ({
        forwardKeydown,
        onBlur: (e: FocusEvent) => {
          props.onBlur?.(e)
        },
        isClickOutside: (target: EventTarget | null) =>
          !elementsContains(
            [panelDivRef.value, startInputDivRef.value, endInputDivRef.value, containerRef.value],
            target as HTMLElement,
          ),
        onFocus: (e: FocusEvent) => {
          setMergedActivePickerIndex(index)
          props.onFocus?.(e)
        },
        triggerOpen: (newOpen: boolean) => {
          triggerOpen(newOpen, index)
        },
        onSubmit: () => {
          if (
            // When user typing disabledDate with keyboard and enter, this value will be empty
            !selectedValue.value ||
            // Normal disabled check
            (props.disabledDate && props.disabledDate(selectedValue.value[index]))
          ) {
            return false
          }
          triggerChange(selectedValue.value, index)
          resetText()
        },
        onCancel: () => {
          triggerOpen(false, index)
          setSelectedValue(mergedValue.value)
          resetText()
        },
      })

      const [startInputProps, { focused: startFocused, typing: startTyping }] = usePickerInput({
        ...getSharedInputHookProps(0, resetStartText),
        blurToCancel: needConfirmButton,
        open: startOpen,
        value: startText,
        onKeydown: (e, preventDefault) => {
          props.onKeydown?.(e, preventDefault)
        },
      })

      const [endInputProps, { focused: endFocused, typing: endTyping }] = usePickerInput({
        ...getSharedInputHookProps(1, resetEndText),
        blurToCancel: needConfirmButton,
        open: endOpen,
        value: endText,
        onKeydown: (e, preventDefault) => {
          props.onKeydown?.(e, preventDefault)
        },
      })

      // ========================== Click Picker ==========================
      const onPickerClick = (e: MouseEvent) => {
        // When click inside the picker & outside the picker's input elements
        // the panel should still be opened
        props.onClick?.(e)
        if (
          !mergedOpen.value &&
          !startInputRef.value.contains(e.target as Node) &&
          !endInputRef.value.contains(e.target as Node)
        ) {
          if (!mergedDisabled.value[0]) {
            triggerOpenAndFocus(0)
          } else if (!mergedDisabled.value[1]) {
            triggerOpenAndFocus(1)
          }
        }
      }

      const onPickerMousedown = (e: MouseEvent) => {
        // shouldn't affect input elements if picker is active
        props.onMousedown?.(e)
        if (
          mergedOpen.value &&
          (startFocused.value || endFocused.value) &&
          !startInputRef.value.contains(e.target as Node) &&
          !endInputRef.value.contains(e.target as Node)
        ) {
          e.preventDefault()
        }
      }

      // ============================= Sync ==============================
      // Close should sync back with text value
      const startStr = computed(() =>
        mergedValue.value?.[0]
          ? formatValue(mergedValue.value[0], {
              locale: props.locale,
              format: 'YYYYMMDDHHmmss',
              generateConfig: props.generateConfig,
            })
          : '',
      )
      const endStr = computed(() =>
        mergedValue.value?.[1]
          ? formatValue(mergedValue.value[1], {
              locale: props.locale,
              format: 'YYYYMMDDHHmmss',
              generateConfig: props.generateConfig,
            })
          : '',
      )

      watch([mergedOpen, startValueTexts, endValueTexts], () => {
        if (!mergedOpen.value) {
          setSelectedValue(mergedValue.value)

          if (!startValueTexts.value.length || startValueTexts.value[0] === '') {
            triggerStartTextChange('')
          } else if (firstStartValueText.value !== startText.value) {
            resetStartText()
          }
          if (!endValueTexts.value.length || endValueTexts.value[0] === '') {
            triggerEndTextChange('')
          } else if (firstEndValueText.value !== endText.value) {
            resetEndText()
          }
        }
      })

      // Sync innerValue with control mode
      watch([startStr, endStr], () => {
        setSelectedValue(mergedValue.value)
      })

      // ============================ Warning ============================
      if (process.env.NODE_ENV !== 'production') {
        watchEffect(() => {
          const { value, disabled } = props
          if (
            value &&
            Array.isArray(disabled) &&
            ((getValue(disabled, 0) && !getValue(value, 0)) ||
              (getValue(disabled, 1) && !getValue(value, 1)))
          ) {
            warning(
              false,
              '`disabled` should not set with empty `value`. You should set `allowEmpty` or `value` instead.',
            )
          }
        })
      }

      expose({
        focus: () => {
          if (startInputRef.value) {
            startInputRef.value.focus()
          }
        },
        blur: () => {
          if (startInputRef.value) {
            startInputRef.value.blur()
          }
          if (endInputRef.value) {
            endInputRef.value.blur()
          }
        },
      })

      // ============================= Panel =============================
      const panelHoverRangedValue = computed(() => {
        if (
          mergedOpen.value &&
          hoverRangedValue.value &&
          hoverRangedValue.value[0] &&
          hoverRangedValue.value[1] &&
          props.generateConfig.isAfter(hoverRangedValue.value[1], hoverRangedValue.value[0])
        ) {
          return hoverRangedValue.value
        } else {
          return null
        }
      })
      function renderPanel(
        panelPosition: 'left' | 'right' | false = false,
        panelProps: Partial<PickerPanelProps<DateType>> = {},
      ) {
        const { generateConfig, showTime, dateRender, direction, disabledTime, prefixCls, locale } =
          props

        let panelShowTime: boolean | SharedTimeProps<DateType> | undefined =
          showTime as SharedTimeProps<DateType>
        if (showTime && typeof showTime === 'object' && showTime.defaultValue) {
          const timeDefaultValues: DateType[] = showTime.defaultValue!
          panelShowTime = {
            ...showTime,
            defaultValue: getValue(timeDefaultValues, mergedActivePickerIndex.value) || undefined,
          }
        }

        let panelDateRender: DateRender<DateType> | null = null
        if (dateRender) {
          panelDateRender = ({ current: date, today }) =>
            dateRender({
              current: date,
              today,
              info: {
                range: mergedActivePickerIndex.value ? 'end' : 'start',
              },
            })
        }

        return (
          <RangeContextProvider
            value={{
              inRange: true,
              panelPosition,
              rangedValue: rangeHoverValue.value || selectedValue.value,
              hoverRangedValue: panelHoverRangedValue.value,
            }}
          >
            <PickerPanel<DateType>
              {...(props as any)}
              {...panelProps}
              dateRender={panelDateRender}
              showTime={panelShowTime}
              mode={mergedModes.value[mergedActivePickerIndex.value]}
              generateConfig={generateConfig}
              style={undefined}
              direction={direction}
              disabledDate={
                mergedActivePickerIndex.value === 0 ? disabledStartDate : disabledEndDate
              }
              disabledTime={(date) => {
                if (disabledTime) {
                  return disabledTime(date, mergedActivePickerIndex.value === 0 ? 'start' : 'end')
                }
                return false
              }}
              class={classNames({
                [`${prefixCls}-panel-focused`]:
                  mergedActivePickerIndex.value === 0 ? !startTyping.value : !endTyping.value,
              })}
              value={getValue(selectedValue.value, mergedActivePickerIndex.value)}
              locale={locale}
              tabIndex={-1}
              onPanelChange={(date, newMode) => {
                // clear hover value when panel change
                if (mergedActivePickerIndex.value === 0) {
                  onStartLeave(true)
                }
                if (mergedActivePickerIndex.value === 1) {
                  onEndLeave(true)
                }
                triggerModesChange(
                  updateValues(mergedModes.value, newMode, mergedActivePickerIndex.value),
                  updateValues(selectedValue.value, date, mergedActivePickerIndex.value),
                )

                let viewDate = date
                if (
                  panelPosition === 'right' &&
                  mergedModes.value[mergedActivePickerIndex.value] === newMode
                ) {
                  viewDate = getClosingViewDate(viewDate, newMode as any, generateConfig, -1)
                }
                setViewDate(viewDate, mergedActivePickerIndex.value)
              }}
              onOk={null}
              onSelect={undefined}
              onChange={undefined}
              defaultValue={
                mergedActivePickerIndex.value === 0
                  ? getValue(selectedValue.value, 1)
                  : getValue(selectedValue.value, 0)
              }
            />
          </RangeContextProvider>
        )
      }

      const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => {
        const values = updateValues(selectedValue.value, date, mergedActivePickerIndex.value)

        if (type === 'submit' || (type !== 'key' && !needConfirmButton.value)) {
          // triggerChange will also update selected values
          triggerChange(values, mergedActivePickerIndex.value)
          // clear hover value style
          if (mergedActivePickerIndex.value === 0) {
            onStartLeave()
          } else {
            onEndLeave()
          }
        } else {
          setSelectedValue(values)
        }
      }

      useProvidePanel({
        operationRef,
        hideHeader: computed(() => props.picker === 'time'),
        onDateMouseenter,
        onDateMouseleave,
        hideRanges: computed(() => true),
        onSelect: onContextSelect,
        open: mergedOpen,
      })

      return () => {
        const {
          prefixCls = 'rc-picker',
          id,
          popupStyle,
          dropdownClassName,
          transitionName,
          dropdownAlign,
          getPopupContainer,
          generateConfig,
          locale,
          placeholder,
          autofocus,
          picker = 'date',
          showTime,
          separator = '~',
          disabledDate,
          panelRender,
          allowClear,
          suffixIcon,
          clearIcon,
          inputReadOnly,
          renderExtraFooter,
          onMouseenter,
          onMouseleave,
          onMouseup,
          onOk,
          components,
          direction,
          autocomplete = 'off',
        } = props

        const arrowPositionStyle =
          direction === 'rtl' ? { right: `${arrowLeft.value}px` } : { left: `${arrowLeft.value}px` }

        function renderPanels() {
          let panels: VueNode
          const extraNode = getExtraFooter(
            prefixCls,
            mergedModes.value[mergedActivePickerIndex.value],
            renderExtraFooter,
          )

          const rangesNode = getRanges({
            prefixCls,
            components,
            needConfirmButton: needConfirmButton.value,
            okDisabled:
              !getValue(selectedValue.value, mergedActivePickerIndex.value) ||
              (disabledDate && disabledDate(selectedValue.value[mergedActivePickerIndex.value])),
            locale,
            onOk: () => {
              if (getValue(selectedValue.value, mergedActivePickerIndex.value)) {
                // triggerChangeOld(selectedValue.value);
                triggerChange(selectedValue.value, mergedActivePickerIndex.value)
                if (onOk) {
                  onOk(selectedValue.value)
                }
              }
            },
          })

          if (picker !== 'time' && !showTime) {
            const viewDate =
              mergedActivePickerIndex.value === 0 ? startViewDate.value : endViewDate.value
            const nextViewDate = getClosingViewDate(viewDate, picker, generateConfig)
            const currentMode = mergedModes.value[mergedActivePickerIndex.value]

            const showDoublePanel = currentMode === picker
            const leftPanel = renderPanel(showDoublePanel ? 'left' : false, {
              pickerValue: viewDate,
              onPickerValueChange: (newViewDate) => {
                setViewDate(newViewDate, mergedActivePickerIndex.value)
              },
            })
            const rightPanel = renderPanel('right', {
              pickerValue: nextViewDate,
              onPickerValueChange: (newViewDate) => {
                setViewDate(
                  getClosingViewDate(newViewDate, picker, generateConfig, -1),
                  mergedActivePickerIndex.value,
                )
              },
            })

            if (direction === 'rtl') {
              panels = (
                <>
                  {rightPanel}
                  {showDoublePanel && leftPanel}
                </>
              )
            } else {
              panels = (
                <>
                  {leftPanel}
                  {showDoublePanel && rightPanel}
                </>
              )
            }
          } else {
            panels = renderPanel()
          }

          let mergedNodes: VueNode = (
            <div class={`${prefixCls}-panel-layout`}>
              <PresetPanel
                prefixCls={prefixCls}
                presets={presetList.value}
                onClick={(nextValue) => {
                  triggerChange(nextValue, null)
                  triggerOpen(false, mergedActivePickerIndex.value)
                }}
                onHover={(hoverValue) => {
                  setRangeHoverValue(hoverValue)
                }}
              />
              <div>
                <div class={`${prefixCls}-panels`}>{panels}</div>
                {(extraNode || rangesNode) && (
                  <div class={`${prefixCls}-footer`}>
                    {extraNode}
                    {rangesNode}
                  </div>
                )}
              </div>
            </div>
          )

          if (panelRender) {
            mergedNodes = panelRender(mergedNodes)
          }

          return (
            <div
              class={`${prefixCls}-panel-container`}
              style={{ marginLeft: `${panelLeft.value}px` }}
              ref={panelDivRef}
              onMousedown={(e) => {
                e.preventDefault()
              }}
            >
              {mergedNodes}
            </div>
          )
        }

        const rangePanel = (
          <div
            class={classNames(`${prefixCls}-range-wrapper`, `${prefixCls}-${picker}-range-wrapper`)}
            style={{ minWidth: `${popupMinWidth.value}px` }}
          >
            <div ref={arrowRef} class={`${prefixCls}-range-arrow`} style={arrowPositionStyle} />
            {renderPanels()}
          </div>
        )

        // ============================= Icons =============================
        let suffixNode: VueNode
        if (suffixIcon) {
          suffixNode = <span class={`${prefixCls}-suffix`}>{suffixIcon}</span>
        }

        let clearNode: VueNode
        if (
          allowClear &&
          ((getValue(mergedValue.value, 0) && !mergedDisabled.value[0]) ||
            (getValue(mergedValue.value, 1) && !mergedDisabled.value[1]))
        ) {
          clearNode = (
            <span
              onMousedown={(e) => {
                e.preventDefault()
                e.stopPropagation()
              }}
              onMouseup={(e) => {
                e.preventDefault()
                e.stopPropagation()
                let values = mergedValue.value

                if (!mergedDisabled.value[0]) {
                  values = updateValues(values, null, 0)
                }
                if (!mergedDisabled.value[1]) {
                  values = updateValues(values, null, 1)
                }

                triggerChange(values, null)
                triggerOpen(false, mergedActivePickerIndex.value)
              }}
              class={`${prefixCls}-clear`}
            >
              {clearIcon || <span class={`${prefixCls}-clear-btn`} />}
            </span>
          )
        }

        const inputSharedProps = {
          size: getInputSize(picker, formatList.value[0], generateConfig),
        }

        let activeBarLeft = 0
        let activeBarWidth = 0
        if (startInputDivRef.value && endInputDivRef.value && separatorRef.value) {
          if (mergedActivePickerIndex.value === 0) {
            activeBarWidth = startInputDivRef.value.offsetWidth
          } else {
            activeBarLeft = arrowLeft.value
            activeBarWidth = endInputDivRef.value.offsetWidth
          }
        }
        const activeBarPositionStyle =
          direction === 'rtl' ? { right: `${activeBarLeft}px` } : { left: `${activeBarLeft}px` }
        // ============================ Return =============================

        return (
          <div
            ref={containerRef}
            class={classNames(prefixCls, `${prefixCls}-range`, attrs.class, {
              [`${prefixCls}-disabled`]: mergedDisabled.value[0] && mergedDisabled.value[1],
              [`${prefixCls}-focused`]:
                mergedActivePickerIndex.value === 0 ? startFocused.value : endFocused.value,
              [`${prefixCls}-rtl`]: direction === 'rtl',
            })}
            style={attrs.style}
            onClick={onPickerClick}
            onMouseenter={onMouseenter}
            onMouseleave={onMouseleave}
            onMousedown={onPickerMousedown}
            onMouseup={onMouseup}
            {...getDataOrAriaProps(props)}
          >
            <div
              class={classNames(`${prefixCls}-input`, {
                [`${prefixCls}-input-active`]: mergedActivePickerIndex.value === 0,
                [`${prefixCls}-input-placeholder`]: !!startHoverValue.value,
              })}
              ref={startInputDivRef}
            >
              <input
                id={id}
                disabled={mergedDisabled.value[0]}
                readonly={
                  inputReadOnly || typeof formatList.value[0] === 'function' || !startTyping.value
                }
                value={startHoverValue.value || startText.value}
                onInput={(e: ChangeEvent) => {
                  triggerStartTextChange(e.target.value)
                }}
                autofocus={autofocus}
                placeholder={getValue(placeholder, 0) || ''}
                ref={startInputRef}
                {...startInputProps.value}
                {...inputSharedProps}
                autocomplete={autocomplete}
              />
            </div>
            <div class={`${prefixCls}-range-separator`} ref={separatorRef}>
              {separator}
            </div>
            <div
              class={classNames(`${prefixCls}-input`, {
                [`${prefixCls}-input-active`]: mergedActivePickerIndex.value === 1,
                [`${prefixCls}-input-placeholder`]: !!endHoverValue.value,
              })}
              ref={endInputDivRef}
            >
              <input
                disabled={mergedDisabled.value[1]}
                readonly={
                  inputReadOnly || typeof formatList.value[0] === 'function' || !endTyping.value
                }
                value={endHoverValue.value || endText.value}
                onInput={(e: ChangeEvent) => {
                  triggerEndTextChange(e.target.value)
                }}
                placeholder={getValue(placeholder, 1) || ''}
                ref={endInputRef}
                {...endInputProps.value}
                {...inputSharedProps}
                autocomplete={autocomplete}
              />
            </div>
            <div
              class={`${prefixCls}-active-bar`}
              style={{
                ...activeBarPositionStyle,
                width: `${activeBarWidth}px`,
                position: 'absolute',
              }}
            />
            {suffixNode}
            {clearNode}
            <PickerTrigger
              visible={mergedOpen.value}
              popupStyle={popupStyle}
              prefixCls={prefixCls}
              dropdownClassName={dropdownClassName}
              dropdownAlign={dropdownAlign}
              getPopupContainer={getPopupContainer}
              transitionName={transitionName}
              range
              direction={direction}
              v-slots={{
                popupElement: () => rangePanel,
              }}
            >
              <div
                style={{
                  pointerEvents: 'none',
                  position: 'absolute',
                  top: 0,
                  bottom: 0,
                  left: 0,
                  right: 0,
                }}
              ></div>
            </PickerTrigger>
          </div>
        )
      }
    },
  })
}

const InterRangerPicker = RangerPicker<any>()
export default InterRangerPicker
