import type { CalendarDateTime, DateFields, DateValue, TimeFields } from '@internationalized/date'
import type { Ref } from 'vue'
import type { AnyExceptLiteral, DateStep, HourCycle, SegmentPart, SegmentValueObj } from './types'
import type { Formatter } from '@/shared'
import {
  DateFormatter,
} from '@internationalized/date'
import { computed } from 'vue'
import { getDaysInMonth, toDate } from '@/date'
import { snapValueToStep, useKbd } from '@/shared'
import { isAcceptableSegmentKey, isNumberString, isSegmentNavigationKey } from './segment'

type MinuteSecondIncrementProps = {
  e: KeyboardEvent
  part: keyof TimeFields
  dateRef: DateValue
  prevValue: number | null
}

type DateTimeValueIncrementation = {
  e: KeyboardEvent
  part: keyof Omit<DateFields, 'era'> | keyof TimeFields
  dateRef: DateValue
  prevValue: number | null
}

type SegmentAttrProps = {
  disabled: boolean
  segmentValues: SegmentValueObj
  hourCycle: HourCycle
  placeholder: DateValue
  formatter: Formatter
}

function commonSegmentAttrs(props: SegmentAttrProps) {
  return {
    role: 'spinbutton',
    contenteditable: true,
    tabindex: props.disabled ? undefined : 0,
    spellcheck: false,
    inputmode: 'numeric',
    autocorrect: 'off',
    enterkeyhint: 'next',
    style: 'caret-color: transparent;',
  }
}

function daySegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues, placeholder } = props
  const isEmpty = segmentValues.day === null

  // Include month from segmentValues to ensure correct max days calculation
  const dateFields: { day?: number, month?: number } = {}
  if (segmentValues.day)
    dateFields.day = segmentValues.day
  if (segmentValues.month)
    dateFields.month = segmentValues.month

  const date = Object.keys(dateFields).length > 0 ? placeholder.set(dateFields) : placeholder

  const valueNow = date.day
  const valueMin = 1
  const valueMax = getDaysInMonth(date)
  const valueText = isEmpty ? 'Empty' : `${valueNow}`

  return {
    ...commonSegmentAttrs(props),
    'aria-label': 'day,',
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
    'data-placeholder': isEmpty ? '' : undefined,
  }
}

function monthSegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues, placeholder, formatter } = props
  const isEmpty = segmentValues.month === null
  const date = segmentValues.month
    ? placeholder.set({ month: segmentValues.month })
    : placeholder
  const valueNow = date.month
  const valueMin = 1
  const valueMax = 12
  const valueText = isEmpty ? 'Empty' : `${valueNow} - ${formatter.fullMonth(toDate(date))}`

  return {
    ...commonSegmentAttrs(props),
    'aria-label': 'month, ',
    'contenteditable': true,
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
    'data-placeholder': isEmpty ? '' : undefined,
  }
}

function yearSegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues, placeholder } = props
  const isEmpty = segmentValues.year === null
  const date = segmentValues.year ? placeholder.set({ year: segmentValues.year }) : placeholder
  const valueMin = 1
  const valueMax = 9999
  const valueNow = date.year
  const valueText = isEmpty ? 'Empty' : `${valueNow}`

  return {
    ...commonSegmentAttrs(props),
    'aria-label': 'year, ',
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
    'data-placeholder': isEmpty ? '' : undefined,
  }
}

function hourSegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues, hourCycle, placeholder } = props

  if (!('hour' in segmentValues) || !('hour' in placeholder))
    return {}
  const isEmpty = segmentValues.hour === null
  const date = segmentValues.hour ? placeholder.set({ hour: segmentValues.hour }) : placeholder
  const valueMin = hourCycle === 12 ? 1 : 0
  const valueMax = hourCycle === 12 ? 12 : 23
  const valueNow = date.hour
  const valueText = isEmpty ? 'Empty' : `${valueNow} ${segmentValues.dayPeriod ?? ''}`

  return {
    ...commonSegmentAttrs(props),
    'aria-label': 'hour, ',
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
    'data-placeholder': isEmpty ? '' : undefined,
  }
}

function minuteSegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues, placeholder } = props
  if (!('minute' in segmentValues) || !('minute' in placeholder))
    return {}
  const isEmpty = segmentValues.minute === null
  const date = segmentValues.minute
    ? placeholder.set({ minute: segmentValues.minute })
    : placeholder
  const valueNow = date.minute
  const valueMin = 0
  const valueMax = 59
  const valueText = isEmpty ? 'Empty' : `${valueNow}`

  return {
    ...commonSegmentAttrs(props),
    'aria-label': 'minute, ',
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
    'data-placeholder': isEmpty ? '' : undefined,
  }
}

function secondSegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues, placeholder } = props
  if (!('second' in segmentValues) || !('second' in placeholder))
    return {}
  const isEmpty = segmentValues.second === null
  const date = segmentValues.second
    ? placeholder.set({ second: segmentValues.second })
    : placeholder
  const valueNow = date.second
  const valueMin = 0
  const valueMax = 59
  const valueText = isEmpty ? 'Empty' : `${valueNow}`

  return {
    ...commonSegmentAttrs(props),
    'aria-label': 'second, ',
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
    'data-placeholder': isEmpty ? '' : undefined,
  }
}

function dayPeriodSegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues } = props
  if (!('dayPeriod' in segmentValues))
    return {}

  const valueMin = 0
  const valueMax = 12
  const valueNow = segmentValues.hour ? (segmentValues.hour > 12 ? segmentValues.hour - 12 : segmentValues.hour) : 0
  const valueText = segmentValues.dayPeriod ?? 'AM'

  return {
    ...commonSegmentAttrs(props),
    'inputmode': 'text',
    'aria-label': 'AM/PM',
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
  }
}

function literalSegmentAttrs(_props: SegmentAttrProps) {
  return {
    'aria-hidden': true,
    'data-segment': 'literal',
  }
}

function timeZoneSegmentAttrs(props: SegmentAttrProps) {
  return {
    'role': 'textbox',
    'aria-label': 'timezone, ',
    'data-readonly': true,
    'data-segment': 'timeZoneName',
    'tabindex': props.disabled ? undefined : 0,
    'style': 'caret-color: transparent;',
  }
}

function eraSegmentAttrs(props: SegmentAttrProps) {
  const { segmentValues, placeholder } = props

  const valueMin = 0
  const valueMax = 0
  const valueNow = 0
  const valueText = 'era' in segmentValues ? segmentValues.era : placeholder.era

  return {
    ...commonSegmentAttrs(props),
    'aria-label': 'era',
    'aria-valuemin': valueMin,
    'aria-valuemax': valueMax,
    'aria-valuenow': valueNow,
    'aria-valuetext': valueText,
  }
}

export const segmentBuilders = {
  day: {
    attrs: daySegmentAttrs,
  },
  month: {
    attrs: monthSegmentAttrs,
  },
  year: {
    attrs: yearSegmentAttrs,
  },
  hour: {
    attrs: hourSegmentAttrs,
  },
  minute: {
    attrs: minuteSegmentAttrs,
  },
  second: {
    attrs: secondSegmentAttrs,
  },
  dayPeriod: {
    attrs: dayPeriodSegmentAttrs,
  },
  literal: {
    attrs: literalSegmentAttrs,
  },
  timeZoneName: {
    attrs: timeZoneSegmentAttrs,
  },
  era: {
    attrs: eraSegmentAttrs,
  },
}

export type UseDateFieldProps = {
  hasLeftFocus: Ref<boolean>
  lastKeyZero: Ref<boolean>
  placeholder: Ref<DateValue>
  hourCycle: HourCycle
  step: Ref<DateStep>
  stepSnapping?: Ref<boolean>
  formatter: Formatter
  segmentValues: Ref<SegmentValueObj>
  disabled: Ref<boolean>
  readonly: Ref<boolean>
  part: SegmentPart
  modelValue: Ref<DateValue | undefined>
  focusNext: () => void
}

export function useDateField(props: UseDateFieldProps) {
  const kbd = useKbd()

  function minuteSecondIncrementation({ e, part, dateRef, prevValue }: MinuteSecondIncrementProps): number {
    const step = props.step.value[part] ?? 1
    const sign = e.key === kbd.ARROW_UP ? step : -step
    const min = 0
    const max = 59

    if (prevValue === null)
      return sign > 0 ? min : max

    const cycleArgs: [keyof TimeFields, number] = [part, sign]
    return (dateRef as CalendarDateTime).set({ [part]: prevValue }).cycle(...cycleArgs)[part]
  }

  function deleteValue(prevValue: number | null) {
    props.hasLeftFocus.value = false
    if (prevValue === null)
      return prevValue

    const str = prevValue.toString()
    if (str.length === 1) {
      props.modelValue.value = undefined
      return null
    }

    return Number.parseInt(str.slice(0, -1))
  }
  function dateTimeValueIncrementation({ e, part, dateRef, prevValue }: DateTimeValueIncrementation): number {
    const step = props.step.value[part] ?? 1
    const sign = e.key === kbd.ARROW_UP ? step : -step

    if (prevValue === null)
      return dateRef[part as keyof Omit<DateFields, 'era'>]

    if (part === 'hour' && 'hour' in dateRef) {
      // Don't pass hourCycle to cycle - internal representation is always 24-hour
      // The hourCycle prop only affects display, not internal cycling
      const cycleArgs: [keyof DateFields | keyof TimeFields, number] = [part, sign]
      return dateRef.set({ [part as keyof DateValue]: prevValue }).cycle(...cycleArgs)[part]
    }

    const cycleArgs: [keyof DateFields, number] = [part as keyof DateFields, sign]
    if (part === 'day') {
      return dateRef.set({
        [part as keyof DateValue]: prevValue,
        /**
         * Edge case for the day field:
         *
         * 1. If the month is filled,
         *   we need to ensure that the day snaps to the maximum value of that month.
         * 2. If the month is not filled,
         *   we default to the month with the maximum number of days (here just using January, 31 days),
         *   so that user can input any possible day.
         */
        month: props.segmentValues.value.month ?? 1,
      }).cycle(...cycleArgs)[part as keyof Omit<DateFields, 'era'>]
    }

    return dateRef.set({ [part as keyof DateValue]: prevValue }).cycle(...cycleArgs)[part as keyof Omit<DateFields, 'era'>]
  }
  function updateDayOrMonth(max: number, num: number, prev: number | null) {
    let moveToNext = false
    const maxStart = Math.floor(max / 10)

    /**
     * If the user has left the segment, we want to reset the
     * `prev` value so that we can start the segment over again
     * when the user types a number.
     */
    if (props.hasLeftFocus.value) {
      props.hasLeftFocus.value = false
      props.lastKeyZero.value = false
      prev = null
    }

    if (prev === null) {
      /**
       * If the user types a 0 as the first number, we want
       * to keep track of that so that when they type the next
       * number, we can move to the next segment.
       */

      if (num === 0) {
        props.lastKeyZero.value = true
        return { value: null, moveToNext }
      }
      /**
       * If the last key was a 0, or if the first number is
       * greater than the max start digit (0-3 in most cases), then
       * we want to move to the next segment, since it's not possible
       * to continue typing a valid number in this segment.
       */

      if (props.lastKeyZero.value || num > maxStart) {
        // move to next
        moveToNext = true
      }
      props.lastKeyZero.value = false
      /**
       * If none of the above conditions are met, then we can just
       * return the number as the segment value and continue typing
       * in this segment.
       */
      return { value: num, moveToNext }
    }

    /**
     * If the number of digits is 2, or if the total with the existing digit
     * and the pressed digit is greater than the maximum value for this
     * month, then we will reset the segment as if the user had pressed the
     * backspace key and then typed the number.
     */
    const digits = prev.toString().length
    const total = Number.parseInt(prev.toString() + num.toString())
    /**
     * If the number of digits is 2, or if the total with the existing digit
     * and the pressed digit is greater than the maximum value for this
     * month, then we will reset the segment as if the user had pressed the
     * backspace key and then typed the number.
     */

    if (digits === 2 || total > max) {
      /**
       * As we're doing elsewhere, we're checking if the number is greater
       * than the max start digit (0-3 in most months), and if so, we're
       * going to move to the next segment.
       */
      if (num > maxStart || total > max) {
        // move to next
        moveToNext = true
      }
      return { value: num, moveToNext }
    }
    // move to next
    moveToNext = true
    return { value: total, moveToNext }
  }

  function updateMinuteOrSecond(num: number, prev: number | null) {
    const max = 59
    let moveToNext = false
    const maxStart = Math.floor(max / 10)

    /**
     * If the user has left the segment, we want to reset the
     * `prev` value so that we can start the segment over again
     * when the user types a number.
     */
    if (props.hasLeftFocus.value) {
      props.hasLeftFocus.value = false
      props.lastKeyZero.value = false
      prev = null
    }

    if (prev === null) {
      /**
       * If the user types a 0 as the first number, we want
       * to keep track of that so that when they type the next
       * number, we can move to the next segment.
       */

      if (num === 0) {
        props.lastKeyZero.value = true
        return { value: 0, moveToNext }
      }
      /**
       * If the last key was a 0, or if the first number is
       * greater than the max start digit (0-3 in most cases), then
       * we want to move to the next segment, since it's not possible
       * to continue typing a valid number in this segment.
       */

      if (props.lastKeyZero.value || num > maxStart) {
        // move to next
        moveToNext = true
      }
      props.lastKeyZero.value = false
      /**
       * If none of the above conditions are met, then we can just
       * return the number as the segment value and continue typing
       * in this segment.
       */
      return { value: num, moveToNext }
    }

    /**
     * If the number of digits is 2, or if the total with the existing digit
     * and the pressed digit is greater than the maximum value for this
     * month, then we will reset the segment as if the user had pressed the
     * backspace key and then typed the number.
     */
    const digits = prev.toString().length
    const total = Number.parseInt(prev.toString() + num.toString())

    /**
     * If the number of digits is 2, or if the total with the existing digit
     * and the pressed digit is greater than the maximum value for this
     * month, then we will reset the segment as if the user had pressed the
     * backspace key and then typed the number.
     */

    if (digits === 2 || total > max) {
      /**
       * As we're doing elsewhere, we're checking if the number is greater
       * than the max start digit (0-3 in most months), and if so, we're
       * going to move to the next segment.
       */
      if (num > maxStart) {
        // move to next
        moveToNext = true
      }
      return { value: num, moveToNext }
    }
    // move to next
    moveToNext = true
    return { value: total, moveToNext }
  }

  function updateHour(max: number, num: number, prev: number | null) {
    let moveToNext = false
    const maxStart = Math.floor(max / 10)

    /**
     * If the user has left the segment, we want to reset the
     * `prev` value so that we can start the segment over again
     * when the user types a number.
     */
    // probably not implement, kind of weird
    if (props.hasLeftFocus.value) {
      props.hasLeftFocus.value = false
      props.lastKeyZero.value = false
      prev = null
    }

    if (prev === null) {
      /**
       * If the user types a 0 as the first number, we want
       * to keep track of that so that when they type the next
       * number, we can move to the next segment.
       */

      if (num === 0) {
        props.lastKeyZero.value = true
        return { value: 0, moveToNext }
      }
      /**
       * If the last key was a 0, or if the first number is
       * greater than the max start digit (0-3 in most cases), then
       * we want to move to the next segment, since it's not possible
       * to continue typing a valid number in this segment.
       */

      if (props.lastKeyZero.value || num > maxStart) {
        // move to next
        moveToNext = true
      }
      props.lastKeyZero.value = false
      /**
       * If none of the above conditions are met, then we can just
       * return the number as the segment value and continue typing
       * in this segment.
       */
      return { value: num, moveToNext }
    }

    /**
     * If the number of digits is 2, or if the total with the existing digit
     * and the pressed digit is greater than the maximum value for this
     * month, then we will reset the segment as if the user had pressed the
     * backspace key and then typed the number.
     */
    const digits = prev.toString().length
    const total = Number.parseInt(prev.toString() + num.toString())

    /**
     * If the number of digits is 2, or if the total with the existing digit
     * and the pressed digit is greater than the maximum value for this
     * month, then we will reset the segment as if the user had pressed the
     * backspace key and then typed the number.
     */

    if (digits === 2 || total > max) {
      /**
       * As we're doing elsewhere, we're checking if the number is greater
       * than the max start digit (0-3 in most months), and if so, we're
       * going to move to the next segment.
       */
      if (num > maxStart) {
        // move to next
        moveToNext = true
      }
      return { value: num, moveToNext }
    }
    // move to next
    moveToNext = true
    return { value: total, moveToNext }
  }

  function updateYear(num: number, prev: number | null) {
    let moveToNext = false

    /**
     * If the user has left the segment, we want to reset the
     * `prev` value so that we can start the segment over again
     * when the user types a number.
     */
    // probably not implement, kind of weird
    if (props.hasLeftFocus.value) {
      props.hasLeftFocus.value = false
      prev = null
    }

    if (prev === null)
      return { value: num === 0 ? 1 : num, moveToNext }

    const str = prev.toString() + num.toString()

    if (str.length > 4)
      return { value: num === 0 ? 1 : num, moveToNext }

    if (str.length === 4)
      moveToNext = true

    const int = Number.parseInt(str)
    return { value: int, moveToNext }
  }

  const attributes = computed(() => segmentBuilders[props.part]?.attrs({
    disabled: props.disabled.value,
    placeholder: props.placeholder.value,
    hourCycle: props.hourCycle,
    segmentValues: props.segmentValues.value,
    formatter: props.formatter,
  }) ?? {})

  // TODO: look into abstracting segment keydown functions since they have the same structure (checks -> arrow_up, arrow_down update -> number string update -> move to next -> backspace update)
  function handleDaySegmentKeydown(e: KeyboardEvent) {
    if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key))
      return

    const prevValue = props.segmentValues.value.day

    if (e.key === kbd.ARROW_DOWN || e.key === kbd.ARROW_UP) {
      props.segmentValues.value.day = dateTimeValueIncrementation({ e, part: 'day', dateRef: props.placeholder.value, prevValue })
      return
    }

    if (isNumberString(e.key)) {
      const num = Number.parseInt(e.key)
      const segmentMonthValue = props.segmentValues.value.month

      const daysInMonth = segmentMonthValue
        ? getDaysInMonth(props.placeholder.value.set({ month: segmentMonthValue }))
        // if the month is not set, we default to the maximum number of days in a month
        // so that user can input any possible day
        : 31

      const { value, moveToNext } = updateDayOrMonth(daysInMonth, num, prevValue)

      props.segmentValues.value.day = value

      if (moveToNext)
        props.focusNext()
    }

    if (e.key === kbd.BACKSPACE) {
      props.hasLeftFocus.value = false
      props.segmentValues.value.day = deleteValue(prevValue)
    }
  }

  function handleMonthSegmentKeydown(e: KeyboardEvent) {
    if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key))
      return

    const prevValue = props.segmentValues.value.month

    if (e.key === kbd.ARROW_DOWN || e.key === kbd.ARROW_UP) {
      props.segmentValues.value.month = dateTimeValueIncrementation({ e, part: 'month', dateRef: props.placeholder.value, prevValue })
      return
    }

    if (isNumberString(e.key)) {
      const num = Number.parseInt(e.key)
      const { value, moveToNext } = updateDayOrMonth(12, num, prevValue)

      props.segmentValues.value.month = value

      if (moveToNext)
        props.focusNext()
    }

    if (e.key === kbd.BACKSPACE) {
      props.hasLeftFocus.value = false
      props.segmentValues.value.month = deleteValue(prevValue)
    }
  }

  function handleYearSegmentKeydown(e: KeyboardEvent) {
    if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key))
      return

    const prevValue = props.segmentValues.value.year

    if (e.key === kbd.ARROW_DOWN || e.key === kbd.ARROW_UP) {
      props.segmentValues.value.year = dateTimeValueIncrementation({ e, part: 'year', dateRef: props.placeholder.value, prevValue })
      return
    }

    if (isNumberString(e.key)) {
      const num = Number.parseInt(e.key)
      const { value, moveToNext } = updateYear(num, prevValue)

      props.segmentValues.value.year = value

      if (moveToNext)
        props.focusNext()
    }

    if (e.key === kbd.BACKSPACE) {
      props.hasLeftFocus.value = false
      props.segmentValues.value.year = deleteValue(prevValue)
    }
  }

  function uses12HourFormat(locale: string): boolean {
    const hourCycle = new DateFormatter(locale, { hour: 'numeric' }).resolvedOptions().hourCycle
    return hourCycle === 'h11' || hourCycle === 'h12'
  }

  function handleHourSegmentKeydown(e: KeyboardEvent) {
    const dateRef = props.placeholder.value
    if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key) || !('hour' in dateRef) || !('hour' in props.segmentValues.value))
      return

    const prevValue = props.segmentValues.value.hour

    if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN) {
      const newHour = dateTimeValueIncrementation({ e, part: 'hour', dateRef: props.placeholder.value, prevValue })
      props.segmentValues.value.hour = newHour

      if ('dayPeriod' in props.segmentValues.value && newHour !== null) {
        // Determine AM/PM based on internal 24-hour value
        // Hour 0-11 = AM, Hour 12-23 = PM
        props.segmentValues.value.dayPeriod = newHour >= 12 ? 'PM' : 'AM'
      }

      return
    }

    if (isNumberString(e.key)) {
      const num = Number.parseInt(e.key)
      const is12Hour = props.hourCycle !== undefined
        ? props.hourCycle === 12
        : uses12HourFormat(props.formatter.getLocale())
      const max = is12Hour ? 12 : 24

      let displayPrev = prevValue
      if (is12Hour && prevValue !== null) {
        // 12 AM/PM should be treated as 0 internally even if it doesn't match the display
        // otherwise repeatedly typing 0 will not advance to the next segment
        displayPrev = prevValue % 12 === 0 ? 0 : (prevValue > 12 ? prevValue - 12 : prevValue)
      }

      const { value, moveToNext } = updateHour(max, num, displayPrev)

      // Convert display hour back to internal 24-hour format
      let internalValue = value
      if (is12Hour && value !== null) {
        const period = props.segmentValues.value.dayPeriod || 'AM'
        if (value === 12) {
          internalValue = period === 'AM' ? 0 : 12
        }
        else {
          internalValue = period === 'PM' ? value + 12 : value
        }
      }

      props.segmentValues.value.hour = internalValue

      if (moveToNext)
        props.focusNext()
    }

    if (e.key === kbd.BACKSPACE) {
      props.hasLeftFocus.value = false
      props.segmentValues.value.hour = deleteValue(prevValue)
    }
  }

  function handleMinuteSegmentKeydown(e: KeyboardEvent) {
    const dateRef = props.placeholder.value

    if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key) || !('minute' in dateRef) || !('minute' in props.segmentValues.value))
      return

    const prevValue = props.segmentValues.value.minute

    if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN) {
      props.segmentValues.value.minute = minuteSecondIncrementation({ e, part: 'minute', dateRef: props.placeholder.value, prevValue })
    }

    if (isNumberString(e.key)) {
      const num = Number.parseInt(e.key)

      const { value, moveToNext } = updateMinuteOrSecond(num, prevValue)

      props.segmentValues.value.minute = value

      if (moveToNext)
        props.focusNext()
    }

    if (e.key === kbd.BACKSPACE) {
      props.hasLeftFocus.value = false
      props.segmentValues.value.minute = deleteValue(prevValue)
    }
  }

  function handleSecondSegmentKeydown(e: KeyboardEvent) {
    const dateRef = props.placeholder.value

    if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key) || !('second' in dateRef) || !('second' in props.segmentValues.value))
      return

    const prevValue = props.segmentValues.value.second

    if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN) {
      props.segmentValues.value.second = minuteSecondIncrementation({ e, part: 'second', dateRef: props.placeholder.value, prevValue })
    }

    if (isNumberString(e.key)) {
      const num = Number.parseInt(e.key)
      const { value, moveToNext } = updateMinuteOrSecond(num, prevValue)

      props.segmentValues.value.second = value

      if (moveToNext)
        props.focusNext()
    }

    if (e.key === kbd.BACKSPACE) {
      props.hasLeftFocus.value = false
      props.segmentValues.value.second = deleteValue(prevValue)
    }
  }

  function handleDayPeriodSegmentKeydown(e: KeyboardEvent) {
    if (((!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key)) && e.key !== 'a' && e.key !== 'p') || !('hour' in props.placeholder.value) || !('dayPeriod' in props.segmentValues.value))
      return

    if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN) {
      if (props.segmentValues.value.dayPeriod === 'AM') {
        props.segmentValues.value.dayPeriod = 'PM'
        props.segmentValues.value.hour = props.segmentValues.value.hour! + 12
        return
      }
      props.segmentValues.value.dayPeriod = 'AM'
      props.segmentValues.value.hour = props.segmentValues.value.hour! - 12
      return
    }

    if (['a', 'A'].includes(e.key) && props.segmentValues.value.dayPeriod !== 'AM') {
      props.segmentValues.value.dayPeriod = 'AM'
      props.segmentValues.value.hour = props.segmentValues.value.hour! - 12
      return
    }

    if (['p', 'P'].includes(e.key) && props.segmentValues.value.dayPeriod !== 'PM') {
      props.segmentValues.value.dayPeriod = 'PM'
      props.segmentValues.value.hour = props.segmentValues.value.hour! + 12
    }
  }

  function handleSegmentClick(e: MouseEvent) {
    const disabled = props.disabled.value
    if (disabled)
      e.preventDefault()
  }

  function handleSegmentKeydown(e: KeyboardEvent) {
    const disabled = props.disabled.value
    const readonly = props.readonly.value
    if (disabled || readonly)
      return
    if (e.key !== kbd.TAB)
      e.preventDefault()
    const segmentKeydownHandlers = {
      day: handleDaySegmentKeydown,
      month: handleMonthSegmentKeydown,
      year: handleYearSegmentKeydown,
      hour: handleHourSegmentKeydown,
      minute: handleMinuteSegmentKeydown,
      second: handleSecondSegmentKeydown,
      dayPeriod: handleDayPeriodSegmentKeydown,
      timeZoneName: () => { },
    } as const

    segmentKeydownHandlers[props.part as keyof typeof segmentKeydownHandlers](e)

    if (![kbd.ARROW_LEFT, kbd.ARROW_RIGHT].includes(e.key) && e.key !== kbd.TAB && e.key !== kbd.SHIFT && isAcceptableSegmentKey(e.key)) {
      if (Object.values(props.segmentValues.value).every(item => item !== null)) {
        const updateObject = { ...props.segmentValues.value as Record<AnyExceptLiteral, number> }

        // Set all date fields at once to avoid order-dependent constraints
        // (e.g., setting day: 31 before month: 3 would incorrectly constrain the day)
        const dateRef = props.placeholder.value.set(updateObject)

        props.modelValue.value = dateRef.copy()
      }
    }
  }

  function handleSegmentFocusOut() {
    if (!props.stepSnapping?.value)
      return

    if (props.part === 'hour' && 'hour' in props.segmentValues.value && props.segmentValues.value.hour !== null && props.step.value.hour && props.step.value.hour > 1) {
      props.segmentValues.value.hour = snapValueToStep(props.segmentValues.value.hour, 0, 23, props.step.value.hour)
      if ('dayPeriod' in props.segmentValues.value) {
        if (props.segmentValues.value.hour < 12)
          props.segmentValues.value.dayPeriod = 'AM'
        else if (props.segmentValues.value.hour)
          props.segmentValues.value.dayPeriod = 'PM'
      }
    }
    else if (props.part === 'minute' && 'minute' in props.segmentValues.value && props.segmentValues.value.minute !== null && props.step.value.minute && props.step.value.minute > 1) {
      props.segmentValues.value.minute = snapValueToStep(props.segmentValues.value.minute, 0, 59, props.step.value.minute)
    }
    else if (props.part === 'second' && 'second' in props.segmentValues.value && props.segmentValues.value.second !== null && props.step.value.second && props.step.value.second > 1) {
      props.segmentValues.value.second = snapValueToStep(props.segmentValues.value.second, 0, 59, props.step.value.second)
    }

    if (Object.values(props.segmentValues.value).every(item => item !== null)) {
      const dateRef = props.placeholder.value.set({ ...props.segmentValues.value as Record<AnyExceptLiteral, number> })
      props.modelValue.value = dateRef.copy()
    }
  }

  return {
    handleSegmentClick,
    handleSegmentKeydown,
    handleSegmentFocusOut,
    attributes,
  }
}
