<script setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'

const props = defineProps({
  modelValue: { type: Boolean, default: false },
  title: { type: String, default: '' },
  draggable: { type: Boolean, default: true },
  autoFocus: { type: Boolean, default: true },
  // Optional external anchor element (e.g., a day cell) to position the dialog below.
  // If not provided, falls back to internal anchorRef span (original behavior).
  anchorEl: { type: Object, default: null },
})

const emit = defineEmits(['update:modelValue', 'opened', 'closed', 'submit'])

const modalRef = ref(null)
const anchorRef = ref(null)
const isDragging = ref(false)
const dragOffset = ref({ x: 0, y: 0 })
const modalPosition = ref({ x: 0, y: 0 })
const dialogWidth = ref(null)
const dialogHeight = ref(null)
const hasMoved = ref(false)
const margin = 8 // viewport margin in px to keep dialog from touching edges

function clamp(val, min, max) {
  return Math.min(Math.max(val, min), max)
}

function startDrag(event) {
  if (!props.draggable || !modalRef.value) return
  const rect = modalRef.value.getBoundingClientRect()
  // Lock current size so moving doesn't cause reflow / resize
  dialogWidth.value = rect.width
  dialogHeight.value = rect.height
  // Initialize position to current on-screen coordinates BEFORE enabling moved mode
  modalPosition.value = { x: rect.left, y: rect.top }
  isDragging.value = true
  hasMoved.value = true
  dragOffset.value = { x: event.clientX - rect.left, y: event.clientY - rect.top }
  if (event.pointerId !== undefined) {
    try {
      event.target.setPointerCapture(event.pointerId)
    } catch {}
  }
  document.addEventListener('pointermove', handleDrag, { passive: false })
  document.addEventListener('pointerup', stopDrag)
  document.addEventListener('pointercancel', stopDrag)
  event.preventDefault()
}
function handleDrag(event) {
  if (!isDragging.value) return
  let x = event.clientX - dragOffset.value.x
  let y = event.clientY - dragOffset.value.y
  const w = dialogWidth.value || modalRef.value?.offsetWidth || 0
  const h = dialogHeight.value || modalRef.value?.offsetHeight || 0
  const vw = window.innerWidth
  const vh = window.innerHeight
  x = clamp(x, margin, Math.max(margin, vw - w - margin))
  y = clamp(y, margin, Math.max(margin, vh - h - margin))
  modalPosition.value = { x, y }
  event.preventDefault()
}
function stopDrag() {
  isDragging.value = false
  document.removeEventListener('pointermove', handleDrag)
  document.removeEventListener('pointerup', stopDrag)
  document.removeEventListener('pointercancel', stopDrag)
}

const modalStyle = computed(() => {
  // Always position relative to calculated modalPosition once opened
  if (modalRef.value && props.modelValue) {
    const style = {
      transform: 'none',
      left: modalPosition.value.x + 'px',
      top: modalPosition.value.y + 'px',
      bottom: 'auto',
      right: 'auto',
    }
    if (hasMoved.value) {
      style.width = dialogWidth.value ? dialogWidth.value + 'px' : undefined
      style.height = dialogHeight.value ? dialogHeight.value + 'px' : undefined
    }
    return style
  }
  return {}
})

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

function handleKeydown(e) {
  if (e.key === 'Escape' && props.modelValue) close()
}

onMounted(() => document.addEventListener('keydown', handleKeydown))
onUnmounted(() => document.removeEventListener('keydown', handleKeydown))

function positionNearAnchor() {
  const anchor = props.anchorEl || anchorRef.value
  if (!anchor) return
  const rect = anchor.getBoundingClientRect()
  const offsetY = 8 // vertical gap below the anchor
  const w = modalRef.value?.offsetWidth || dialogWidth.value || 320
  const h = modalRef.value?.offsetHeight || dialogHeight.value || 200
  const vw = window.innerWidth
  const vh = window.innerHeight
  let x = rect.left
  let y = rect.bottom + offsetY
  // If anchor is wider than dialog and would overflow right edge, clamp; otherwise keep left align
  x = clamp(x, margin, Math.max(margin, vw - w - margin))
  y = clamp(y, margin, Math.max(margin, vh - h - margin))
  modalPosition.value = { x, y }
}

watch(
  () => props.modelValue,
  async (v) => {
    if (v) {
      emit('opened')
      await nextTick()
      // Reset movement state each time opened
      hasMoved.value = false
      dialogWidth.value = null
      dialogHeight.value = null
      positionNearAnchor()
      if (props.autoFocus) {
        const el = modalRef.value?.querySelector('[autofocus]')
        if (el) el.focus()
      }
    }
  },
)

// Reposition if anchorEl changes while open and user hasn't dragged dialog yet
watch(
  () => props.anchorEl,
  () => {
    if (props.modelValue && !hasMoved.value) {
      nextTick(() => positionNearAnchor())
    }
  },
)

function handleResize() {
  if (!props.modelValue) return
  // Re-clamp current position, and if not moved recalc near anchor
  if (!hasMoved.value) positionNearAnchor()
  else if (modalRef.value) {
    const w = modalRef.value.offsetWidth
    const h = modalRef.value.offsetHeight
    const vw = window.innerWidth
    const vh = window.innerHeight
    modalPosition.value = {
      x: clamp(modalPosition.value.x, margin, Math.max(margin, vw - w - margin)),
      y: clamp(modalPosition.value.y, margin, Math.max(margin, vh - h - margin)),
    }
  }
}

onMounted(() => {
  window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
})
</script>

<template>
  <span ref="anchorRef" class="ec-modal-anchor" aria-hidden="true"></span>
  <div v-if="modelValue" class="ec-modal" ref="modalRef" :style="modalStyle">
    <form class="ec-form" @submit.prevent="emit('submit')">
      <header class="ec-header" @pointerdown="startDrag">
        <h2 class="ec-title">
          <slot name="title">{{ title }}</slot>
        </h2>
        <div class="ec-header-extra"><slot name="header-extra" /></div>
      </header>
      <div class="ec-body">
        <slot />
      </div>
      <footer v-if="$slots.footer" class="ec-footer">
        <slot name="footer" />
      </footer>
    </form>
  </div>
</template>

<style scoped>
.ec-modal {
  position: fixed; /* still fixed for overlay & dragging, but now top/left are set dynamically */
  background: color-mix(in srgb, var(--panel) 85%, transparent);
  backdrop-filter: blur(0.625em);
  -webkit-backdrop-filter: blur(0.625em);
  color: var(--ink);
  border-radius: 0.6em;
  min-height: 23em;
  min-width: 26em;
  max-width: min(34em, 90vw);
  box-shadow: 0 0.6em 1.8em rgba(0, 0, 0, 0.35);
  border: 0.0625em solid color-mix(in srgb, var(--muted) 40%, transparent);
  z-index: 1000;
  overflow: hidden;
}
.ec-modal-anchor {
  display: inline-block;
  width: 0;
  height: 0;
}
.ec-form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 23em;
  height: 100%;
  width: 100%;
}
.ec-header {
  cursor: move;
  user-select: none;
  padding: 0.75em 1em 0.5em 1em;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1em;
}
.ec-title {
  margin: 0;
  font-size: 1.1em;
}
.ec-body {
  display: flex;
  flex-direction: column;
  gap: 1em;
  padding: 0 1em 0.5em 1em;
  overflow: auto;
}
.ec-footer {
  padding: 0.5em 1em 1em 1em;
  display: flex;
  justify-content: space-between;
  gap: 1em;
  flex-wrap: wrap;
}
</style>
