<template>
  <teleport to="body">
    <template v-if="show">
      <div
        v-bind="defaultAttributes"
        class="modal fixed inset-0 z-[60]"
        :class="{
          'px-3 md:px-0 py-3 md:py-6 overflow-x-hidden overflow-y-auto':
            modalStyle === 'window',
          'h-full': modalStyle === 'fullscreen',
        }"
        :role="role"
        :data-modal-open="show"
        :aria-modal="show"
      >
        <div
          class="@container/modal relative mx-auto z-20"
          :class="contentClasses"
          ref="modalContent"
        >
          <slot />
        </div>
      </div>

      <div
        class="fixed inset-0 z-[55] bg-gray-500/75 dark:bg-gray-900/75"
        dusk="modal-backdrop"
      />
    </template>
  </teleport>
</template>

<script setup>
import { useStore } from 'vuex'
import filter from 'lodash/filter'
import omit from 'lodash/omit'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { useEventListener } from '@vueuse/core'
import {
  computed,
  nextTick,
  onBeforeUnmount,
  onMounted,
  ref,
  useAttrs,
  watch,
} from 'vue'

const modalContent = ref(null)

const attrs = useAttrs()

const emit = defineEmits(['showing', 'closing', 'close-via-escape'])

defineOptions({ inheritAttrs: false })

const props = defineProps({
  show: { type: Boolean, default: false },
  size: {
    type: String,
    default: 'xl',
    validator: v =>
      [
        'sm',
        'md',
        'lg',
        'xl',
        '2xl',
        '3xl',
        '4xl',
        '5xl',
        '6xl',
        '7xl',
      ].includes(v),
  },
  modalStyle: { type: String, default: 'window' },
  role: { type: String, default: 'dialog' },
  useFocusTrap: { type: Boolean, default: true },
})

const usesFocusTrap = ref(true)

const hasTrapFocus = computed(() => {
  return props.useFocusTrap && usesFocusTrap.value === true
})

const { activate, deactivate } = useFocusTrap(modalContent, {
  immediate: false,
  allowOutsideClick: true,
  escapeDeactivates: false,
})

watch(
  () => props.show,
  v => handleVisibilityChange(v)
)

watch(hasTrapFocus, enable => {
  try {
    if (enable) {
      nextTick(() => activate())
    } else {
      deactivate()
    }
  } catch (e) {
    //
  }
})

useEventListener(document, 'keydown', e => {
  if (e.key === 'Escape' && props.show === true) {
    emit('close-via-escape', e)
  }
})

const disableModalFocusTrap = () => {
  usesFocusTrap.value = false
}

const enableModalFocusTrap = () => {
  usesFocusTrap.value = true
}

onMounted(() => {
  Nova.$on('disable-focus-trap', disableModalFocusTrap)
  Nova.$on('enable-focus-trap', enableModalFocusTrap)

  if (props.show === true) handleVisibilityChange(true)
})

onBeforeUnmount(() => {
  document.body.classList.remove('overflow-hidden')
  Nova.resumeShortcuts()

  Nova.$off('disable-focus-trap', disableModalFocusTrap)
  Nova.$off('enable-focus-trap', enableModalFocusTrap)

  usesFocusTrap.value = false
})

const store = useStore()

async function handleVisibilityChange(showing) {
  if (showing === true) {
    emit('showing')
    document.body.classList.add('overflow-hidden')
    Nova.pauseShortcuts()

    usesFocusTrap.value = true
  } else {
    usesFocusTrap.value = false

    emit('closing')
    document.body.classList.remove('overflow-hidden')
    Nova.resumeShortcuts()
  }

  store.commit('allowLeavingModal')
}

const defaultAttributes = computed(() => {
  return omit(attrs, ['class'])
})

const sizeClasses = computed(() => {
  return {
    sm: 'max-w-sm',
    md: 'max-w-md',
    lg: 'max-w-lg',
    xl: 'max-w-xl',
    '2xl': 'max-w-2xl',
    '3xl': 'max-w-3xl',
    '4xl': 'max-w-4xl',
    '5xl': 'max-w-5xl',
    '6xl': 'max-w-6xl',
    '7xl': 'max-w-7xl',
  }
})

const contentClasses = computed(() => {
  let windowClasses = props.modalStyle === 'window' ? sizeClasses.value : {}

  return filter([
    windowClasses[props.size] ?? null,
    props.modalStyle === 'fullscreen' ? 'h-full' : '',
    attrs.class,
  ])
})
</script>
