<template>
  <!-- Collapses entirely unless the source has an embedding AND we're loading
       or have results — so a slow/empty/failed fetch leaves no trace and never
       affects the modal. -->
  <div v-if="show" class="fc-related">
    <div class="fc-related__head">
      <span class="fc-related__title">Related</span>
      <v-btn
        size="x-small" variant="text" color="accent"
        :disabled="loading || !results.length"
        @click="seeAll"
      >See all similar</v-btn>
    </div>
    <div class="fc-related__row">
      <template v-if="loading">
        <div v-for="n in 6" :key="n" class="fc-related__skel" />
      </template>
      <button
        v-for="img in results" :key="img.id"
        class="fc-related__item" type="button"
        @click="openImage(img.id)"
      >
        <img :src="img.thumbnail_url" :alt="`image ${img.id}`" loading="lazy" />
      </button>
    </div>
  </div>
</template>

<script setup>
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useApi } from '../../composables/useApi.js'
import { useModalStore } from '../../stores/modal.js'

// Deferred so the modal's main image gets network/decode priority — the strip
// is a nice-to-have and must never block or slow the modal load.
const DEFER_MS = 200
const STRIP_LIMIT = 12

const api = useApi()
const router = useRouter()
const modal = useModalStore()

const results = ref([])
const loading = ref(false)

const hasEmbedding = computed(() => modal.current?.has_embedding === true)
const show = computed(() => hasEmbedding.value && (loading.value || results.value.length > 0))

let seq = 0
let timer = null

async function fetchSimilar(id) {
  const mine = ++seq
  loading.value = true
  results.value = []
  try {
    const body = await api.get('/api/gallery/similar', {
      params: { similar_to: id, limit: STRIP_LIMIT },
    })
    if (mine !== seq) return
    results.value = body.images || []
  } catch {
    if (mine === seq) results.value = []  // quietly collapse on error
  } finally {
    if (mine === seq) loading.value = false
  }
}

// Re-fetch whenever the modal lands on a new embedded image. modal.current is
// null while the next image loads, so this also clears the strip during
// prev/next nav and repopulates once the new payload arrives.
watch(
  () => (hasEmbedding.value ? modal.current?.id : null),
  (id) => {
    if (timer) { clearTimeout(timer); timer = null }
    seq++              // cancel any in-flight fetch
    results.value = []
    loading.value = false
    if (!id) return
    timer = setTimeout(() => fetchSimilar(id), DEFER_MS)
  },
  { immediate: true },
)
onBeforeUnmount(() => { if (timer) clearTimeout(timer) })

function openImage(id) {
  modal.open(id)
}

function seeAll() {
  const id = modal.current?.id
  if (!id) return
  modal.close()
  router.push({ name: 'gallery', query: { similar_to: String(id) } })
}
</script>

<style scoped>
.fc-related {
  padding: 12px;
  border-top: 1px solid rgb(var(--v-theme-surface-light));
}
.fc-related__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.fc-related__title {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: rgb(var(--v-theme-on-surface-variant));
}
.fc-related__row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.fc-related__item {
  display: block; padding: 0; border: 0; background: none;
  cursor: pointer; border-radius: 4px; overflow: hidden;
  aspect-ratio: 1; width: 100%;
}
.fc-related__item img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  background: rgb(var(--v-theme-surface-light));
  transition: transform 0.2s ease, filter 0.2s ease;
}
.fc-related__item:hover img { transform: scale(1.05); filter: brightness(1.1); }
.fc-related__skel {
  aspect-ratio: 1; border-radius: 4px;
  background: rgb(var(--v-theme-surface-light));
  opacity: 0.5;
}
</style>
