<template>
  <Teleport to="body">
    <Transition name="sheet">
      <div
        v-if="modelValue"
        class="sheet-overlay"
        role="dialog"
        :aria-label="label"
        aria-modal="true"
        @click.self="close"
        @keydown.esc="close"
      >
        <div
          ref="sheetRef"
          class="sheet"
          :class="{ 'sheet--dragging': isDragging }"
          :style="dragY > 0 ? { transform: `translateY(${dragY}px)` } : undefined"
          tabindex="-1"
        >
          <div
            class="sheet__handle-target"
            @touchstart.passive="onDragStart"
            @touchmove.passive="onDragMove"
            @touchend="onDragEnd"
            @touchcancel="onDragEnd"
            @pointerdown="onPointerStart"
            aria-hidden="true"
          >
            <div class="sheet__handle" />
          </div>
          <slot />
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'

const props = defineProps<{
  modelValue: boolean
  label: string
}>()

const emit = defineEmits<{
  'update:modelValue': [value: boolean]
}>()

const sheetRef = ref<HTMLElement | null>(null)
const dragY = ref(0)
const isDragging = ref(false)
let dragStartY = 0
let pointerId: number | null = null
let triggerEl: HTMLElement | null = null

const DISMISS_THRESHOLD = 80

function close() {
  emit('update:modelValue', false)
}

function onDragStart(e: TouchEvent) {
  dragStartY = e.touches[0].clientY
  isDragging.value = true
  dragY.value = 0
}

function onDragMove(e: TouchEvent) {
  if (!isDragging.value) return
  const delta = e.touches[0].clientY - dragStartY
  dragY.value = delta > 0 ? delta : 0
}

function onDragEnd() {
  if (!isDragging.value) return
  isDragging.value = false
  if (dragY.value > DISMISS_THRESHOLD) {
    close()
  }
  dragY.value = 0
}

// Pointer events let desktop / non-touch testing exercise the drag too.
function onPointerStart(e: PointerEvent) {
  if (e.pointerType === 'touch') return // touch events handle this
  dragStartY = e.clientY
  isDragging.value = true
  dragY.value = 0
  pointerId = e.pointerId
  ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
  window.addEventListener('pointermove', onPointerMove)
  window.addEventListener('pointerup', onPointerEnd)
  window.addEventListener('pointercancel', onPointerEnd)
}

function onPointerMove(e: PointerEvent) {
  if (!isDragging.value || e.pointerId !== pointerId) return
  const delta = e.clientY - dragStartY
  dragY.value = delta > 0 ? delta : 0
}

function onPointerEnd(e: PointerEvent) {
  if (e.pointerId !== pointerId) return
  pointerId = null
  window.removeEventListener('pointermove', onPointerMove)
  window.removeEventListener('pointerup', onPointerEnd)
  window.removeEventListener('pointercancel', onPointerEnd)
  onDragEnd()
}

watch(() => props.modelValue, async (open) => {
  if (open) {
    triggerEl = document.activeElement as HTMLElement
    await nextTick()
    sheetRef.value?.focus()
  } else {
    triggerEl?.focus()
    triggerEl = null
    dragY.value = 0
    isDragging.value = false
  }
})
</script>

<style scoped lang="scss">
.sheet-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 100;
  display: flex;
  align-items: flex-end;
}

.sheet {
  width: 100%;
  background: var(--color-surface);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: 0 var(--space-4) calc(var(--space-6) + env(safe-area-inset-bottom));
  max-height: 90dvh;
  overflow-y: auto;
  outline: none;
  transition: transform 200ms var(--ease-out);

  &--dragging {
    transition: none;
  }

  &__handle-target {
    padding: var(--space-3) 0 var(--space-4);
    margin: 0 calc(-1 * var(--space-4));
    display: flex;
    justify-content: center;
    cursor: grab;
    touch-action: none;

    &:active { cursor: grabbing; }
  }

  &__handle {
    width: 36px;
    height: 4px;
    border-radius: var(--radius-full);
    background: var(--color-border);
  }
}

.sheet-enter-active {
  .sheet-overlay { transition: background var(--duration-base) var(--ease-out); }
  .sheet { transition: transform 250ms var(--ease-out); }
}

.sheet-leave-active {
  .sheet { transition: transform 200ms ease-in; }
  transition: background 200ms ease-in;
}

.sheet-enter-from {
  background: rgba(0, 0, 0, 0);
  .sheet { transform: translateY(100%); }
}

.sheet-leave-to {
  background: rgba(0, 0, 0, 0);
  .sheet { transform: translateY(100%); }
}
</style>
