import { noop } from '@navinc/utils'
import predictInputValue from '@wojtekmaj/predict-input-value'
import { ChangeEvent, KeyboardEvent, MutableRefObject, useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
import VMasker from 'vanilla-masker'
import Copy from '../copy.js'
import { Err, Errors } from '../form-elements/shared.js'
import Input from '../input.js'
import { InferComponentProps } from '../types.js'

const DAYS_IN_MONTH = 31
const MONTHS_IN_YEAR = 12

const DAYS_PATTERN = '99'
const MONTHS_PATTERN = '99'
const YEARS_PATTERN = '9999'

const createBackspaceHandler =
  (ref: MutableRefObject<HTMLInputElement | undefined>) => (e: KeyboardEvent<HTMLInputElement>) => {
    if (ref.current && e.currentTarget.value.length === 0 && e.key === 'Backspace') {
      ref.current.focus()
    }
  }

const createMaxNumberJumper =
  (ref: MutableRefObject<HTMLInputElement | undefined>, maxNumber: number) => (e: KeyboardEvent<HTMLInputElement>) => {
    if (ref.current && Number(predictInputValue(e.nativeEvent)) > maxNumber) {
      ref.current.focus()
      ref.current.select()
    }
  }

export const DatePickerWrapper = styled.div`
  div > ${Copy} {
    text-align: left;
  }
`

const StyledCopy = styled(Copy)`
  margin-bottom: 8px;
`

const InputWrapper = styled.div`
  display: flex;
  flex-direction: row;
  gap: 12px;

  ${Input} {
    &:nth-child(1),
    &:nth-child(2) {
      width: 25%;
      min-width: 3.5em;
    }
    &:nth-child(3) {
      width: 50%;
      min-width: 4.5em;
    }
  }

  ${Errors} {
    display: none;
  }
`

type DateSegmentComponentProps = {
  errors?: string[]
  hasSpaceForErrors?: boolean
  isInvalid?: boolean
  label: string
  value: string
  onBlur?: () => void
  onFocus?: () => void
  onChange?: (value: string) => void
} & InferComponentProps<typeof DatePickerWrapper>

export const DateSegmentComponent = ({
  errors = [],
  hasSpaceForErrors,
  isInvalid,
  label,
  onBlur,
  onFocus,
  onChange = noop,
  value,
  ...rest
}: DateSegmentComponentProps) => {
  const monthRef = useRef<HTMLInputElement>()
  const dayRef = useRef<HTMLInputElement>()
  const yearRef = useRef<HTMLInputElement>()
  const timerRef = useRef<NodeJS.Timeout>()

  const [focused, setFocused] = useState(false)

  const [year = '', month = '', day = ''] = value.split('-')

  const forwardToDay = createMaxNumberJumper(dayRef, MONTHS_IN_YEAR)
  const backToDay = createBackspaceHandler(dayRef)
  const forwardToYear = createMaxNumberJumper(yearRef, DAYS_IN_MONTH)
  const backToMonth = createBackspaceHandler(monthRef)

  const handleMonthChange = (event: ChangeEvent<HTMLInputElement>) => {
    const monthValue = VMasker.toPattern(event.target.value, MONTHS_PATTERN)

    if (Number(monthValue) > MONTHS_IN_YEAR) {
      return
    }

    onChange(`${year}-${monthValue}-${day}`)

    if (dayRef.current && (monthValue.length >= 2 || Number(monthValue) > 1)) {
      dayRef.current.focus()
      dayRef.current.select()
    }
  }

  const handleDayChange = (event: ChangeEvent<HTMLInputElement>) => {
    const dayValue = VMasker.toPattern(event.target.value, DAYS_PATTERN)

    if (Number(dayValue) > DAYS_IN_MONTH) {
      return
    }

    onChange(`${year}-${month}-${dayValue}`)

    if (yearRef.current && (dayValue.length >= 2 || Number(dayValue) > 3)) {
      yearRef.current.focus()
      yearRef.current.select()
    }
  }

  const handleYearChange = (event: ChangeEvent<HTMLInputElement>) => {
    const yearValue = VMasker.toPattern(event.target.value, YEARS_PATTERN)
    onChange(`${yearValue}-${month}-${day}`)
  }

  const isInvalidOrHasErrors = isInvalid || !!errors.length

  const internalOnBlur = () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current)
    }
    timerRef.current = setTimeout(() => {
      setFocused(false)
    }, 0)
  }

  const handleMonthBlur = () => {
    if (monthRef.current) {
      const monthValue = monthRef.current.value

      if (monthValue?.length === 1) {
        const paddedMonthValue = monthValue.padStart(2, '0')
        monthRef.current.value = paddedMonthValue
        onChange(`${year}-${paddedMonthValue}-${day}`)
      }
    }

    internalOnBlur()
  }

  const handleDayBlur = () => {
    if (dayRef.current) {
      const dayValue = dayRef.current.value

      if (dayValue?.length === 1) {
        const paddedDayValue = dayValue.padStart(2, '0')
        dayRef.current.value = paddedDayValue
        onChange(`${year}-${month}-${paddedDayValue}`)
      }
    }

    internalOnBlur()
  }

  const internalOnFocus = () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current)
    }
    setFocused(true)
  }

  useEffect(() => {
    if (timerRef.current && !focused) {
      onBlur && onBlur()
    } else if (focused) {
      onFocus && onFocus()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [focused])

  return (
    <DatePickerWrapper {...rest}>
      {label && (
        <div>
          <StyledCopy light size="sm">
            {label}
          </StyledCopy>
        </div>
      )}
      <InputWrapper>
        <Input
          data-testid="date-segment-component:month"
          isInvalid={isInvalidOrHasErrors}
          label="MM"
          maxLength={2}
          innerRef={monthRef}
          onBlur={handleMonthBlur}
          onFocus={internalOnFocus}
          onChange={handleMonthChange}
          onKeyDown={forwardToDay}
          type="text"
          value={month || ''}
        />
        <Input
          data-testid="date-segment-component:day"
          isInvalid={isInvalidOrHasErrors}
          label="DD"
          maxLength={2}
          innerRef={dayRef}
          onBlur={handleDayBlur}
          onFocus={internalOnFocus}
          onChange={handleDayChange}
          onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
            backToMonth(e)
            forwardToYear(e)
          }}
          type="text"
          value={day || ''}
        />
        <Input
          data-testid="date-segment-component:year"
          isInvalid={isInvalidOrHasErrors}
          label="YYYY"
          maxLength={4}
          innerRef={yearRef}
          onBlur={internalOnBlur}
          onFocus={internalOnFocus}
          onChange={handleYearChange}
          onKeyDown={backToDay}
          type="text"
          value={year || ''}
        />
      </InputWrapper>
      <Errors hasSpaceForErrors={hasSpaceForErrors} data-testid="date-segment-component:errors">
        {!!errors.length && errors.map((err, i) => <Err key={`err-${i}`}>{err}</Err>)}
      </Errors>
    </DatePickerWrapper>
  )
}
