<template>
  <section class="fc-suggestions" aria-label="Tag suggestions">
    <h3 class="fc-suggestions__title">Suggestions</h3>

    <div v-if="store.loading" class="fc-suggestions__skeleton">
      <div v-for="i in 4" :key="i" class="fc-suggestions__skel-row" />
    </div>
    <v-alert v-else-if="store.error" type="error" variant="tonal" density="compact">
      {{ store.error }}
    </v-alert>
    <div v-else-if="isEmpty" class="text-caption">
      No suggestions above threshold.
    </div>

    <!-- Flows in the rail's main scroll area; Related is pinned to the bottom
         of the rail (ImageViewer side layout), so a long suggestion set no
         longer needs an internal scroll cap to keep Related reachable. -->
    <div v-else class="fc-suggestions__list">
      <!-- System hygiene tags (wip/banner/editor) surface in their own group,
           first, so false positives are easy to spot and reject (reject-to-train
           for these heads). Small set — collapsible, open by default. -->
      <SuggestionsCategoryGroup
        v-if="store.aboveByCategory.system && store.aboveByCategory.system.length"
        label="System" :items="store.aboveByCategory.system"
        collapsible :default-open="true"
        @accept="onAccept"
        @dismiss="onDismiss" @undismiss="onUndismiss"
        @reject-all="onRejectAll('system')"
      />
      <SuggestionsCategoryGroup
        v-for="cat in peopleCats" :key="cat"
        v-show="store.aboveByCategory[cat] && store.aboveByCategory[cat].length"
        :label="labelFor(cat)" :items="store.aboveByCategory[cat] || []"
        @accept="onAccept"
        @dismiss="onDismiss" @undismiss="onUndismiss"
        @reject-all="onRejectAll(cat)"
      />
      <SuggestionsCategoryGroup
        v-if="store.aboveByCategory.general && store.aboveByCategory.general.length"
        label="General" :items="store.aboveByCategory.general"
        collapsible :default-open="true"
        @accept="onAccept"
        @dismiss="onDismiss" @undismiss="onUndismiss"
        @reject-all="onRejectAll('general')"
      />
    </div>
  </section>
</template>

<script setup>
import { toast } from '../../utils/toast.js'
import { computed, watch } from 'vue'
import { useSuggestionsStore, CATEGORY_LABELS } from '../../stores/suggestions.js'
import { useModalStore } from '../../stores/modal.js'
import SuggestionsCategoryGroup from './SuggestionsCategoryGroup.vue'

const props = defineProps({
  imageId: { type: Number, required: true },
  // The tagging host whose chip rail to refresh after an accept. Defaults to
  // the modal store (image modal); the Explore workspace passes its anchor host
  // so the same panel refreshes the right surface. See TagPanel.
  host: { type: Object, default: null },
})
// 'accepted'/'dismissed' let the parent return focus to the tag input after a
// suggestion is accepted OR rejected, so the operator keeps the keyboard flow on
// the input without re-clicking (operator-asked 2026-06-08, 2026-06-30).
const emit = defineEmits(['accepted', 'dismissed'])

// Reject (✗) / un-reject (↶): apply the store change, then signal the parent to
// re-focus the tag input — same return-to-input behaviour as accept.
function onDismiss (s) { store.dismiss(s); emit('dismissed') }
function onUndismiss (s) { store.undismiss(s); emit('dismissed') }
// Section-level "Reject rest": dismiss every still-unhandled suggestion in the
// category, then return focus to the input like a single reject does.
async function onRejectAll (category) {
  try {
    await store.dismissRemaining(category)
    emit('dismissed')
  } catch (e) {
    toast({ text: `Reject failed: ${e.message}`, type: 'error' })
  }
}
const store = useSuggestionsStore()
const modalStore = useModalStore()
const host = props.host || modalStore

// 'artist' (FC-2d-vii-c) and 'copyright' (2026-06-01) retired as
// suggestion categories. Only 'character' remains as a people-style
// category alongside the general bucket.
const peopleCats = ['character']
function labelFor(c) { return CATEGORY_LABELS[c] || c }

const isEmpty = computed(() =>
  Object.values(store.aboveByCategory).every(list => !list || list.length === 0)
)

watch(() => props.imageId, (id) => {
  if (id == null) return
  // One fetch (min=0) backs both surfaces: the panel reads aboveByCategory, the
  // typed tag-input dropdown reads the full set. No second request.
  store.load(id)
}, { immediate: true })

// After a successful accept/alias-accept, refresh the modal's current
// tag list so TagPanel's chip rail reflects the newly-attached tag.
// Operator-flagged 2026-06-01: the suggestion store dropped the
// suggestion (correct) but didn't propagate the new tag back into the
// modal store, so the chip rail looked unchanged even though the
// backend had recorded the application. Mirrors the addExistingTag /
// createAndAdd flows which already call reloadTags() after applying.
async function onAccept(s) {
  try {
    await store.accept(s)
    await host.reloadTags()
    emit('accepted')
  } catch (e) {
    toast({ text: `Accept failed: ${e.message}`, type: 'error' })
  }
}
</script>

<style scoped>
.fc-suggestions {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid rgb(var(--v-theme-surface-light));
}
.fc-suggestions__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 16px; font-weight: 500;
  color: rgb(var(--v-theme-on-surface));
  margin-bottom: 8px;
}
.fc-suggestions__list {
  /* No internal scroll cap: the rail now scrolls suggestions in its single
     main scroll area while Related is pinned to the bottom (ImageViewer side
     layout), so suggestions flow naturally without a nested scrollbar. */
  padding-right: 4px;
}
.fc-suggestions__skeleton { display: flex; flex-direction: column; gap: 8px; }
.fc-suggestions__skel-row {
  height: 18px; border-radius: 4px;
  background: linear-gradient(
    90deg,
    rgb(var(--v-theme-surface)) 0%,
    rgb(var(--v-theme-surface-light)) 50%,
    rgb(var(--v-theme-surface)) 100%
  );
  background-size: 200% 100%;
  animation: fc-shimmer 1.4s infinite;
}
@keyframes fc-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
</style>
