<template>
  <aside class="fc-tag-panel" aria-label="Tags for this image">
    <h3 class="fc-tag-panel__title">Tags</h3>
    <div class="fc-tag-panel__chips">
      <TagChip
        v-for="tag in host.current?.tags || []"
        :key="tag.id" :tag="tag" :image-id="host.currentImageId"
        @remove="onRemove" @rename="openRename" @set-fandom="openSetFandom"
        @navigate="onNavigate" @confirm="onConfirm"
      />
      <span v-if="!host.current?.tags?.length" class="text-caption">No tags yet.</span>
    </div>

    <v-divider class="my-3" />

    <TagAutocomplete
      ref="tagInputRef"
      :applied-tags="host.current?.tags || []"
      @pick-existing="onPickExisting" @pick-new="onPickNew"
    />

    <v-alert v-if="errorMsg" type="error" variant="tonal" class="mt-2" closable>
      {{ errorMsg }}
    </v-alert>

    <SuggestionsPanel
      v-if="host.currentImageId != null"
      :image-id="host.currentImageId"
      :host="host"
      @accepted="focusTagInput"
      @dismissed="focusTagInput"
    />

    <!-- @after-leave: when either dialog finishes closing (apply OR cancel),
         hand focus back to the tag input. Fires after Vuetify's own
         focus-return to the activator, so ours wins; keeps the keyboard flow on
         the input (operator-asked 2026-06-26). Mobile guard preserved via
         focusTagInput → TagAutocomplete. -->
    <v-dialog
      v-model="renameDialog" max-width="420"
      @after-leave="focusTagInput"
    >
      <TagRenameDialog
        v-if="renameTarget"
        :tag="renameTarget"
        @renamed="onRenamed" @cancel="renameDialog = false"
      />
    </v-dialog>

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

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useApi } from '../../composables/useApi.js'
import { useModalStore } from '../../stores/modal.js'
import { useSuggestionsStore } from '../../stores/suggestions.js'
import TagChip from './TagChip.vue'
import TagAutocomplete from './TagAutocomplete.vue'
import SuggestionsPanel from './SuggestionsPanel.vue'
import TagRenameDialog from './TagRenameDialog.vue'
import FandomSetDialog from './FandomSetDialog.vue'

// `host` is the tagging context — a store-like object exposing
// current / currentImageId + removeTag/addExistingTag/createAndAdd/reloadTags
// (+ optional close). Defaults to the modal store so the image modal is
// unchanged; the Explore workspace passes its own host bound to the anchor,
// reusing this panel verbatim for modal-parity tagging.
const props = defineProps({ host: { type: Object, default: null } })
const modalStore = useModalStore()
const host = props.host || modalStore
const suggestions = useSuggestionsStore()
const router = useRouter()
const api = useApi()
const errorMsg = ref(null)
const tagInputRef = ref(null)

// #5: clicking a tag chip's body leaves the current surface and opens the
// gallery filtered for that single tag (a fresh filter — the obvious "show me
// more like this tag" move). Rename/set-fandom (kebab) and remove (✕) stay put.
async function onNavigate(tag) {
  await host.close?.()
  router.push({ name: 'gallery', query: { tag_id: String(tag.id) } })
}

// Return focus to the tag input after a suggestion is accepted (from the
// Suggestions panel or the autocomplete dropdown) so the operator can keep
// typing the next tag without re-clicking the field (operator-asked 2026-06-08).
// Exposed so a host surface (the Explore workspace) can re-focus the field when
// it swaps the focused image, without re-clicking. The field's own mobile guard
// (TagAutocomplete) is preserved.
function focusTagInput() { tagInputRef.value?.focus?.() }
defineExpose({ focusTagInput })

// Every tag mutation hands focus back to the input so the operator can keep
// typing the next tag without re-clicking — matches the accept-suggestion flow
// (operator-asked 2026-06-26; the Explore workspace leans on this hard).
// Confirm/keep an auto-applied tag (milestone 139): records the affirmation so
// the tag becomes a training positive AND is shielded from the retraction sweep,
// then reloads so the chip drops its "auto" badge + Keep button.
async function onConfirm(tag) {
  errorMsg.value = null
  try {
    await api.post(`/api/images/${host.currentImageId}/tags/${tag.id}/confirm`)
    await host.reloadTags()
    // Return the cursor to the tag input — its resting position in these views,
    // matching every other chip action (remove/accept) so the keyboard flow
    // never leaves the field (operator-asked 2026-07-07).
    focusTagInput()
  }
  catch (e) { errorMsg.value = e.message }
}

async function onRemove(tagId) {
  errorMsg.value = null
  try {
    await host.removeTag(tagId)
    // removeTag also records a per-image dismissal server-side, so reloading
    // puts a model-suggested tag BACK in the suggestions area (flagged
    // rejected — visible + one-click reversible) instead of it vanishing
    // until the next modal open (operator-asked 2026-07-03).
    if (host.currentImageId != null) {
      suggestions.load(host.currentImageId)
    }
    focusTagInput()
  }
  catch (e) { errorMsg.value = e.message }
}
async function onPickExisting(hit) {
  errorMsg.value = null
  try {
    // Manually picking a tag the model also suggested = accepting the
    // suggestion: the acceptance is recorded and the row drops from the
    // panel, instead of silently bypassing the feedback loop
    // (operator-asked 2026-07-03).
    const pending = suggestions.findPending(hit.kind, hit.name, hit.id)
    if (pending) {
      await suggestions.accept(pending, { tagId: hit.id })
      await host.reloadTags()
    } else {
      await host.addExistingTag(hit.id)
    }
    focusTagInput()
  }
  catch (e) { errorMsg.value = e.message }
}
async function onPickNew(payload) {
  errorMsg.value = null
  try {
    const imageId = host.currentImageId
    const created = await host.createAndAdd(payload)
    // The typed name can match a raw (or canonical) suggestion; creating it
    // by hand still counts as accepting — record it and drop the row. Runs
    // AFTER createAndAdd so a character's fandom pick is preserved (accept's
    // own create path knows no fandom); the accept apply is idempotent.
    // Guard on the image not having changed mid-create (fast prev/next).
    if (created && host.currentImageId === imageId) {
      const pending = suggestions.findPending(payload.kind, payload.name, created.id)
      if (pending) {
        await suggestions.accept(pending, { tagId: created.id })
      }
    }
    focusTagInput()
  }
  catch (e) { errorMsg.value = e.message }
}
const renameDialog = ref(false)
const renameTarget = ref(null)

function openRename(tag) {
  renameTarget.value = tag
  renameDialog.value = true
}
async function onRenamed() {
  renameDialog.value = false
  // Reflect the new name in the current tag list without a full reload.
  await host.reloadTags()
}

const fandomDialog = ref(false)
const fandomTarget = ref(null)
const fandomSetRef = ref(null)
function openSetFandom(tag) {
  fandomTarget.value = tag
  fandomDialog.value = true
}
async function onFandomUpdated() {
  fandomDialog.value = false
  // A fandom change can merge the tag away; reload to reflect the new state.
  await host.reloadTags()
}
</script>

<style scoped>
.fc-tag-panel {
  padding: 16px;
}
.fc-tag-panel__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 18px;
  font-weight: 500;
  color: rgb(var(--v-theme-on-surface));
  margin-bottom: 12px;
}
.fc-tag-panel__chips { display: flex; flex-wrap: wrap; gap: 6px; }
</style>
