<template>
  <div
    ref="canvasEl"
    class="fc-canvas"
    :class="{ 'fc-canvas--zoomed': panZoom.state.scale > 1 }"
    @wheel="panZoom.handlers.onWheel"
    @pointerdown="panZoom.handlers.onPointerDown"
    @pointermove="panZoom.handlers.onPointerMove"
    @pointerup="panZoom.handlers.onPointerUp"
    @click="onCanvasClick"
  >
    <img
      ref="imgEl"
      :src="src" :alt="alt"
      :style="{
        transform: `translate(${panZoom.state.x}px, ${panZoom.state.y}px) scale(${panZoom.state.scale})`
      }"
      draggable="false"
      @load="onImgLoad"
    >
    <!-- #1206 grounding overlay: tracks the <img>'s live rendered rect (so it's
         correct under object-fit letterboxing + pan/zoom) and draws the crop
         region a hovered suggestion came from. Null grounding → a subtle
         whole-image frame ("the global vector won, not a crop"). -->
    <div
      v-if="hover && overlayBox"
      class="fc-canvas__overlay"
      :style="{
        left: overlayBox.left + 'px', top: overlayBox.top + 'px',
        width: overlayBox.width + 'px', height: overlayBox.height + 'px',
      }"
    >
      <div
        class="fc-canvas__region"
        :class="{ 'fc-canvas__region--whole': !regionStyle }"
        :style="regionStyle || {}"
      >
        <span class="fc-canvas__region-label">
          <span v-if="regionTag" class="fc-canvas__region-tag">{{ regionTag }}</span>
          <span class="fc-canvas__region-origin">{{ regionOrigin }}</span>
        </span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { usePanZoom } from '../../composables/usePanZoom.js'

const props = defineProps({
  src: String,
  alt: String,
  // Hovered-suggestion state, or null when nothing is hovered:
  //   null                             → no overlay
  //   { g: {bbox,kind,detector}, tag } → highlight that crop, label with the tag
  //   { g: null, tag }                 → whole-image frame (not localized)
  // `tag` is the hovered concept's name (primary label line); `g` is where the
  // crop came from (secondary provenance line).
  hover: { type: Object, default: null },
})
const emit = defineEmits(['close-request'])
const panZoom = usePanZoom()

const canvasEl = ref(null)
const imgEl = ref(null)
const overlayBox = ref(null)   // the <img>'s rect relative to the canvas

// The <img> renders aspect-fit + transformed, so its getBoundingClientRect is
// the ground truth for where the picture actually is. Position the overlay to
// match it, then place the region as a % within.
function measure () {
  const img = imgEl.value
  const canvas = canvasEl.value
  if (!img || !canvas) { overlayBox.value = null; return }
  const ir = img.getBoundingClientRect()
  const cr = canvas.getBoundingClientRect()
  if (!ir.width || !ir.height) { overlayBox.value = null; return }
  overlayBox.value = {
    left: ir.left - cr.left, top: ir.top - cr.top,
    width: ir.width, height: ir.height,
  }
}

const regionStyle = computed(() => {
  const g = props.hover?.g
  if (!g || !Array.isArray(g.bbox)) return null   // null grounding → whole frame
  const [rx, ry, rw, rh] = g.bbox
  return {
    left: `${rx * 100}%`, top: `${ry * 100}%`,
    width: `${rw * 100}%`, height: `${rh * 100}%`,
  }
})
// The hovered tag/suggestion name — the PRIMARY label line (what you care about).
const regionTag = computed(() => props.hover?.tag || '')
// Where the winning crop came from (the region's detector_version, else kind) —
// shown small + dimmed beneath the tag as provenance, not the headline. "whole
// image" = null grounding (the global vector won, not a crop).
const regionOrigin = computed(() => {
  const g = props.hover?.g
  return g ? (g.detector || g.kind || 'region') : 'whole image'
})

// Re-measure whenever hovering starts or the image could have moved.
watch(() => props.hover, (h) => { if (h) nextTick(measure) })
watch(
  () => [panZoom.state.scale, panZoom.state.x, panZoom.state.y],
  () => { if (props.hover) measure() },
)
function onResize () { if (props.hover) measure() }
function onImgLoad () { if (props.hover) measure() }
onMounted(() => window.addEventListener('resize', onResize))
onUnmounted(() => window.removeEventListener('resize', onResize))

// Reset zoom when the src changes (prev/next nav).
watch(() => props.src, () => panZoom.reset())

// Click on the image → toggle zoom (IR/expected behavior). Click on
// the haze area around the image → request close. We use the image's
// own bounding rect rather than @click.self because the <img> sets
// pointer-events: none for the pan-drag pathway, so the click always
// targets the canvas div regardless of where the cursor was.
function onCanvasClick(ev) {
  if (panZoom.state.scale > 1) {
    // When zoomed, any click resets zoom — preserve old behavior.
    panZoom.handlers.onClick(ev)
    return
  }
  const img = imgEl.value
  if (!img) return
  const r = img.getBoundingClientRect()
  const inside =
    ev.clientX >= r.left && ev.clientX <= r.right &&
    ev.clientY >= r.top && ev.clientY <= r.bottom
  if (inside) {
    panZoom.handlers.onClick(ev)
  } else {
    emit('close-request')
  }
}
</script>

<style scoped>
.fc-canvas {
  flex: 1;
  position: relative;
  /* Transparent so the viewer's haze shows through to the image area
     instead of a solid panel sitting on top of it. */
  background: transparent;
  overflow: hidden;
  cursor: zoom-in;
  user-select: none;
  display: flex; align-items: center; justify-content: center;
}
.fc-canvas--zoomed { cursor: grab; }
.fc-canvas--zoomed:active { cursor: grabbing; }
.fc-canvas img {
  max-width: 100%; max-height: 100%;
  transform-origin: center;
  transition: transform 100ms ease-out;
  pointer-events: none;
}

/* Grounding overlay — never intercepts pan/zoom/click. */
.fc-canvas__overlay {
  position: absolute;
  pointer-events: none;
  z-index: 2;
}
.fc-canvas__region {
  position: absolute;
  border: 2px solid rgb(var(--v-theme-accent));
  border-radius: 3px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.28);   /* dim everything outside it */
  transition: all 90ms ease-out;
}
/* Null grounding: a subtle full-image frame, no dimming — it says "this tag
   came from the whole image, not a crop." */
.fc-canvas__region--whole {
  inset: 0;
  border-style: dashed;
  border-color: rgb(var(--v-theme-on-surface-variant));
  box-shadow: none;
}
.fc-canvas__region-label {
  position: absolute;
  top: 0; left: 0;
  transform: translateY(-100%);
  display: flex; flex-direction: column;
  line-height: 1.25;
  padding: 2px 6px;
  white-space: nowrap;
  background: rgb(var(--v-theme-accent));
  color: rgb(var(--v-theme-on-surface));
  border-radius: 3px 3px 0 0;
}
/* The hovered tag is the headline. */
.fc-canvas__region-tag {
  font-size: 12px;
  font-weight: 600;
}
/* Crop origin is provenance, not the point — subordinate: smaller, dimmed, and
   monospace (it's a detector token like booru:head, not prose). */
.fc-canvas__region-origin {
  font-size: 10px;
  font-family: 'JetBrains Mono', monospace;
  color: rgb(var(--v-theme-on-surface), 0.72);
}
.fc-canvas__region--whole .fc-canvas__region-label {
  background: rgb(var(--v-theme-on-surface-variant));
}
</style>
