// ── Drag & Drop composable: job drag, tech drag, block move, block resize, batch drag ──
import { ref } from 'vue'
import { snapH, hToTime, fmtDur, localDateStr, SNAP, serializeAssistants } from './useHelpers'
import { updateJob } from 'src/api/dispatch'

export function useDragDrop (deps) {
  const {
    store, pxPerHr, dayW, periodStart, periodDays, H_START,
    getJobDate, bottomSelected, multiSelect,
    pushUndo, smartAssign, invalidateRoutes,
  } = deps

  const dragJob       = ref(null)
  const dragSrc       = ref(null)
  const dragIsAssist  = ref(false)
  const dropGhost     = ref(null)
  const dragTech      = ref(null)
  const dragBatchIds  = ref(null)

  function cleanupDropIndicators () {
    document.querySelectorAll('.sb-block-drop-hover').forEach(el => el.classList.remove('sb-block-drop-hover'))
    dropGhost.value = null
  }

  function onJobDragStart (e, job, srcTechId, isAssist = false) {
    dragJob.value = job; dragSrc.value = srcTechId || null; dragIsAssist.value = isAssist
    if (!srcTechId && bottomSelected.value.size > 1 && bottomSelected.value.has(job.id)) {
      dragBatchIds.value = new Set(bottomSelected.value)
      e.dataTransfer.setData('text/plain', `batch:${dragBatchIds.value.size}`)
    } else {
      dragBatchIds.value = null
    }
    e.dataTransfer.effectAllowed = 'move'
    e.target.addEventListener('dragend', () => { cleanupDropIndicators(); dragIsAssist.value = false; dragBatchIds.value = null }, { once: true })
  }

  function onTimelineDragOver (e, tech) {
    e.preventDefault()
    if (!dragJob.value && !dragTech.value) return
    const x = e.clientX - e.currentTarget.getBoundingClientRect().left
    dropGhost.value = { techId: tech.id, x, dateStr: xToDateStr(x) }
  }

  function onTimelineDragLeave (e) {
    if (!e.currentTarget.contains(e.relatedTarget)) dropGhost.value = null
  }

  function onTechDragStart (e, tech) {
    dragTech.value = tech
    e.dataTransfer.effectAllowed = 'copyMove'
    e.dataTransfer.setData('text/plain', tech.id)
    e.target.addEventListener('dragend', () => { dragTech.value = null; cleanupDropIndicators() }, { once: true })
    return tech
  }

  function onBlockDrop (e, job) {
    if (dragTech.value) {
      e.preventDefault(); e.stopPropagation()
      cleanupDropIndicators()
      pushUndo({ type: 'removeAssistant', jobId: job.id, techId: dragTech.value.id, duration: 0, note: '' })
      store.addAssistant(job.id, dragTech.value.id)
      dragTech.value = null
      invalidateRoutes()
    }
  }

  function assignDroppedJob (tech, dateStr) {
    if (!dragJob.value) return
    if (dragIsAssist.value) {
      dragJob.value = null; dragSrc.value = null; dragIsAssist.value = false; return
    }
    if (dragBatchIds.value && dragBatchIds.value.size > 1) {
      const prevStates = []
      dragBatchIds.value.forEach(jobId => {
        const j = store.jobs.find(x => x.id === jobId)
        if (j && !j.assignedTech) {
          prevStates.push({ jobId: j.id, techId: j.assignedTech, routeOrder: j.routeOrder, scheduledDate: j.scheduledDate, assistants: [...(j.assistants || [])] })
          smartAssign(j, tech.id, dateStr)
        }
      })
      if (prevStates.length) pushUndo({ type: 'batchAssign', assignments: prevStates, targetTechId: tech.id })
      bottomSelected.value = new Set()
      dragBatchIds.value = null
    } else if (multiSelect && multiSelect.value?.length > 1 && multiSelect.value.some(s => s.job.id === dragJob.value.id)) {
      // Dragging a multi-selected block from timeline — move all selected
      const prevStates = []
      const prevQueues = {}
      store.technicians.forEach(t => { prevQueues[t.id] = [...t.queue] })
      multiSelect.value.filter(s => !s.isAssist).forEach(s => {
        prevStates.push({ jobId: s.job.id, techId: s.job.assignedTech, routeOrder: s.job.routeOrder, scheduledDate: s.job.scheduledDate, assistants: [...(s.job.assistants || [])] })
        smartAssign(s.job, tech.id, dateStr)
      })
      if (prevStates.length) pushUndo({ type: 'batchAssign', assignments: prevStates, prevQueues })
      multiSelect.value = []
    } else {
      const job = dragJob.value
      pushUndo({ type: 'unassignJob', jobId: job.id, techId: job.assignedTech, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...(job.assistants || [])] })
      smartAssign(job, tech.id, dateStr)
    }
    dropGhost.value = null; dragJob.value = null; dragSrc.value = null
    invalidateRoutes()
  }

  function onTimelineDrop (e, tech) {
    e.preventDefault()
    cleanupDropIndicators()

    if (dragTech.value) {
      const els = document.elementsFromPoint(e.clientX, e.clientY)
      const blockEl = els.find(el => el.dataset?.jobId)
      if (blockEl) {
        const job = store.jobs.find(j => j.id === blockEl.dataset.jobId)
        if (job) {
          pushUndo({ type: 'removeAssistant', jobId: job.id, techId: dragTech.value.id, duration: 0, note: '' })
          store.addAssistant(job.id, dragTech.value.id)
          dragTech.value = null; invalidateRoutes(); return
        }
      }
      dragTech.value = null; return
    }

    if (!dragJob.value) return

    if (dragJob.value.assignedTech === tech.id) {
      const rect = e.currentTarget.getBoundingClientRect()
      const x = (e.clientX || e.pageX) - rect.left
      const dropH = H_START.value + x / pxPerHr.value
      const dayStr = localDateStr(periodStart.value)
      pushUndo({ type: 'optimizeRoute', techId: tech.id, prevQueue: [...tech.queue] })
      const draggedJob = dragJob.value
      tech.queue = tech.queue.filter(j => j.id !== draggedJob.id)
      const dayJobs = tech.queue.filter(j => getJobDate(j.id) === dayStr)
      const queueDayStart = tech.queue.findIndex(j => getJobDate(j.id) === dayStr)
      let slot = dayJobs.length, cursor = 8
      for (let i = 0; i < dayJobs.length; i++) {
        const dur = parseFloat(dayJobs[i].duration) || 1
        if (dropH < cursor + dur / 2) { slot = i; break }
        cursor += dur + 0.5
      }
      const insertAt = queueDayStart >= 0 ? queueDayStart + slot : tech.queue.length
      tech.queue.splice(insertAt, 0, draggedJob)
      tech.queue.forEach((q, i) => { q.routeOrder = i; updateJob(q.name || q.id, { route_order: i }).catch(() => {}) })
      dragJob.value = null; dragSrc.value = null; invalidateRoutes(); return
    }

    if (dragIsAssist.value) {
      dragJob.value = null; dragSrc.value = null; dragIsAssist.value = false; return
    }

    assignDroppedJob(tech, xToDateStr(e.clientX - e.currentTarget.getBoundingClientRect().left))
  }

  function onCalDrop (e, tech, dateStr) { assignDroppedJob(tech, dateStr) }

  function xToDateStr (x) {
    const di = Math.max(0, Math.min(periodDays.value - 1, Math.floor(x / dayW.value)))
    const d = new Date(periodStart.value); d.setDate(d.getDate() + di)
    return localDateStr(d)
  }

  function startBlockMove (e, job, block) {
    if (e.button !== 0) return
    const startX = e.clientX, startY = e.clientY
    const startLeft = parseFloat(block.style.left) || 0
    let moving = false
    function onMove (ev) {
      const dx = ev.clientX - startX, dy = ev.clientY - startY
      if (!moving && Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 5) { cleanup(); return }
      if (!moving && Math.abs(dx) > 5) { moving = true; block.style.zIndex = '10' }
      if (!moving) return
      ev.preventDefault()
      const newLeft = Math.max(0, startLeft + dx)
      const newH = snapH(H_START.value + newLeft / pxPerHr.value)
      block.style.left = ((newH - H_START.value) * pxPerHr.value) + 'px'
      const meta = block.querySelector('.sb-block-meta')
      if (meta) meta.textContent = `${hToTime(newH)} · ${fmtDur(job.duration)}`
    }
    function cleanup () {
      document.removeEventListener('mousemove', onMove)
      document.removeEventListener('mouseup', onUp)
    }
    function onUp (ev) {
      cleanup()
      if (!moving) return
      block.style.zIndex = ''
      const dx = ev.clientX - startX
      const newH = snapH(H_START.value + Math.max(0, startLeft + dx) / pxPerHr.value)
      job.startHour = newH; job.startTime = hToTime(newH)
      store.setJobSchedule(job.id, job.scheduledDate, hToTime(newH))
      invalidateRoutes()
    }
    document.addEventListener('mousemove', onMove)
    document.addEventListener('mouseup', onUp)
  }

  function startResize (e, job, mode, assistTechId) {
    e.preventDefault()
    const startX = e.clientX
    const startDur = mode === 'assist'
      ? (job.assistants.find(a => a.techId === assistTechId)?.duration || job.duration)
      : job.duration
    const block = e.target.parentElement
    const startW = block.offsetWidth
    function onMove (ev) {
      const dx = ev.clientX - startX
      const newDur = Math.max(SNAP, snapH(Math.max(18, startW + dx) / pxPerHr.value))
      block.style.width = (newDur * pxPerHr.value) + 'px'
      const meta = block.querySelector('.sb-block-meta')
      if (meta) meta.textContent = mode === 'assist' ? `assistant · ${fmtDur(newDur)}` : fmtDur(newDur)
    }
    function onUp (ev) {
      document.removeEventListener('mousemove', onMove)
      document.removeEventListener('mouseup', onUp)
      const dx = ev.clientX - startX
      const newDur = Math.max(SNAP, snapH(Math.max(18, startW + dx) / pxPerHr.value))
      if (mode === 'assist' && assistTechId) {
        const assist = job.assistants.find(a => a.techId === assistTechId)
        if (assist) {
          assist.duration = newDur
          updateJob(job.name || job.id, {
            assistants: serializeAssistants(job.assistants),
          }).catch(() => {})
        }
      } else {
        job.duration = newDur
        updateJob(job.name || job.id, { duration_h: newDur }).catch(() => {})
      }
      invalidateRoutes()
    }
    document.addEventListener('mousemove', onMove)
    document.addEventListener('mouseup', onUp)
  }

  return {
    dragJob, dragSrc, dragIsAssist, dropGhost, dragTech, dragBatchIds,
    cleanupDropIndicators,
    onJobDragStart, onTimelineDragOver, onTimelineDragLeave,
    onTechDragStart, onBlockDrop,
    assignDroppedJob, onTimelineDrop, onCalDrop, xToDateStr,
    startBlockMove, startResize,
  }
}
