<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useMosaicStore } from '@/stores/mosaic'
import { rgbToHex, relativeLuminance } from '@/utils/colors'
import { forEachMaskRun, resolveRegionLabelPosition } from '@/utils/regionMask'
import { useCanvasViewport } from '@/composables/useCanvasViewport'
import ImageDropzone from './ImageDropzone.vue'

const store = useMosaicStore()
const { imageCanvas, imageWidth, imageHeight, regions, groups, selectedGroupId, selectedRegionId, settings, isAnalyzing } = storeToRefs(store)
const host = ref<HTMLDivElement | null>(null)
const canvas = ref<HTMLCanvasElement | null>(null)
const { viewport, fit, zoomAt } = useCanvasViewport()
let observer: ResizeObserver | null = null
let dragStart: { x: number; y: number; viewportX: number; viewportY: number } | null = null
let moved = false

function resize(): void {
  const element = canvas.value
  const container = host.value
  if (!element || !container) return
  const rectangle = container.getBoundingClientRect()
  const ratio = window.devicePixelRatio || 1
  element.width = Math.max(1, Math.round(rectangle.width * ratio))
  element.height = Math.max(1, Math.round(rectangle.height * ratio))
  element.style.width = `${rectangle.width}px`
  element.style.height = `${rectangle.height}px`
  draw()
}

function resetViewport(): void {
  const rectangle = host.value?.getBoundingClientRect()
  if (!rectangle) return
  fit(rectangle.width, rectangle.height, imageWidth.value, imageHeight.value)
  draw()
}

function draw(): void {
  const element = canvas.value
  if (!element) return
  const context = element.getContext('2d')
  if (!context) return
  const ratio = window.devicePixelRatio || 1
  const cssWidth = element.width / ratio
  const cssHeight = element.height / ratio
  context.setTransform(ratio, 0, 0, ratio, 0, 0)
  context.clearRect(0, 0, cssWidth, cssHeight)
  context.fillStyle = '#e7ebf0'
  context.fillRect(0, 0, cssWidth, cssHeight)
  if (!imageCanvas.value) return

  context.save()
  context.translate(viewport.x, viewport.y)
  context.scale(viewport.scale, viewport.scale)
  context.imageSmoothingEnabled = viewport.scale < 3
  context.drawImage(imageCanvas.value, 0, 0)
  const groupMap = new Map(groups.value.map((group) => [group.id, group]))
  for (const region of regions.value) {
    const group = groupMap.get(region.groupId)
    if (!group) continue
    const selected = group.id === selectedGroupId.value
    if (!selected && selectedGroupId.value && settings.value.otherGroupsMode === 'hidden') continue
    context.globalAlpha = selected ? 0.52 : selectedGroupId.value && settings.value.otherGroupsMode === 'dim' ? 0.08 : 0.24
    context.fillStyle = rgbToHex(group.color)
    forEachMaskRun(region.mask, (y, xStart, xEnd) => context.fillRect(xStart, y, xEnd - xStart + 1, 1))
  }
  context.restore()

  context.save()
  context.setTransform(ratio, 0, 0, ratio, 0, 0)
  context.textAlign = 'center'
  context.textBaseline = 'middle'
  context.font = '700 14px system-ui, sans-serif'
  for (const region of regions.value) {
    const group = groupMap.get(region.groupId)
    if (!group) continue
    if (selectedGroupId.value && settings.value.otherGroupsMode === 'hidden' && group.id !== selectedGroupId.value) continue
    const labelPosition = resolveRegionLabelPosition(region, settings.value.useUserClickPositionForLabels)
    const x = viewport.x + labelPosition.x * viewport.scale
    const y = viewport.y + labelPosition.y * viewport.scale
    const active = region.id === selectedRegionId.value
    context.beginPath()
    context.arc(x, y, active ? 13 : 11, 0, Math.PI * 2)
    context.fillStyle = relativeLuminance(group.color) > 0.45 ? 'rgba(255,255,255,.94)' : 'rgba(15,23,42,.9)'
    context.fill()
    if (active) {
      context.lineWidth = 2
      context.strokeStyle = '#2563eb'
      context.stroke()
    }
    context.fillStyle = relativeLuminance(group.color) > 0.45 ? '#0f172a' : '#ffffff'
    context.fillText(String(group.number), x, y + 0.5)
  }
  context.restore()
}

function pointerDown(event: PointerEvent): void {
  if (!imageCanvas.value) return
  canvas.value?.setPointerCapture(event.pointerId)
  dragStart = { x: event.clientX, y: event.clientY, viewportX: viewport.x, viewportY: viewport.y }
  moved = false
}

function pointerMove(event: PointerEvent): void {
  if (!dragStart) return
  const dx = event.clientX - dragStart.x
  const dy = event.clientY - dragStart.y
  if (Math.hypot(dx, dy) > 4) moved = true
  if (moved) {
    viewport.x = dragStart.viewportX + dx
    viewport.y = dragStart.viewportY + dy
    draw()
  }
}

function pointerUp(event: PointerEvent): void {
  const start = dragStart
  dragStart = null
  if (!start || moved || !canvas.value || !imageCanvas.value) return
  const rectangle = canvas.value.getBoundingClientRect()
  const x = Math.floor((event.clientX - rectangle.left - viewport.x) / viewport.scale)
  const y = Math.floor((event.clientY - rectangle.top - viewport.y) / viewport.scale)
  if (x < 0 || y < 0 || x >= imageWidth.value || y >= imageHeight.value) return
  store.addRegionAt(x, y)
}

function wheel(event: WheelEvent): void {
  if (!imageCanvas.value || !canvas.value) return
  event.preventDefault()
  const rectangle = canvas.value.getBoundingClientRect()
  zoomAt(event.deltaY < 0 ? 1.13 : 1 / 1.13, event.clientX - rectangle.left, event.clientY - rectangle.top)
  draw()
}

watch([imageCanvas, imageWidth, imageHeight], async () => { await nextTick(); resetViewport() })
watch([
  regions,
  groups,
  selectedGroupId,
  selectedRegionId,
  () => settings.value.otherGroupsMode,
  () => settings.value.useUserClickPositionForLabels,
], draw, { deep: true })

onMounted(() => {
  observer = new ResizeObserver(resize)
  if (host.value) observer.observe(host.value)
  resize()
})
onBeforeUnmount(() => observer?.disconnect())

defineExpose({ resetViewport })
</script>

<template>
  <div ref="host" class="canvas-host" :class="{ analyzing: isAnalyzing }">
    <canvas
      ref="canvas"
      aria-label="Editor mozaiky"
      @pointerdown="pointerDown"
      @pointermove="pointerMove"
      @pointerup="pointerUp"
      @pointercancel="dragStart = null"
      @wheel="wheel"
    />
    <ImageDropzone v-if="!imageCanvas" @file="store.loadImage($event, $event.name)" />
    <div v-if="isAnalyzing" class="busy"><span class="spinner" />Analyzujem plochu…</div>
    <div v-if="imageCanvas" class="canvas-help">Kliknutie označí plochu · ťahanie posúva · koliesko približuje</div>
  </div>
</template>

<style scoped>
.canvas-host { position: relative; width: 100%; height: 100%; min-height: 460px; overflow: hidden; background: #e7ebf0; }
canvas { display: block; touch-action: none; cursor: crosshair; }
.canvas-host:active canvas { cursor: grabbing; }
.canvas-host.analyzing canvas { cursor: progress; }
.canvas-help { position: absolute; right: 12px; bottom: 12px; padding: 6px 9px; border: 1px solid rgba(148,163,184,.6); border-radius: 7px; background: rgba(255,255,255,.88); color: #475569; font-size: .72rem; pointer-events: none; backdrop-filter: blur(4px); }
.busy { position: absolute; top: 14px; left: 50%; display: flex; align-items: center; gap: 8px; transform: translateX(-50%); padding: 8px 12px; border-radius: 8px; background: #0f172a; color: white; font-size: .82rem; box-shadow: 0 5px 20px #0f172a33; }
.spinner { width: 13px; height: 13px; border: 2px solid #ffffff55; border-top-color: white; border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (max-width: 800px) { .canvas-host { min-height: 55vh; } .canvas-help { display: none; } }
</style>
