<template>
  <div class="pitcher-timepicker">
    <div class="p-before">
      <slot name="before" :hour="hour" :minute="minute" :ampm="AMPM" />
    </div>

    <div class="pitcher-timepicker__wrapper">
      <!-- Time input -->
      <VChip class="p-input" v-bind="containerChipAttrs">
        <!-- hidden input -->
        <input
          ref="timeInput"
          type="tel"
          class="p-input__element"
          @keydown="handleKeydown"
          @focus="handleFocus"
          @blur="handleBlur"
        />
        <!-- visible text to user -->
        <div class="p-input__text">
          <div class="p-input__text--hour">
            {{ hour }}
          </div>
          <div class="p-input__text--minute">
            {{ minute }}
          </div>
        </div>

        <!-- Placeholder -->
        <div v-if="!inputValue" class="p-input__text placeholder">
          <div class="p-input__text--hour">12</div>
          <div class="p-input__text--minute">30</div>
        </div>
      </VChip>

      <!-- Append -->
      <div class="p-append">
        <!-- PM/AM on Right side -->
        <VChipGroup
          v-if="enableAmPm"
          v-model="AMPM"
          v-bind="ampmChipAttrs"
          class="p-append__ampm ml-3"
          @change="onAmPmChange"
        >
          <VChip label value="AM" :disabled="disabled">AM</VChip>
          <VChip label value="PM" :disabled="disabled">PM</VChip>
        </VChipGroup>
      </div>
    </div>
  </div>
</template>

<script>
/** 
|--------------------------------------------------
|	time-picker props
|--------------------------------------------------
props: {
  value: accepts date or time hh:mm or hhmm format
  lazy: to emit on input blur
  color: vuetify colors
  enableAmPm: to enable 12h format
  minuteInterval: default 0, if you want to specify minute intervals, force select minutes to 5/10/15/30
  readonly: makes input readonly
  disabled: disable input entirely
}
**/

/**
 * Pitcher Time Picker component
 */

import { computed, defineComponent, reactive, toRefs, watch } from '@vue/composition-api'

// Helper functions
export const padLeftZero = (val) => `0${val}`.slice(-2)

export const convertTo12Hour = (val) => {
  const ampm = Number(val) > 11 ? 'PM' : 'AM'
  let convertedHour = (Number(val) % 12 || 12).toString()

  convertedHour = padLeftZero(convertedHour)

  return { convertedHour, ampm }
}

export const convertTo24Hour = (val, ampm) => {
  let convertedHour = val.replace('12', '00')

  convertedHour = ampm === 'PM' ? (Number(convertedHour) + 12).toString() : convertedHour
  convertedHour = padLeftZero(convertedHour)

  return convertedHour
}

export const timeLimit = (time, limit) => {
  // if time bigger than limit, set the tens digit to 0
  // otherwise return time with left padded 0 if time is less then 2 digits
  return time.length === 2 && Number(time) > limit ? `0${time[1]}` : padLeftZero(time)
}

export default defineComponent({
  name: 'PTimePicker',
  props: {
    value: {
      type: [String, Date],
    },
    lazy: {
      type: Boolean,
      default: false,
    },
    color: {
      type: String,
      default: 'primary',
    },
    enableAmPm: {
      type: Boolean,
      default: false,
    },
    minuteInterval: {
      type: Number,
      default: undefined,
      validator: (v) => {
        if (![0, 1, 5, 10, 15, 30].includes(v)) {
          console.error(`[Vue warn]: Validation error time picker!`)
          console.error(`[Vue warn]: minute-interval accepted values: undefined | 5 | 10 | 15 | 30`)

          return false
        }

        return true
      },
    },
    readonly: {
      type: Boolean,
      default: false,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  emits: ['input', 'blur', 'focus', 'on-press-enter'],
  setup(props, ctx) {
    const state = reactive({
      inputValue: '',
      inputValueBefore: '',
      // take first 2 chars of input depending on input length
      hour: computed(() => (state.inputValue.length > 2 ? state.inputValue.slice(0, -2) : '')),
      // take last 2 chars of input depending on input length
      minute: computed(() => (state.inputValue.length <= 2 ? state.inputValue : state.inputValue.slice(-2))),
      AMPM: 'PM',
      isFocused: false,
    })

    const containerChipAttrs = computed(() => ({
      outlined: true,
      label: true,
      color: state.isFocused ? props.color : undefined,
      disabled: props.disabled,
      style: {
        pointerEvents: props.readonly ? 'none' : undefined,
      },
    }))

    const ampmChipAttrs = computed(() => ({
      mandatory: true,
      color: props.color,
      style: {
        pointerEvents: props.readonly ? 'none' : undefined,
      },
    }))

    // when ampm changes, emit new value
    const onAmPmChange = () => {
      emit()
    }

    // main emitter function, emits only if the value is valid
    const emit = () => {
      if (state.inputValue.length === 4) {
        // If 12h format is enabled, convert the hour to 24h first
        // should always emit 24h format
        const hour = props.enableAmPm ? convertTo24Hour(state.hour, state.AMPM) : state.hour
        const value = `${hour}:${state.minute}`

        ctx.emit('input', value)
      }
    }

    // reset value
    const reset = () => {
      state.inputValue = ''
      ctx.emit('input', '')
    }

    // adjusting the hour, manipulates directly inputValue
    const adjustHour = () => {
      if (state.inputValue.length < 4) return

      const parsedHour = state.inputValue.slice(0, -2)
      let hour = timeLimit(parsedHour, 23)

      if (props.enableAmPm) {
        const { convertedHour, ampm } = convertTo12Hour(hour)

        hour = convertedHour
        state.AMPM = ampm
      }

      state.inputValue = `${hour}${state.minute}`
    }

    // adjusting the minute, manipulates directly inputValue
    const adjustMinute = () => {
      if (state.inputValue.length < 4) {
        return
      }

      const parsedMinute = state.inputValue.slice(-2)
      let minute = timeLimit(parsedMinute, 59)

      // rounding up/down logic
      if (state.inputValue.length === 4 && props.minuteInterval) {
        const rounded = (Math.round(Number(minute) / props.minuteInterval) * props.minuteInterval).toString()

        // in case the minute is rounded over 59
        minute = timeLimit(rounded, 59)
      }

      state.inputValue = `${state.hour}${minute}`
    }

    const handleKeydown = (e) => {
      const keyCode = e.keyCode ? e.keyCode : event.which

      // do not trigger anything if disabled or readonly
      if (props.disabled || props.readonly) return

      // if keyCode is a number
      if ((keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 105) || Number(keyCode) === 229) {
        // Prevent default event
        e.preventDefault()

        // parse number
        const parsed = parseInt(e.key.replace(/\D/g, ''))

        // stop if not number
        if (isNaN(parsed)) return

        // reset input if value is already in place
        if (state.inputValue.length === 4) {
          state.inputValue = ''
        }

        // set full hour here
        state.inputValue += parsed.toString()

        // adjust time
        adjustHour()
        adjustMinute()

        // emit if not lazy
        if (!props.lazy) emit()
      } else if (keyCode === 8) {
        // Prevent default event
        e.preventDefault()
        // Backspace
        state.inputValue = state.inputValue.slice(0, -1)
      } else if (keyCode === 13) {
        e.preventDefault()
        // Enter clicked
        ctx.emit('on-press-enter')
      }
    }

    const handleFocus = (e) => {
      state.isFocused = true
      ctx.emit('focus', e)
    }

    const handleBlur = (e) => {
      state.isFocused = false

      // If the user enters 3 digits only, it will pad it with zero and format minutes
      if (state.inputValue.length === 3) {
        state.inputValue = `0${state.inputValue}`
        adjustMinute()
        emit()
      }

      // reset input if the value is not valid
      if (state.inputValue.length < 4) {
        state.inputValue = state.inputValueBefore.toString()
        adjustHour()
      }

      // emit if lazy
      if (state.lazy) emit()
      ctx.emit('blur', e)
    }

    // force focus timeInput
    // this method can be called externally
    const focus = () => {
      ctx.refs.timeInput.focus()
    }

    // Parse the incoming time
    const parseTime = (val) => {
      let hour = '00'
      let minute = '00'

      // eslint-disable-next-line default-case
      switch (val.constructor.name) {
        // incoming as a date
        case 'Date':
          hour = padLeftZero(val.getHours().toString())
          minute = padLeftZero(val.getMinutes().toString())
          break
        // incoming as a string
        case 'String':
          hour = val.slice(0, 2)
          if (val.length <= 5) {
            // time hh:mm or hhmm
            minute = val.slice(-2)
          } else if (val.length === 6) {
            // time hhmmss
            minute = val.slice(2, -2)
          } else if (val.length === 8) {
            // time hh:mm:ss
            minute = val.slice(-5, -3)
          }

          hour = timeLimit(hour, 23)
          minute = timeLimit(minute, 59)
          break
      }

      return { hour, minute }
    }

    // should execute immediately to parse the incoming value properly
    watch(
      () => props.value,
      (newTime) => {
        if (!newTime) {
          reset()

          return
        }

        const { hour, minute } = parseTime(newTime)
        const time = `${hour}${minute}`

        // if not equal to current value inside input
        if (state.inputValue !== time) {
          state.inputValue = `${hour}${minute}`
          // adjust after parsing the hour depending on
          // if timepicker has 24hr or ampm format
          adjustHour()
        }
        if (state.inputValue.length === 4) {
          state.inputValueBefore = `${hour}${minute}`
        }
      },
      { immediate: true }
    )

    // re-format the inputValue if time-format is changed
    watch(
      () => props.enableAmPm,
      (newVal) => {
        // don't execute any conversion if value is not valid
        if (state.inputValue.length < 4) return

        if (newVal) {
          // converting 24-hour clock to 12-hour
          const { convertedHour, ampm } = convertTo12Hour(state.hour)

          state.inputValue = `${convertedHour}${state.minute}`
          state.AMPM = ampm
        } else {
          // converting 12-hour to 24-hour
          if (state.inputValue.length < 4) return
          const hour = convertTo24Hour(state.hour, state.AMPM)

          state.inputValue = `${hour}${state.minute}`
        }
      }
    )

    /* eslint-disable vue/no-unused-properties */
    // disable eslint rule, as some of the functions here are added
    // to be called externally
    return {
      ...toRefs(state),
      containerChipAttrs,
      ampmChipAttrs,
      onAmPmChange,
      handleKeydown,
      handleFocus,
      handleBlur,
      focus,
      emit,
      adjustHour,
      adjustMinute,
    }
    /* eslint-enable vue/no-unused-properties */
  },
})
</script>

<style lang="scss" scoped>
.pitcher-timepicker {
  width: 100%;

  &__wrapper {
    display: flex;
    align-items: center;
  }

  .p-before {
    flex: 1 0 100%;
  }

  .v-chip--outlined {
    transition: all 0.2s ease-in-out;
  }

  .p-input {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100px;
    height: 40px;
    position: relative;

    &::before,
    &:hover::before {
      content: ':';
      position: absolute;
      display: block;
      width: 10px;
      font-size: 24px;
      height: 40px;
      line-height: 36px;
      text-align: center;
      left: 0;
      right: 0;
      margin: auto;
      opacity: 1;
      background-color: transparent;
    }

    &__element {
      width: 100%;
      height: 100%;
      opacity: 0;
      z-index: 9;
      cursor: pointer;
      caret-color: transparent;
    }

    &__text {
      position: absolute;
      width: 100%;
      height: 100%;
      left: 0;
      top: 0;
      font-size: 24px;
      line-height: 36px;
      display: flex;

      &.placeholder {
        opacity: 0.3;
      }

      & > div {
        width: 50%;
        text-align: right;
        padding: 1px 12px 0 0;
      }
    }
  }

  .p-append {
    &__ampm {
      .v-chip:last-child {
        margin-right: 0;
      }

      .v-chip--active {
        background-color: var(--v-primary-base) !important;
        color: white !important;
      }
    }
  }
}
</style>
