<template>
  <Transition name="collapse-transition" v-on="bindings">
    <slot />
  </Transition>
</template>

<script lang="ts" setup>
import type { RendererElement } from 'vue'

defineOptions({
  name: 'CollapseTransition',
})

const emit = defineEmits<{
  expanded: []
  collapsed: []
}>()

const bindings = {
  beforeEnter(el: RendererElement) {
    if (!el.dataset)
      el.dataset = {}

    el.dataset.oldPaddingTop = el.style.paddingTop
    el.dataset.oldPaddingBottom = el.style.paddingBottom
    el.dataset.elExistsHeight = el.style.height ?? undefined
    el.style.maxHeight = 0
    el.style.paddingTop = 0
    el.style.paddingBottom = 0
  },

  enter(el: RendererElement) {
    requestAnimationFrame(() => {
      el.dataset.oldOverflow = el.style.overflow
      if (el.dataset.elExistsHeight) {
        el.style.maxHeight = el.dataset.elExistsHeight
      }
      else if (el.scrollHeight !== 0) {
        el.style.maxHeight = `${el.scrollHeight}px`
      }
      else {
        el.style.maxHeight = 0
      }

      el.style.paddingTop = el.dataset.oldPaddingTop
      el.style.paddingBottom = el.dataset.oldPaddingBottom
      el.style.overflow = 'hidden'
    })
  },

  afterEnter(el: RendererElement) {
    el.style.maxHeight = ''
    el.style.overflow = el.dataset.oldOverflow

    emit('expanded')
  },

  enterCancelled(el: RendererElement) {
    reset(el)
  },

  beforeLeave(el: RendererElement) {
    if (!el.dataset)
      el.dataset = {}
    el.dataset.oldPaddingTop = el.style.paddingTop
    el.dataset.oldPaddingBottom = el.style.paddingBottom
    el.dataset.oldOverflow = el.style.overflow

    el.style.maxHeight = `${el.scrollHeight}px`
    el.style.overflow = 'hidden'
  },

  leave(el: RendererElement) {
    if (el.scrollHeight !== 0) {
      el.style.maxHeight = 0
      el.style.paddingTop = 0
      el.style.paddingBottom = 0
    }
  },

  afterLeave(el: RendererElement) {
    reset(el)

    emit('collapsed')
  },

  leaveCancelled(el: RendererElement) {
    reset(el)
  },
}

function reset(el: RendererElement) {
  el.style.maxHeight = ''
  el.style.overflow = el.dataset.oldOverflow
  el.style.paddingTop = el.dataset.oldPaddingTop
  el.style.paddingBottom = el.dataset.oldPaddingBottom
}
</script>

<style lang="scss">
.collapse-transition {
  transition-property: height, padding-top, padding-bottom;
  transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
  transition-duration: var(--default-transition-duration, 150ms);
}

.collapse-transition-leave-active,
.collapse-transition-enter-active {
  transition-property: opacity, max-height, padding-top, padding-bottom;
  transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
  transition-duration: var(--default-transition-duration, 150ms);
}

.collapse-transition-leave-to,
.collapse-transition-enter-from {
  opacity: 0;
}
</style>
