import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { defaultSingleStrings, defaultRangeStrings } from 'shared-types/datepicker'
import type { TPktInputSize } from 'shared-types/size'
import { valueToArray } from 'shared-utils/value-utils'
import { isIOS } from 'shared-utils/device-utils'
import { newDate, filterSelectableDates } from 'shared-utils/date-utils'
import {
  getDatepickerInputType,
  getDatepickerInputClasses,
  getDatepickerButtonClasses,
  getRangeLabelClasses,
  processDateSelection,
  handleDatepickerKeydown,
  validateRangeOrder,
} from 'shared-utils/datepicker-utils'

import type { IPktDatepicker, IDatepickerState } from './types'

export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRef<HTMLDivElement>): IDatepickerState {
  const {
    id,
    label,
    value,
    defaultValue,
    dateformat = 'dd.MM.yyyy',
    multiple = false,
    maxlength,
    range = false,
    showRangeLabels = false,
    weeknumbers = false,
    withcontrols = false,
    excludedates,
    excludeweekdays,
    currentmonth,
    today,
    calendarOpen: calendarOpenProp,
    timezone,
    fullwidth = false,
    inputSize = 'medium' as TPktInputSize,
    hasFieldset = false,
    inline = false,
    helptext,
    helptextDropdown,
    helptextDropdownButton,
    hasError = false,
    errorMessage,
    optionalTag = false,
    optionalText,
    requiredTag = false,
    requiredText,
    tagText,
    useWrapper = true,
    strings: stringsProp,
    disabled = false,
    readOnly = false,
    required = false,
    name,
    min,
    max,
    placeholder,
    onChange,
    onValueChange,
    className,
    ...restProps
  } = props

  //  Value management
  const normalizeValue = useCallback((val: string | string[] | undefined): string[] => {
    if (val === undefined) return []
    return valueToArray(val)
  }, [])

  const isControlled = value !== undefined
  const initialDefault = typeof defaultValue === 'number' ? undefined : (defaultValue as string | string[] | undefined)
  const [internalValue, setInternalValue] = useState<string[]>(() => normalizeValue(value ?? initialDefault))
  const values = isControlled ? normalizeValue(value) : internalValue

  // Store initial default values for form reset
  const initialValuesRef = useRef(internalValue)

  useEffect(() => {
    if (isControlled) {
      setInternalValue(normalizeValue(value))
    }
  }, [value, isControlled, normalizeValue])

  //  Calendar state
  const [calendarOpen, setCalendarOpen] = useState(calendarOpenProp ?? false)

  useEffect(() => {
    if (calendarOpenProp !== undefined) {
      setCalendarOpen(calendarOpenProp)
    }
  }, [calendarOpenProp])

  //  Refs
  const inputRef = useRef<HTMLInputElement>(null)
  const inputRefTo = useRef<HTMLInputElement>(null)
  const changeInputRef = useRef<HTMLInputElement>(null)
  const btnRef = useRef<HTMLButtonElement>(null)
  const wrapperRef = useRef<HTMLDivElement>(null)

  //  Derived state
  const inputType = useMemo(() => getDatepickerInputType(), [])
  const isIOSDevice = useMemo(() => isIOS(), [])
  const strings = useMemo(
    () => stringsProp ?? (range ? defaultRangeStrings : defaultSingleStrings),
    [stringsProp, range],
  )

  const minStr = typeof min === 'string' ? min : min !== undefined ? String(min) : undefined
  const maxStr = typeof max === 'string' ? max : max !== undefined ? String(max) : undefined

  const inputClasses = useMemo(() => {
    const classMap = getDatepickerInputClasses(
      fullwidth,
      showRangeLabels,
      multiple,
      range,
      readOnly,
      inputType,
      inputSize,
    )
    return Object.entries(classMap)
      .filter(([, v]) => v)
      .map(([k]) => k)
      .join(' ')
  }, [fullwidth, showRangeLabels, multiple, range, readOnly, inputType, inputSize])

  const buttonClasses = Object.entries(getDatepickerButtonClasses(inputSize))
    .filter(([, v]) => v)
    .map(([k]) => k)
    .join(' ')

  const rangeLabelClasses = useMemo(() => {
    const classMap = getRangeLabelClasses(showRangeLabels)
    return Object.entries(classMap)
      .filter(([, v]) => v)
      .map(([k]) => k)
      .join(' ')
  }, [showRangeLabels])

  const datepickerInputsClasses = useMemo(
    () => ['pkt-datepicker__inputs', range && 'pkt-input__range-inputs'].filter(Boolean).join(' '),
    [range, showRangeLabels],
  )

  const effectiveCurrentMonth = useMemo(() => {
    if (currentmonth) return currentmonth
    if (maxStr) return maxStr.slice(0, 7)
    return undefined
  }, [currentmonth, maxStr])

  const isInputDisabled = disabled || (multiple && maxlength != null && values.length >= maxlength)
  const hasCounter = multiple && maxlength != null
  const inputId = `${id}-input`
  const formValue = values.join(',')

  //  Sync hidden input value for form submission (input is uncontrolled)
  useEffect(() => {
    if (changeInputRef.current) {
      changeInputRef.current.value = formValue
    }
  }, [formValue])

  //  Timezone
  useEffect(() => {
    if (timezone && typeof window !== 'undefined' && timezone !== window.pktTz) {
      window.pktTz = timezone
    }
  }, [timezone])

  //  Value update helpers
  const updateValues = useCallback(
    (newValues: string[], notify = true) => {
      if (!isControlled) {
        setInternalValue(newValues)
      }
      if (notify) onValueChange?.(newValues)
    },
    [isControlled, onValueChange],
  )

  const nativeInputValueSetter = useMemo(
    () => Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set,
    [],
  )

  const dispatchChange = useCallback(
    (newValues: string[]) => {
      const input = changeInputRef.current
      if (!input || !nativeInputValueSetter) return
      nativeInputValueSetter.call(input, newValues.join(','))
      input.dispatchEvent(new Event('input', { bubbles: true }))
    },
    [nativeInputValueSetter],
  )

  //  Calendar toggle
  const toggleCalendar = useCallback(() => {
    if (disabled) return
    setCalendarOpen((prev) => !prev)
  }, [disabled])

  const hideCalendar = useCallback(() => {
    setCalendarOpen(false)
  }, [])

  //  Outside click / Escape
  useEffect(() => {
    if (!calendarOpen) return

    const handleClick = (e: MouseEvent) => {
      const target = e.target as Node
      if (
        wrapperRef.current &&
        !wrapperRef.current.contains(target) &&
        !(target as Element).closest?.('.pkt-calendar-popup')
      ) {
        hideCalendar()
      }
    }

    const handleKeydown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        hideCalendar()
        inputRef.current?.focus()
      }
    }

    document.addEventListener('click', handleClick, true)
    document.addEventListener('keydown', handleKeydown)
    return () => {
      document.removeEventListener('click', handleClick, true)
      document.removeEventListener('keydown', handleKeydown)
    }
  }, [calendarOpen, hideCalendar])

  //  iOS auto-open on focus
  const handleFocus = useCallback(() => {
    if (isIOSDevice && !calendarOpen) {
      setCalendarOpen(true)
    }
  }, [isIOSDevice, calendarOpen])

  //  Calendar date selection
  const handleDateSelected = useCallback(
    (selected: string[]) => {
      const processed = processDateSelection(selected, multiple, range)
      const newValues = valueToArray(processed)

      updateValues(newValues)
      dispatchChange(newValues)

      if (!multiple && !range) {
        hideCalendar()
        inputRef.current?.focus()
      }
    },
    [multiple, range, updateValues, dispatchChange, hideCalendar],
  )

  const handleCalendarClose = useCallback(() => {
    hideCalendar()
    inputRef.current?.focus()
  }, [hideCalendar])

  //  Single mode input change
  const handleSingleInputChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const newValue = e.target.value
      const newValues = newValue ? [newValue] : []
      updateValues(newValues)
      dispatchChange(newValues)
    },
    [updateValues, dispatchChange],
  )

  //  Range mode input change
  const handleRangeFromChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const fromVal = e.target.value
      const toVal = values[1] ?? ''
      const newValues = [fromVal, toVal].filter(Boolean)
      updateValues(newValues)
      dispatchChange(newValues)
    },
    [values, updateValues, dispatchChange],
  )

  const handleRangeToChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const fromVal = values[0] ?? ''
      const toVal = e.target.value
      const newValues = [fromVal, toVal].filter(Boolean)
      updateValues(newValues)
      dispatchChange(newValues)
    },
    [values, updateValues, dispatchChange],
  )

  const handleRangeBlur = useCallback(() => {
    if (values.length === 2 && !validateRangeOrder(values)) {
      updateValues([values[0]])
      dispatchChange([values[0]])
    }
  }, [values, updateValues, dispatchChange])

  //  Multiple mode handlers
  const addDateToMultiple = useCallback(
    (dateStr: string) => {
      const date = newDate(dateStr)
      const minAsDate = minStr ? newDate(minStr) : null
      const maxAsDate = maxStr ? newDate(maxStr) : null

      if (date && !isNaN(date.getTime()) && (!minAsDate || date >= minAsDate) && (!maxAsDate || date <= maxAsDate)) {
        const filtered = filterSelectableDates([...values, dateStr], minStr, maxStr, excludedates, excludeweekdays)
        const unique = [...new Set(filtered)]
        updateValues(unique)
        dispatchChange(unique)
      }
    },
    [values, minStr, maxStr, excludedates, excludeweekdays, updateValues, dispatchChange],
  )

  const handleMultipleBlur = useCallback(
    (e: React.FocusEvent<HTMLInputElement>) => {
      const target = e.target
      if (!target.value) return
      addDateToMultiple(target.value.split(',')[0])
      target.value = ''
    },
    [addDateToMultiple],
  )

  const handleMultipleComma = useCallback(
    (e: KeyboardEvent) => {
      const target = e.target as HTMLInputElement
      if (!target.value) return
      addDateToMultiple(target.value.split(',')[0])
      target.value = ''
    },
    [addDateToMultiple],
  )

  const handleTagRemoved = useCallback(
    (date: string) => {
      const newValues = values.filter((d) => d !== date)
      updateValues(newValues)
      dispatchChange(newValues)
    },
    [values, updateValues, dispatchChange],
  )

  //  Keyboard navigation
  const submitFormOrBlur = useCallback((inputEl: HTMLInputElement | null) => {
    const form = inputEl?.closest('form')
    if (form) {
      form.requestSubmit()
    } else {
      inputEl?.blur()
    }
  }, [])

  const handleSingleKeydown = useCallback(
    (e: React.KeyboardEvent<HTMLInputElement>) => {
      handleDatepickerKeydown(
        e.nativeEvent,
        () => toggleCalendar(),
        () => submitFormOrBlur(inputRef.current),
        undefined,
        () => inputRef.current?.blur(),
      )
    },
    [toggleCalendar, submitFormOrBlur],
  )

  const handleRangeFromKeydown = useCallback(
    (e: React.KeyboardEvent<HTMLInputElement>) => {
      handleDatepickerKeydown(
        e.nativeEvent,
        () => toggleCalendar(),
        () => submitFormOrBlur(inputRef.current),
        () => inputRefTo.current?.focus(),
        () => inputRef.current?.blur(),
      )
    },
    [toggleCalendar, submitFormOrBlur],
  )

  const handleRangeToKeydown = useCallback(
    (e: React.KeyboardEvent<HTMLInputElement>) => {
      handleDatepickerKeydown(
        e.nativeEvent,
        () => toggleCalendar(),
        () => submitFormOrBlur(inputRefTo.current),
        undefined,
        () => inputRefTo.current?.blur(),
      )
    },
    [toggleCalendar, submitFormOrBlur],
  )

  const handleMultipleKeydown = useCallback(
    (e: React.KeyboardEvent<HTMLInputElement>) => {
      handleDatepickerKeydown(
        e.nativeEvent,
        () => toggleCalendar(),
        () => submitFormOrBlur(inputRef.current),
        undefined,
        () => inputRef.current?.blur(),
        handleMultipleComma,
      )
    },
    [toggleCalendar, submitFormOrBlur, handleMultipleComma],
  )

  // Form reset: listen for the parent <form>'s reset event and restore initial values
  useEffect(() => {
    const form = wrapperRef.current?.closest('form')
    if (!form) return

    const handleReset = () => {
      window.setTimeout(() => {
        setInternalValue(initialValuesRef.current)
        setCalendarOpen(false)
        if (changeInputRef.current) {
          changeInputRef.current.value = initialValuesRef.current.join(',')
        }
      }, 0)
    }

    form.addEventListener('reset', handleReset)
    return () => form.removeEventListener('reset', handleReset)
  }, [])

  //  Expose value getter/setter on ref for RHF register() compatibility.
  //  RHF reads ref.value via getFieldValue(field._f) — we read directly from the
  //  hidden input DOM element so the value is always fresh (not stale from a
  //  batched state update). RHF calls ref.value = x on mount and on reset.
  useImperativeHandle(
    ref,
    () =>
      ({
        get value() {
          return changeInputRef.current?.value ?? ''
        },
        set value(newVal: string) {
          const newValues = newVal ? newVal.split(',').filter(Boolean) : []
          setInternalValue(newValues)
          if (changeInputRef.current) {
            changeInputRef.current.value = newVal ?? ''
          }
        },
        focus() {
          inputRef.current?.focus()
        },
        blur() {
          inputRef.current?.blur()
        },
      }) as unknown as HTMLDivElement,
    [],
  )

  return {
    id,
    inputId,
    label,
    values,
    calendarOpen,
    dateformat,
    multiple,
    maxlength,
    range,
    showRangeLabels,
    weeknumbers,
    withcontrols,
    excludedates,
    excludeweekdays,
    disabled,
    readOnly,
    required,
    name,
    placeholder,
    fullwidth,
    inputSize,
    hasFieldset,
    inline,
    helptext,
    helptextDropdown,
    helptextDropdownButton,
    hasError,
    errorMessage,
    optionalTag,
    optionalText,
    requiredTag,
    requiredText,
    tagText,
    useWrapper,
    className,
    minStr,
    maxStr,
    effectiveCurrentMonth,
    today,
    inputType,
    isIOSDevice,
    strings,
    inputClasses,
    buttonClasses,
    rangeLabelClasses,
    datepickerInputsClasses,
    isInputDisabled,
    hasCounter,
    inputRef,
    inputRefTo,
    changeInputRef,
    btnRef,
    wrapperRef,
    onChange,
    toggleCalendar,
    hideCalendar,
    handleDateSelected,
    handleCalendarClose,
    handleFocus,
    handleSingleInputChange,
    handleRangeFromChange,
    handleRangeToChange,
    handleRangeBlur,
    handleMultipleBlur,
    handleTagRemoved,
    handleSingleKeydown,
    handleRangeFromKeydown,
    handleRangeToKeydown,
    handleMultipleKeydown,
    restProps,
  }
}
