<template>
  <div class="datepicker-field">
    <label v-if="label" :for="inputId" class="datepicker-field__label">{{ label }}</label>

    <div
      :id="inputId"
      ref="referenceRef"
      tabindex="0"
      class="datepicker-field__trigger"
      :class="{
        'datepicker-field__trigger--error': error,
        'datepicker-field__trigger--disabled': disabled,
      }"
      @click="toggle"
    >
      <span class="datepicker-field__value">{{ displayValue }}</span>
      <SvgIcon name="calendar" :size="20" color="#DDDDDD" class="datepicker-field__icon" />
    </div>

    <div v-if="error" class="datepicker-field__error" :class="{ 'animate-shake': vibration }">
      {{ error }}
    </div>

    <Teleport to="body">
      <transition name="fade">
        <div
          v-show="isOpen"
          ref="floatingRef"
          class="date-picker-dropdown datepicker-field__dropdown"
          :style="floatingStyles"
        >
          <DatePicker
            v-model="internalValue"
            locale="fa"
            inline
            editable
            compact-time
            :min="jalaliMin"
            :max="jalaliMax"
            :type="type"
            :simple="simple"
            :range="range"
            :auto-submit="false"
            :time-picker="type === 'datetime'"
            :compact-time="type === 'datetime'"
            :format="type === 'datetime' ? 'jYYYY/jMM/jDD HH:mm' : 'jYYYY/jMM/jDD'"
            :display-format="type === 'datetime' ? 'jYYYY/jMM/jDD HH:mm' : 'jYYYY/jMM/jDD'"
            :confirm="type === 'datetime'"
            @update:model-value="onSelect"
          />
        </div>
      </transition>
    </Teleport>
  </div>
</template>

<script>
import SvgIcon from '@/components/icons/SvgIcon.vue'
import DatePicker from 'vue3-persian-datetime-picker'
import { computePosition, autoUpdate, offset, shift, flip, size } from '@floating-ui/dom'
import {
  JalaliToGregorianString,
  gregorianToJalaliString,
  gregorianToJalaliStringLong,
  JalaliToGregorianStringWithTime,
} from '@/utils/date-convertor'

let uid = 0
const nextUid = () => `datepicker-field-${++uid}`

export default {
  name: 'DatePickerField',
  components: { DatePicker, SvgIcon },

  props: {
    modelValue: { type: String, default: '' },
    name: { type: String, default: '' },
    label: { type: String, default: '' },
    placeholder: { type: String, default: 'یک تاریخ را انتخاب کنید' },
    disabled: { type: Boolean, default: false },
    error: { type: String, default: '' },
    vibration: { type: Boolean, default: false },
    min: { type: String, default: undefined },
    max: { type: String, default: undefined },
    type: { type: String, default: 'date' },
    simple: { type: Boolean, default: false },
    range: { type: [Boolean, Array], default: false },
    autoSubmit: { type: Boolean, default: true },
  },

  emits: ['update:modelValue', 'change'],

  data() {
    return {
      inputId: this.name || nextUid(),
      isOpen: false,
      internalValue: null,
      cleanup: null,
      floatingStyles: {
        position: 'absolute',
        top: '0px',
        left: '0px',
      },
      confirmHandler: null,
      mutationObserver: null,
    }
  },

  computed: {
    displayValue() {
      return this.internalValue || this.placeholder
    },
    jalaliMin() {
      if (!this.min) return
      if (this.type === 'datetime') {
        return gregorianToJalaliStringLong(this.min, '/', false).trim().replace('، ', ' ')
      }
      return gregorianToJalaliString(this.min, '/', false)
    },
    jalaliMax() {
      if (!this.max) return
      if (this.type === 'datetime') {
        return gregorianToJalaliStringLong(this.max, '/', false).trim().replace('، ', ' ')
      }
      return gregorianToJalaliString(this.max, '/', false)
    },
  },

  watch: {
    modelValue: {
      immediate: true,
      handler(val) {
        this.syncFromModel(val)
      },
    },
  },

  beforeUnmount() {
    this.cleanup?.()
    this.removeConfirmButtonListener()
    document.removeEventListener('mousedown', this.handleOutside)
  },

  methods: {
    syncFromModel(val) {
      if (val && typeof val === 'string') {
        this.internalValue =
          this.type === 'datetime'
            ? gregorianToJalaliStringLong(val, '/', false)
            : gregorianToJalaliString(val, '/', false)
      } else {
        this.internalValue = null
      }
    },

    toggle() {
      if (this.disabled) return
      this.isOpen ? this.close() : this.open()
    },

    open() {
      this.isOpen = true

      this.$nextTick(() => {
        this.initFloating()
        if (this.type === 'datetime') {
          this.$nextTick(() => {
            this.attachConfirmButtonListener()
          })
        }
      })
    },

    close() {
      this.isOpen = false
      this.cleanup?.()
      this.cleanup = null
      this.removeConfirmButtonListener()
    },

    removeConfirmButtonListener() {
      if (this.confirmHandler) {
        this.confirmHandler.element.removeEventListener('click', this.confirmHandler.handler)
        delete this.confirmHandler.element.dataset.confirmListener
        this.confirmHandler = null
      }

      if (this.mutationObserver) {
        this.mutationObserver.disconnect()
        this.mutationObserver = null
      }
    },

    initFloating() {
      const reference = this.$refs.referenceRef
      const floating = this.$refs.floatingRef
      if (!reference || !floating) return

      this.cleanup = autoUpdate(reference, floating, () => {
        computePosition(reference, floating, {
          placement: 'bottom-start',
          middleware: [
            offset(6),
            flip(),
            shift({ padding: 8 }),
            size({
              apply({ rects }) {
                Object.assign(floating.style, {
                  minWidth: `${rects.reference.width}px`,
                })
              },
            }),
          ],
        }).then(({ x, y }) => {
          Object.assign(this.floatingStyles, {
            left: `${x}px`,
            top: `${y}px`,
          })
        })
      })

      document.addEventListener('mousedown', this.handleOutside)
    },

    handleOutside(e) {
      const r = this.$refs.referenceRef
      const f = this.$refs.floatingRef
      if (r?.contains(e.target) || f?.contains(e.target)) return
      this.close()
      document.removeEventListener('mousedown', this.handleOutside)
    },

    onSelect(jalaliValue) {
      if (!jalaliValue) return

      if (this.type === 'datetime') {
        const gregorian = JalaliToGregorianStringWithTime(jalaliValue)
        this.$emit('update:modelValue', gregorian)
        this.$emit('change', gregorian)
        this.internalValue = jalaliValue
      } else {
        const gregorian = JalaliToGregorianString(jalaliValue)
        this.$emit('update:modelValue', gregorian)
        this.$emit('change', gregorian)
        this.internalValue = jalaliValue
        this.close()
      }
    },

    attachConfirmButtonListener() {
      const findAndAttach = () => {
        const dropdown = this.$refs.floatingRef
        if (!dropdown) return false
        let confirmBtn = dropdown.querySelector('.vpd-actions')
        if (confirmBtn) {
          const allButtons = confirmBtn.querySelectorAll('button')
          for (const btn of allButtons) {
            if (btn.innerHTML.includes('svg') || btn.innerText.includes('تایید')) {
              confirmBtn = btn
              break
            }
          }
        }

        if (confirmBtn && !Object.hasOwn(confirmBtn.dataset, 'confirmListener')) {
          const handler = (e) => {
            e.preventDefault()
            e.stopPropagation()
            this.handleConfirm()
          }

          confirmBtn.addEventListener('click', handler, true)
          confirmBtn.dataset.confirmListener = 'true'
          this.confirmHandler = { element: confirmBtn, handler }
          return true
        }
        return false
      }

      if (!findAndAttach()) {
        const timeouts = [100, 300, 500, 1000]
        timeouts.forEach((delay) => {
          setTimeout(() => {
            if (this.isOpen) {
              findAndAttach()
            }
          }, delay)
        })
      }

      this.setupMutationObserver()
    },

    setupMutationObserver() {
      const dropdown = this.$refs.floatingRef
      if (!dropdown) return

      this.mutationObserver = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
          if (mutation.addedNodes.length > 0) {
            setTimeout(() => {
              for (const node of mutation.addedNodes) {
                if (node.nodeType === 1) {
                  if (this.isConfirmButton(node)) {
                    this.attachListenerToButton(node)
                  }

                  const btns = node.querySelectorAll ? node.querySelectorAll('button') : []
                  for (const btn of btns) {
                    if (this.isConfirmButton(btn)) {
                      this.attachListenerToButton(btn)
                    }
                  }
                }
              }
            }, 50)
          }
        }
      })

      this.mutationObserver.observe(dropdown, {
        childList: true,
        subtree: true,
      })
    },

    isConfirmButton(element) {
      if (!element || !element.tagName) return false

      if (
        element.classList?.contains('vpd-confirm-btn') ||
        element.classList?.contains('vpd-action-btn')
      ) {
        return true
      }

      if (element.innerText?.includes('تایید')) {
        return true
      }

      if (element.innerHTML?.includes('svg')) {
        const svgPaths = element.innerHTML.match(/d="[^"]*"/g) || []

        const hasCheckIcon = svgPaths.some(
          (path) =>
            path.includes('M20 6L9 17l-5-5') ||
            path.includes('M9 16.17L4.83 12') ||
            path.includes('M5 13l4 4L19 7')
        )

        return hasCheckIcon
      }

      return false
    },

    attachListenerToButton(btn) {
      if (Object.hasOwn(btn.dataset, 'confirmListener')) return

      const handler = (e) => {
        e.preventDefault()
        e.stopPropagation()
        this.handleConfirm()
      }

      btn.addEventListener('click', handler, true)
      btn.dataset.confirmListener = 'true'
      this.confirmHandler = { element: btn, handler }
    },

    handleConfirm() {
      if (!this.internalValue) return

      try {
        const gregorian = JalaliToGregorianStringWithTime(this.internalValue)
        this.$emit('update:modelValue', gregorian)
        this.$emit('change', gregorian)
        this.close()
      } catch (error) {
        // eslint-disable-next-line no-console
        console.error('Error in handleConfirm:', error)
      }
    },
  },
}
</script>

<style lang="scss" scoped>
.datepicker-field {
  position: relative;
  width: 100%;
  margin-bottom: 0.75rem;

  &__label {
    display: flex;
    margin-bottom: 0.25rem;
    font-family: var(--font-family-fa);
    font-weight: 300;
    line-height: 1.5rem;
    font-size: 0.875rem;
    color: var(--color-prim-gray);
    pointer-events: none;
  }

  &__trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    height: 2.35rem;
    padding: 0 1.25rem;
    border: 1px solid var(--color-thd-gray);
    border-radius: 1.5rem;
    cursor: pointer;
    font-family: var(--font-family-number-fa);
    font-size: 0.9rem;
    transition: all 0.2s;
    outline: none;

    &:focus {
      box-shadow: 0 0 0 1px var(--color-validate);
    }

    &--error {
      border-color: var(--color-error);

      &:focus {
        box-shadow: 0 0 0 1px var(--color-error);
      }
    }

    &--disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
  }

  &__value {
    flex: 1;
    text-align: start;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  &__icon {
    color: var(--color-thd-gray);
    flex-shrink: 0;
  }

  &__error {
    margin-top: 0.25rem;
    font-family: var(--font-family-fa);
    font-size: 0.75rem;
    color: var(--color-error);
  }

  &__dropdown {
    z-index: 50;
    border: 1px solid var(--color-thd-gray);
    border-radius: 0.75rem;
    background: #fff;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 10%), 0 4px 6px -4px rgba(0, 0, 0, 10%);
  }
}
</style>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: all 0.2s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: translateY(-4px);
}

.date-picker-dropdown .vpd-main .vpd-input-group {
  display: none;
}

.date-picker-dropdown .vpd-wrapper .vpd-container {
  margin: 0 !important;
  font-family: iran-yekan;
  width: 100%;
}

.date-picker-dropdown .vpd-wrapper .vpd-content {
  width: auto;
  border-radius: 8px;
}

.date-picker-dropdown .vpd-wrapper .vpd-header .vpd-date {
  display: none;
}

.date-picker-dropdown .vpd-wrapper .vpd-header {
  background-color: transparent !important;
  padding: 5px;
}

.date-picker-dropdown .vpd-wrapper .vpd-header .vpd-year-label {
  color: #5f5f5f;
  text-align: center;
}

.date-picker-dropdown .vpd-wrapper .vpd-body {
  overflow-x: hidden;
}

.date-picker-dropdown .vpd-wrapper .vpd-body .vpd-controls button {
  display: flex;
  align-items: center;
  justify-content: center;
}

.date-picker-dropdown .vpd-wrapper .vpd-body .vpd-clearfix.vpd-month .vpd-clearfix.vpd-week {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.date-picker-dropdown
  .vpd-wrapper
  .vpd-body
  .vpd-clearfix.vpd-month
  .vpd-clearfix.vpd-week
  .vpd-weekday {
  width: auto;
  float: none;
  clear: both;
  padding: 0 12px;
}

@media screen and (max-width: 400px) {
  .date-picker-dropdown .vpd-wrapper .vpd-body .vpd-clearfix.vpd-month .vpd-clearfix.vpd-week,
  .date-picker-dropdown .vpd-wrapper .vpd-body .vpd-clearfix.vpd-month .vpd-days {
    padding: 0 5px;
  }
}

.date-picker-dropdown
  .vpd-wrapper
  .vpd-body
  .vpd-clearfix.vpd-month
  .vpd-days
  .direction-prev
  .vpd-clearfix {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.date-picker-dropdown .vpd-wrapper .vpd-body .vpd-clearfix.vpd-month .vpd-days {
  height: fit-content !important;
  padding: 0 12px;
}

.date-picker-dropdown .vpd-wrapper .vpd-body .vpd-clearfix.vpd-month .vpd-days .vpd-day {
  width: 35px;
  height: 35px;
  float: right;
  line-height: 35px;
}

.date-picker-dropdown
  .vpd-wrapper
  .vpd-body
  .vpd-clearfix.vpd-month
  .vpd-days
  .vpd-day:not([disabled='true']).vpd-selected
  .vpd-day-effect,
.date-picker-dropdown
  .vpd-wrapper
  .vpd-body
  .vpd-clearfix.vpd-month
  .vpd-days
  .vpd-day:not([disabled='true']):hover
  .vpd-day-effect {
  background-color: #f36675 !important;
}

.date-picker-dropdown
  .vpd-wrapper
  .vpd-body
  .vpd-clearfix.vpd-month
  .vpd-days
  .vpd-day
  .vpd-day-effect {
  width: 35px;
  height: 35px;
  top: -2px;
  left: 0;
}

.date-picker-dropdown .vpd-wrapper .vpd-body .vpd-close-addon {
  background-color: #f5f5f5;
  border-radius: 5px;
}

.date-picker-dropdown .vpd-wrapper .vpd-body .vpd-month-label {
  width: auto;
}
</style>
