<template>
  <v-container fluid class="pt-2 pb-6">

    <!-- Search lives in BrowseView's sticky bar (shared across tabs); this view
         reacts to route.query.q. The kind filter stays here. -->
    <div class="fc-tags__controls">
      <v-chip-group
        v-model="kind" selected-class="text-accent" :mandatory="false"
      >
        <v-chip v-for="k in KINDS" :key="k" :value="k" filter size="small">
          {{ k }}
        </v-chip>
      </v-chip-group>
    </div>

    <v-alert v-if="store.error" type="error" variant="tonal" closable class="my-4">
      Failed to load: {{ store.error }}
    </v-alert>
    <v-alert v-else-if="store.isEmpty" type="info" variant="tonal" class="my-4">
      No tags match.
    </v-alert>

    <div class="fc-tags__grid">
      <TagCard
        v-for="c in store.cards" :key="c.id" :card="c"
        @open="openTag" @rename="onRename" @manage="onManage" @read="onRead"
        @merge-with="onMergeWith" @delete="onDeleteTag"
        @set-fandom="onSetFandom" @aliases="onAliases"
      />
    </div>

    <div v-if="store.loading" class="fc-tags__sentinel">
      <v-progress-circular indeterminate color="accent" size="28" />
    </div>
    <div v-else-if="store.hasMore" ref="sentinelEl" class="fc-tags__sentinel" />

    <MergeConfirmDialog
      :model-value="pendingMerge !== null"
      :collision="pendingMerge"
      @confirm="confirmMerge"
      @cancel="pendingMerge = null"
    />

    <v-dialog v-model="mergePickerOpen" max-width="520">
      <v-card>
        <v-card-title>Merge “{{ mergeSource?.name }}” into…</v-card-title>
        <v-card-text>
          <p class="fc-muted text-body-2 mb-3">
            Pick the target tag. Source tag's image associations move to the
            target. Must be same kind ({{ mergeSource?.kind }}).
          </p>
          <v-autocomplete
            v-model="mergeTargetId"
            v-model:menu="mergeMenuOpen"
            :items="mergeHits" item-title="name" item-value="id"
            :loading="mergeLoading"
            density="compact" variant="outlined" hide-details
            placeholder="Search target tag…" no-filter
            @update:search="onMergeSearch"
            @keydown.enter.capture="onMergeEnter"
          />

          <v-alert
            v-if="adminStore.lastError"
            type="warning" variant="tonal" density="compact" class="mt-3"
          >{{ adminStore.lastError }}</v-alert>

          <!-- #8: dry-run preview of what the merge will move, before commit. -->
          <div v-if="mergeTargetId" class="fc-merge-preview mt-3">
            <div v-if="mergePreviewing" class="d-flex align-center ga-2">
              <v-progress-circular indeterminate size="18" width="2" color="accent" />
              <span class="text-body-2 fc-muted">Checking what would move…</span>
            </div>
            <div v-else-if="mergePreview">
              <p class="text-body-2 mb-1">
                Moves <strong>{{ mergePreview.images_moving }}</strong>
                image(s) onto <strong>{{ mergePreview.target_name }}</strong>.
                <span v-if="mergePreview.images_already_on_target" class="fc-muted">
                  ({{ mergePreview.images_already_on_target }} already on it.)
                </span>
              </p>
              <p v-if="mergePreview.series_pages" class="text-body-2 mb-1">
                {{ mergePreview.series_pages }} series page(s) repointed.
              </p>
              <p class="text-body-2 mb-1">
                <template v-if="mergePreview.will_alias">
                  “{{ mergePreview.source_name }}” kept as an alias — the tagger may still predict it.
                </template>
                <template v-else>
                  “{{ mergePreview.source_name }}” will be permanently deleted.
                </template>
              </p>
              <v-alert
                v-if="!mergePreview.compatible"
                type="error" variant="tonal" density="compact" class="mb-2"
              >Different kind or fandom — this merge would be rejected.</v-alert>
              <div v-if="mergePreview.sample_thumbnails?.length" class="fc-merge-thumbs">
                <img
                  v-for="(t, i) in mergePreview.sample_thumbnails" :key="i"
                  :src="t" alt="" loading="lazy"
                />
              </div>
            </div>
          </div>
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="mergePickerOpen = false">Cancel</v-btn>
          <v-btn
            color="warning" variant="flat" rounded="pill"
            :disabled="!mergePreview || !mergePreview.compatible"
            :loading="merging"
            @click="onMergeConfirm"
          >Merge{{ mergePreview ? ` ${mergePreview.images_moving} image(s)` : '' }}</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>

    <DestructiveConfirmModal
      v-model="deleteTagModalOpen"
      action="delete"
      kind="tag"
      :run-id="deleteTagTarget?.id || 0"
      tier="B"
      :projected-counts="{ associations: deleteTagUsage }"
      :description="deleteTagTarget
        ? `Delete tag “${deleteTagTarget.name}” and remove all image associations.`
        : ''"
      @confirm="onDeleteTagConfirm"
    />

    <v-dialog
      v-model="fandomDialogOpen" max-width="460"
      @after-enter="fandomSetRef?.focusSearch?.()"
    >
      <FandomSetDialog
        ref="fandomSetRef"
        v-if="fandomTarget" :tag="fandomTarget"
        @updated="onFandomUpdated" @cancel="fandomDialogOpen = false"
      />
    </v-dialog>

    <v-dialog v-model="aliasesDialogOpen" max-width="480">
      <TagAliasesDialog
        v-if="aliasesTarget" :tag="aliasesTarget"
        @close="aliasesDialogOpen = false"
      />
    </v-dialog>
  </v-container>
</template>

<script setup>
import { computed, ref, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTagDirectoryStore } from '../stores/tagDirectory.js'
import { useAdminStore } from '../stores/admin.js'
import { useApi } from '../composables/useApi.js'
import { useInfiniteScroll } from '../composables/useInfiniteScroll.js'
import { useAcceptOnEnter } from '../composables/useAcceptOnEnter.js'
import { usePreviewCommit } from '../composables/usePreviewCommit.js'
import TagCard from '../components/discovery/TagCard.vue'
import MergeConfirmDialog from '../components/discovery/MergeConfirmDialog.vue'
import DestructiveConfirmModal from '../components/modal/DestructiveConfirmModal.vue'
import FandomSetDialog from '../components/modal/FandomSetDialog.vue'
import TagAliasesDialog from '../components/discovery/TagAliasesDialog.vue'

// Must stay a subset of the backend TagKind enum (character, fandom,
// general, series, archive, post). 'fandom' is this model's
// copyright/franchise concept (characters link via fandom_id).
// 'artist' retired in FC-2d-vii-c — artists are the Artist row, not
// a tag. 'meta' + 'rating' retired by operator 2026-05-26 (alembic 0023).
const KINDS = ['character', 'fandom', 'general', 'series']
const store = useTagDirectoryStore()
const route = useRoute()
const router = useRouter()

const kind = ref(null)
const sentinelEl = ref(null)
const pendingMerge = ref(null)

async function onRename({ id, name }) {
  const res = await store.rename(id, name)
  if (res.collision) pendingMerge.value = res.collision
}

async function confirmMerge() {
  const c = pendingMerge.value
  pendingMerge.value = null
  if (c) await store.merge(c.sourceId, c.target.id)
}

// Search term comes from BrowseView's shared sticky bar via route.query.q.
const searchTerm = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))
watch(searchTerm, (v) => store.setQuery(v || ''))
watch(kind, (v) => store.setKind(v || null))

useInfiniteScroll(sentinelEl, () => {
  if (store.hasMore && !store.loading) store.loadMore()
})
// Apply any deep-linked q (and load). setQuery resets + fetches.
onMounted(() => store.setQuery(searchTerm.value || ''))

function openTag(tagId) {
  router.push({ name: 'gallery', query: { tag_id: tagId } })
}

// Character fandom editing (dots-menu → FandomSetDialog).
const fandomDialogOpen = ref(false)
const fandomTarget = ref(null)
const fandomSetRef = ref(null)
function onSetFandom(card) {
  fandomTarget.value = card
  fandomDialogOpen.value = true
}
function onFandomUpdated() {
  fandomDialogOpen.value = false
  store.reset()  // reload so the card reflects the new fandom (or its removal)
}

// Tag-side alias view (TagCard ⋮ → Aliases…): see/remove the model keys that
// fold into this tag. Creation lives in the image modal's suggestion flow.
const aliasesDialogOpen = ref(false)
const aliasesTarget = ref(null)
function onAliases(card) {
  aliasesTarget.value = card
  aliasesDialogOpen.value = true
}

function onManage(id) {
  router.push({ name: 'series-manage', params: { tagId: id } })
}

function onRead(id) {
  router.push({ name: 'series-read', params: { tagId: id } })
}

// --- FC-3k tag merge + delete ----------------------------------------

const adminStore = useAdminStore()
const api = useApi()

// Tag merge via dots-menu (separate from inline-rename collision flow above)
const mergePickerOpen = ref(false)
// Enter on the closed dropdown accepts the target instead of re-opening it.
const { menuOpen: mergeMenuOpen, onEnter: onMergeEnter } = useAcceptOnEnter(
  () => onMergeConfirm()
)
const mergeSource = ref(null)
const mergeTargetId = ref(null)
const mergeHits = ref([])
const mergeLoading = ref(false)
let mergeDebounce = null

// #8: dry-run preview (counts + sample of what moves) before the irreversible
// merge — same usePreviewCommit primitive the maintenance tiles use.
const {
  previewData: mergePreview, previewing: mergePreviewing, committing: merging,
  runPreview: runMergePreview, runCommit: runMergeCommit,
} = usePreviewCommit({
  preview: async () =>
    (await adminStore.mergeTags(
      mergeTargetId.value, mergeSource.value.id, { dryRun: true },
    )).preview,
  commit: () => adminStore.mergeTags(
    mergeTargetId.value, mergeSource.value.id, { dryRun: false },
  ),
})

// Re-preview whenever the chosen target changes (and clear it when unset).
watch(mergeTargetId, (id) => {
  if (id && mergeSource.value) runMergePreview()
  else mergePreview.value = null
})

function onMergeWith(card) {
  mergeSource.value = card
  mergeTargetId.value = null
  mergeHits.value = []
  mergePreview.value = null
  mergePickerOpen.value = true
}

function onMergeSearch(q) {
  if (mergeDebounce) clearTimeout(mergeDebounce)
  if (!q) { mergeHits.value = []; return }
  mergeDebounce = setTimeout(async () => {
    mergeLoading.value = true
    try {
      const hits = await api.get('/api/tags/autocomplete', {
        params: { q, kind: mergeSource.value?.kind, limit: 20 },
      })
      mergeHits.value = (hits || []).filter(
        (h) => h.id !== mergeSource.value?.id,
      )
    } finally {
      mergeLoading.value = false
    }
  }, 250)
}

async function onMergeConfirm() {
  if (!mergeSource.value || !mergeTargetId.value) return
  if (!mergePreview.value || !mergePreview.value.compatible) return
  try {
    await runMergeCommit()
    mergePickerOpen.value = false
    store.reset()
  } catch {
    // adminStore.lastError already set; UI surfaces it elsewhere.
  }
}

// Tag delete via dots-menu (Tier B)
const deleteTagModalOpen = ref(false)
const deleteTagTarget = ref(null)
const deleteTagUsage = ref(0)

async function onDeleteTag(card) {
  deleteTagTarget.value = card
  try {
    deleteTagUsage.value = await adminStore.tagUsageCount(card.id)
  } catch {
    deleteTagUsage.value = 0
  }
  deleteTagModalOpen.value = true
}

async function onDeleteTagConfirm() {
  if (!deleteTagTarget.value) return
  try {
    await adminStore.deleteTag(deleteTagTarget.value.id)
    store.reset()
  } catch {
    // adminStore.lastError already set.
  }
}
</script>

<style scoped>
.fc-tags__controls {
  display: flex; align-items: center; gap: 16px;
  flex-wrap: wrap; margin-bottom: 16px;
}
.fc-tags__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
  gap: 12px;
}
.fc-tags__sentinel {
  display: flex; justify-content: center; padding: 32px 0; min-height: 60px;
}
.fc-merge-preview {
  padding: 10px 12px;
  border: 1px solid rgba(var(--v-theme-on-surface), 0.12);
  border-radius: 10px;
  background: rgba(var(--v-theme-on-surface), 0.03);
}
.fc-merge-thumbs {
  display: flex; gap: 4px; flex-wrap: wrap; margin-top: 6px;
}
.fc-merge-thumbs img {
  width: 56px; height: 56px; object-fit: cover; border-radius: 6px;
  background: rgb(var(--v-theme-surface-light));
}
</style>
