<template>
  <div class="week-overlay">
    <div
      v-for="span in eventSpans"
      :key="span.id"
      class="event-span"
      :class="[`event-color-${span.colorId}`]"
      :data-id="span.id"
      :data-n="span._recurrenceIndex != null ? span._recurrenceIndex : 0"
      :style="{
        gridColumn: `${span.startIdx + 1} / ${span.endIdx + 2}`,
        gridRow: `${span.row}`,
      }"
      @click="handleEventClick(span)"
      @pointerdown="handleEventPointerDown(span, $event)"
    >
      <span class="event-title">{{ span.title }}</span>
      <div
        class="resize-handle left"
        @pointerdown="handleResizePointerDown(span, 'resize-left', $event)"
      ></div>
      <div
        class="resize-handle right"
        @pointerdown="handleResizePointerDown(span, 'resize-right', $event)"
      ></div>
    </div>
  </div>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useCalendarStore } from '@/stores/CalendarStore'
import { daysInclusive, addDaysStr } from '@/utils/date'

const props = defineProps({
  week: { type: Object, required: true },
})
const emit = defineEmits(['event-click'])
const store = useCalendarStore()

// Drag state
const dragState = ref(null)
const justDragged = ref(false)

// Consolidate already-provided day.events into contiguous spans (no recurrence generation)
const eventSpans = computed(() => {
  const weekEvents = new Map()
  props.week.days.forEach((day, dayIndex) => {
    day.events.forEach((ev) => {
      const key = ev.id
      if (!weekEvents.has(key)) {
        weekEvents.set(key, { ...ev, startIdx: dayIndex, endIdx: dayIndex })
      } else {
        const ref = weekEvents.get(key)
        ref.endIdx = Math.max(ref.endIdx, dayIndex)
      }
    })
  })
  const arr = Array.from(weekEvents.values())
  arr.sort((a, b) => {
    const spanA = a.endIdx - a.startIdx
    const spanB = b.endIdx - b.startIdx
    if (spanA !== spanB) return spanB - spanA
    if (a.startIdx !== b.startIdx) return a.startIdx - b.startIdx
    // For one-day events that are otherwise equal, sort by color (0 first)
    if (spanA === 0 && spanB === 0 && a.startIdx === b.startIdx) {
      const colorA = a.colorId || 0
      const colorB = b.colorId || 0
      if (colorA !== colorB) return colorA - colorB
    }
    return String(a.id).localeCompare(String(b.id))
  })
  // Assign non-overlapping rows
  const rowsLastEnd = []
  arr.forEach((ev) => {
    let row = 0
    while (row < rowsLastEnd.length && !(ev.startIdx > rowsLastEnd[row])) row++
    if (row === rowsLastEnd.length) rowsLastEnd.push(-1)
    rowsLastEnd[row] = ev.endIdx
    ev.row = row + 1
  })
  return arr
})

function handleEventClick(span) {
  if (justDragged.value) return
  // Emit composite payload: base id (without virtual marker), instance id, occurrence index (data-n)
  const idStr = span.id
  const hasVirtualMarker = typeof idStr === 'string' && idStr.includes('_v_')
  const baseId = hasVirtualMarker ? idStr.slice(0, idStr.lastIndexOf('_v_')) : idStr
  emit('event-click', {
    id: baseId,
    instanceId: span.id,
    occurrenceIndex: span._recurrenceIndex != null ? span._recurrenceIndex : 0,
  })
}

function handleEventPointerDown(span, event) {
  if (event.target.classList.contains('resize-handle')) return
  event.stopPropagation()
  const idStr = span.id
  const hasVirtualMarker = typeof idStr === 'string' && idStr.includes('_v_')
  const baseId = hasVirtualMarker ? idStr.slice(0, idStr.lastIndexOf('_v_')) : idStr
  const isVirtual = hasVirtualMarker
  startLocalDrag(
    {
      id: baseId,
      originalId: span.id,
      isVirtual,
      mode: 'move',
      pointerStartX: event.clientX,
      pointerStartY: event.clientY,
      anchorDate: span.startDate,
      startDate: span.startDate,
      endDate: span.endDate,
    },
    event,
  )
}

function handleResizePointerDown(span, mode, event) {
  event.stopPropagation()
  const idStr = span.id
  const hasVirtualMarker = typeof idStr === 'string' && idStr.includes('_v_')
  const baseId = hasVirtualMarker ? idStr.slice(0, idStr.lastIndexOf('_v_')) : idStr
  const isVirtual = hasVirtualMarker
  startLocalDrag(
    {
      id: baseId,
      originalId: span.id,
      isVirtual,
      mode,
      pointerStartX: event.clientX,
      pointerStartY: event.clientY,
      anchorDate: null,
      startDate: span.startDate,
      endDate: span.endDate,
    },
    event,
  )
}

// Local drag handling
function startLocalDrag(init, evt) {
  const spanDays = daysInclusive(init.startDate, init.endDate)
  let anchorOffset = 0
  if (init.mode === 'move' && init.anchorDate) {
    if (init.anchorDate < init.startDate) anchorOffset = 0
    else if (init.anchorDate > init.endDate) anchorOffset = spanDays - 1
    else anchorOffset = daysInclusive(init.startDate, init.anchorDate) - 1
  }

  dragState.value = {
    ...init,
    anchorOffset,
    originSpanDays: spanDays,
    eventMoved: false,
    tentativeStart: init.startDate,
    tentativeEnd: init.endDate,
  }

  // Begin compound history session (single snapshot after drag completes)
  store.$history?.beginCompound()

  // Capture pointer events globally
  if (evt.currentTarget && evt.pointerId !== undefined) {
    try {
      evt.currentTarget.setPointerCapture(evt.pointerId)
    } catch (e) {
      console.warn('Could not set pointer capture:', e)
    }
  }

  // Prevent default for mouse/pen to avoid text selection. For touch we skip so the user can still scroll.
  if (!(evt.pointerType === 'touch')) {
    evt.preventDefault()
  }

  window.addEventListener('pointermove', onDragPointerMove, { passive: false })
  window.addEventListener('pointerup', onDragPointerUp, { passive: false })
  window.addEventListener('pointercancel', onDragPointerUp, { passive: false })
}

// Determine date under pointer: traverse DOM to find day cell carrying data-date attribute
function getDateUnderPointer(x, y, el) {
  let cur = el
  while (cur) {
    if (cur.dataset && cur.dataset.date) {
      return { date: cur.dataset.date }
    }
    cur = cur.parentElement
  }
  // Fallback: elementFromPoint scan
  const probe = document.elementFromPoint(x, y)
  let p = probe
  while (p) {
    if (p.dataset && p.dataset.date) return { date: p.dataset.date }
    p = p.parentElement
  }
  return null
}

function onDragPointerMove(e) {
  const st = dragState.value
  if (!st) return
  const dx = e.clientX - st.pointerStartX
  const dy = e.clientY - st.pointerStartY
  const distance = Math.sqrt(dx * dx + dy * dy)
  if (!st.eventMoved && distance < 5) return
  st.eventMoved = true

  const hitEl = document.elementFromPoint(e.clientX, e.clientY)
  const hit = getDateUnderPointer(e.clientX, e.clientY, hitEl)

  // If we can't find a date, don't update the range but keep the drag active
  if (!hit || !hit.date) return

  const [ns, ne] = computeTentativeRangeFromPointer(st, hit.date)
  if (!ns || !ne) return
  // Only proceed if changed
  if (ns === st.tentativeStart && ne === st.tentativeEnd) return
  st.tentativeStart = ns
  st.tentativeEnd = ne
  // Real-time update only for non-virtual events (avoid repeated split operations)
  if (!st.isVirtual) {
    applyRangeDuringDrag(
      { id: st.id, isVirtual: st.isVirtual, mode: st.mode, startDate: ns, endDate: ne },
      ns,
      ne,
    )
  }
}

function onDragPointerUp(e) {
  const st = dragState.value
  if (!st) return

  // Release pointer capture if it was set
  if (e.target && e.pointerId !== undefined) {
    try {
      e.target.releasePointerCapture(e.pointerId)
    } catch (err) {
      // Ignore errors - capture might not have been set
    }
  }

  const moved = !!st.eventMoved
  const finalStart = st.tentativeStart
  const finalEnd = st.tentativeEnd
  dragState.value = null

  window.removeEventListener('pointermove', onDragPointerMove)
  window.removeEventListener('pointerup', onDragPointerUp)
  window.removeEventListener('pointercancel', onDragPointerUp)

  if (moved) {
    // Apply final mutation if virtual (we deferred) or if non-virtual no further change (rare)
    if (st.isVirtual) {
      applyRangeDuringDrag(
        {
          id: st.id,
          isVirtual: st.isVirtual,
          mode: st.mode,
          startDate: finalStart,
          endDate: finalEnd,
        },
        finalStart,
        finalEnd,
      )
    }
    justDragged.value = true
    setTimeout(() => {
      justDragged.value = false
    }, 120)
  }
  // End compound session (snapshot if changed)
  store.$history?.endCompound()
}

function computeTentativeRangeFromPointer(st, dropDateStr) {
  const anchorOffset = st.anchorOffset || 0
  const spanDays = st.originSpanDays || daysInclusive(st.startDate, st.endDate)
  let startStr = st.startDate
  let endStr = st.endDate
  if (st.mode === 'move') {
    startStr = addDaysStr(dropDateStr, -anchorOffset)
    endStr = addDaysStr(startStr, spanDays - 1)
  } else if (st.mode === 'resize-left') {
    startStr = dropDateStr
    endStr = st.endDate
  } else if (st.mode === 'resize-right') {
    startStr = st.startDate
    endStr = dropDateStr
  }
  return normalizeDateOrder(startStr, endStr)
}

function normalizeDateOrder(aStr, bStr) {
  if (!aStr) return [bStr, bStr]
  if (!bStr) return [aStr, aStr]
  return aStr <= bStr ? [aStr, bStr] : [bStr, aStr]
}

function applyRangeDuringDrag(st, startDate, endDate) {
  if (st.isVirtual) {
    if (st.mode !== 'move') return // no resize for virtual occurrence
    // Split-move: occurrence being dragged treated as first of new series
    store.splitMoveVirtualOccurrence(st.id, st.startDate, startDate, endDate)
    return
  }
  store.setEventRange(st.id, startDate, endDate, { mode: st.mode })
}
</script>

<style scoped>
.week-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 15;
  display: grid;
  /* Prevent content from expanding tracks beyond container width */
  grid-template-columns: repeat(7, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1.5em);

  row-gap: 0.05em;
  margin-top: 1.8em;
  align-content: start;
}

.event-span {
  padding: 0.1em 0.3em;
  border-radius: 0.2em;
  font-size: clamp(0.45em, 1.8vh, 0.75em);
  font-weight: 600;
  cursor: grab;
  pointer-events: auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1;
  display: flex;
  align-items: center;
  position: relative;
  user-select: none;
  height: 100%;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  z-index: 1;
  text-align: center;
  min-width: 0;
}

/* Inner title wrapper ensures proper ellipsis within flex/grid constraints */
.event-title {
  display: block;
  flex: 1 1 0%;
  min-width: 0;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
  pointer-events: none;
}

/* Resize handles */
.event-span .resize-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 6px;
  background: transparent;
  z-index: 2;
  cursor: ew-resize;
}

.event-span .resize-handle.left {
  left: 0;
}

.event-span .resize-handle.right {
  right: 0;
}
</style>
