<template>
  <Teleport to="body">
    <div
      class="fc-viewer" role="dialog" aria-modal="true"
      tabindex="-1" ref="rootEl"
    >
      <button class="fc-viewer__close" @click="$emit('close')" aria-label="Close">
        <v-icon>mdi-close</v-icon>
      </button>

      <v-chip
        v-if="integrityBadge"
        :color="integrityBadge.color"
        class="fc-viewer__integrity"
        size="x-small" variant="flat"
      >{{ integrityBadge.label }}</v-chip>

      <button
        class="fc-viewer__nav fc-viewer__nav--prev"
        :disabled="!modal.canPrev" @click="modal.goPrev()" aria-label="Previous"
      >
        <v-icon size="32">mdi-chevron-left</v-icon>
      </button>
      <button
        class="fc-viewer__nav fc-viewer__nav--next"
        :disabled="!modal.canNext" @click="modal.goNext()" aria-label="Next"
      >
        <v-icon size="32">mdi-chevron-right</v-icon>
      </button>

      <div class="fc-viewer__body">
        <div class="fc-viewer__media">
          <v-progress-circular
            v-if="modal.loading" indeterminate color="accent" size="36"
          />
          <template v-else-if="modal.current">
            <ImageCanvas
              v-if="!isVideo" :src="modal.current.image_url" :alt="`Image ${modal.current.id}`"
              @close-request="$emit('close')"
            />
            <VideoCanvas
              v-else :src="modal.current.image_url" :mime="modal.current.mime"
              @close-request="$emit('close')"
            />
          </template>
          <v-alert v-else-if="modal.error" type="error" variant="tonal">
            {{ modal.error }}
          </v-alert>
        </div>

        <aside v-if="modal.current" class="fc-viewer__side">
          <ProvenancePanel />
          <TagPanel />
        </aside>
      </div>
    </div>
  </Teleport>
</template>

<script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useModalStore } from '../../stores/modal.js'
import ImageCanvas from './ImageCanvas.vue'
import VideoCanvas from './VideoCanvas.vue'
import TagPanel from './TagPanel.vue'
import ProvenancePanel from './ProvenancePanel.vue'

const emit = defineEmits(['close'])

const modal = useModalStore()
const rootEl = ref(null)

const isVideo = computed(() =>
  modal.current?.mime && modal.current.mime.startsWith('video/')
)

const integrityBadge = computed(() => {
  const s = modal.current?.integrity_status
  if (s === 'corrupt') return { color: 'error', label: 'Corrupt' }
  if (s === 'failed_verification')
    return { color: 'warning', label: 'Unverified' }
  return null
})

let prevBodyOverflow = null

// Document-level keyboard handler. Bound on document (not on the modal
// root) because <video> elements capture focus when the user interacts
// with their controls — when video has focus, Escape and arrow keys
// don't bubble reliably to ancestors. Listening on document side-steps
// that. Filtered via isTextEntry so tag/comment inputs still get their
// own keystrokes.
function onKeyDown(ev) {
  if (ev.key === 'Escape') {
    if (isTextEntry(ev.target)) return
    ev.preventDefault()
    emit('close')
  } else if (ev.key === 'ArrowLeft') {
    if (isTextEntry(ev.target)) return
    ev.preventDefault()
    modal.goPrev()
  } else if (ev.key === 'ArrowRight') {
    if (isTextEntry(ev.target)) return
    ev.preventDefault()
    modal.goNext()
  }
}

onMounted(async () => {
  prevBodyOverflow = document.body.style.overflow
  document.body.style.overflow = 'hidden'
  document.addEventListener('keydown', onKeyDown, true)
  await nextFrame()
  rootEl.value?.focus()
})
onUnmounted(() => {
  document.body.style.overflow = prevBodyOverflow
  document.removeEventListener('keydown', onKeyDown, true)
})

watch(() => modal.currentImageId, async () => {
  await nextFrame()
  rootEl.value?.focus()
})

function nextFrame() {
  return new Promise(resolve => requestAnimationFrame(resolve))
}

function isTextEntry(el) {
  if (!el) return false
  const tag = el.tagName
  return tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable
}
</script>

<style scoped>
.fc-viewer {
  position: fixed; inset: 0; z-index: 2000;
  /* Obsidian haze (#14171A = 20,23,26) — same palette as TopNav,
     mid-opacity + blur so the page behind shows through faintly. */
  background: rgba(20, 23, 26, 0.65);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  outline: none;
  display: flex; flex-direction: column;
}
.fc-viewer__close, .fc-viewer__nav {
  position: absolute; top: 50%;
  background: rgba(20, 23, 26, 0.7);
  color: rgb(var(--v-theme-parchment, 232 228 216));
  border: 1px solid rgb(var(--v-theme-surface-light));
  border-radius: 50%;
  width: 48px; height: 48px;
  display: grid; place-items: center;
  cursor: pointer; z-index: 2;
  transition: background 100ms ease;
}
.fc-viewer__close:hover, .fc-viewer__nav:hover:not(:disabled) {
  background: rgb(var(--v-theme-surface));
}
.fc-viewer__nav:disabled { opacity: 0.3; cursor: not-allowed; }
.fc-viewer__close { top: 16px; right: 16px; transform: none; }
.fc-viewer__integrity {
  position: absolute; top: 72px; right: 16px; z-index: 3;
}
.fc-viewer__nav--prev { left: 16px; transform: translateY(-50%); }
.fc-viewer__nav--next { right: 16px; transform: translateY(-50%); }
.fc-viewer__body {
  flex: 1; display: flex; min-height: 0;
}
.fc-viewer__media {
  /* Let the canvas fill us; the canvas does its own centering. Both
     min-* are needed so this child can shrink inside its flex parents
     (without them, max-height/max-width on the <img> have nothing to
     bound against and the image overflows the viewport). */
  flex: 1; display: flex;
  min-width: 0; min-height: 0;
}
.fc-viewer__side {
  width: 320px; flex-shrink: 0;
  background: rgb(var(--v-theme-surface));
  border-left: 1px solid rgb(var(--v-theme-surface-light));
  overflow-y: auto;
}

@media (max-width: 900px) {
  .fc-viewer__body { flex-direction: column; }
  .fc-viewer__side {
    width: 100%;
    max-height: 40vh;
    border-left: none;
    border-top: 1px solid rgb(var(--v-theme-surface-light));
  }
}
</style>
