<template>
  <aside class="fc-bulk-panel" :class="{ active: sel.isSelectMode }">
    <div class="fc-bulk-panel__head">
      <h3>Bulk Edit</h3>
      <v-btn icon="mdi-close" variant="text" size="small" @click="sel.exitSelectMode()" />
    </div>
    <div class="fc-bulk-panel__stat">{{ sel.count }} image(s) selected</div>

    <div class="fc-bulk-panel__section">
      <h4>Add tag</h4>
      <v-autocomplete
        v-model="addModel" :items="addHits" item-title="name" item-value="id"
        :loading="addLoading" density="compact" variant="outlined" hide-details
        placeholder="Search tags…" no-filter
        @update:search="onAddSearch" @update:model-value="onAddPick"
      />
    </div>

    <div class="fc-bulk-panel__section">
      <h4>Remove common tag</h4>
      <div v-if="sel.commonTags.length === 0" class="fc-bulk-panel__hint">
        No tags common to all selected images.
      </div>
      <v-chip
        v-for="t in sel.commonTags" :key="t.id" size="small" label
        class="ma-1" closable @click:close="sel.bulkRemove(t.id)"
      >{{ t.name }}</v-chip>
    </div>

    <div class="fc-bulk-panel__section">
      <h4>Consensus suggestions</h4>
      <div v-if="sel.count < 2" class="fc-bulk-panel__hint">
        Select 2+ images for consensus suggestions.
      </div>
      <template v-else>
        <div
          v-for="(items, cat) in sel.consensus" :key="cat"
          class="fc-bulk-panel__cat"
        >
          <div class="fc-bulk-panel__cat-name">{{ cat }}</div>
          <div
            v-for="s in items" :key="s.canonical_tag_id"
            class="fc-bulk-panel__sug"
          >
            <span>{{ s.name }}</span>
            <span class="fc-bulk-panel__cov">
              {{ Math.round(s.coverage * 100) }}% · {{ s.confidence.toFixed(2) }}
            </span>
            <v-btn
              size="x-small" variant="tonal" color="accent"
              @click="sel.bulkAdd(s.canonical_tag_id, 'ml_accepted')"
            >Accept</v-btn>
          </div>
        </div>
      </template>
    </div>

    <div class="fc-bulk-panel__section">
      <h4>Destructive</h4>
      <v-btn
        color="error" variant="flat" rounded="pill" block
        prepend-icon="mdi-delete-forever"
        :disabled="!sel.count"
        :loading="deleting"
        @click="onDeleteClick"
      >Delete {{ sel.count }} selected</v-btn>
    </div>

    <div class="fc-bulk-panel__foot">
      <v-btn variant="text" block @click="sel.clear()">Clear selection</v-btn>
    </div>

    <DestructiveConfirmModal
      v-model="deleteModalOpen"
      action="delete"
      kind="images-selection"
      :expected-token-override="bulkProjected?.confirm_token || ''"
      tier="C"
      :projected-counts="bulkProjectedCounts"
      :description="bulkDescription"
      @confirm="onDeleteConfirm"
    />
  </aside>
</template>

<script setup>
import { computed, ref, watch } from 'vue'
import { useGallerySelectionStore } from '../../stores/gallerySelection.js'
import { useAdminStore } from '../../stores/admin.js'
import { useApi } from '../../composables/useApi.js'
import DestructiveConfirmModal from '../modal/DestructiveConfirmModal.vue'

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

const addModel = ref(null)
const addHits = ref([])
const addLoading = ref(false)
let addDebounce = null

function onAddSearch(q) {
  if (addDebounce) clearTimeout(addDebounce)
  if (!q) { addHits.value = []; return }
  addDebounce = setTimeout(async () => {
    addLoading.value = true
    try {
      addHits.value = await api.get('/api/tags/autocomplete', {
        params: { q, limit: 20 }
      })
    } finally {
      addLoading.value = false
    }
  }, 250)
}
async function onAddPick(id) {
  if (id == null) return
  await sel.bulkAdd(id)
  addModel.value = null
  addHits.value = []
}

// Refresh common tags + consensus whenever the selection changes.
watch(() => sel.order.length, () => {
  if (sel.isSelectMode) sel.refresh()
})

// --- FC-3k bulk delete -----------------------------------------------

const deleting = ref(false)
const deleteModalOpen = ref(false)
const bulkProjected = ref(null)

const bulkProjectedCounts = computed(() => bulkProjected.value
  ? {
    images: bulkProjected.value.images_found,
    thumbnails: bulkProjected.value.thumbs_to_unlink,
    bytes: bulkProjected.value.bytes_on_disk,
  }
  : null,
)
const bulkDescription = computed(
  () => bulkProjected.value
    ? `${bulkProjected.value.images_found} images, `
        + `${Math.round(bulkProjected.value.bytes_on_disk / 1_048_576)} MiB on disk`
    : '',
)

// The dry-run response hands the canonical Tier-C confirm token back
// as `confirm_token` (e.g. `delete-images-1a2b3c4d`), passed straight
// to the modal via `expected-token-override`. We used to compute the
// hash client-side via `crypto.subtle.digest`, but (1) that's
// Secure-Context-gated and undefined on plain-HTTP origins
// (homelab posture), so the click silently threw TypeError and the
// modal never opened, and (2) the modal's `kind="images-selection"`
// produced `delete-images-selection-<sha8>` while the backend
// expected `delete-images-<sha8>` — so it never would have worked
// even on HTTPS. Operator-flagged 2026-05-27.

async function onDeleteClick() {
  if (!sel.order.length) return
  deleting.value = true
  try {
    bulkProjected.value = await adminStore.projectBulkImageDelete(sel.order)
    deleteModalOpen.value = true
  } finally {
    deleting.value = false
  }
}

async function onDeleteConfirm(token) {
  deleting.value = true
  try {
    const result = await adminStore.dispatchBulkImageDelete(sel.order, token)
    const taskId = result.task_id
    if (taskId) {
      adminStore.pollTaskUntilDone(taskId).catch(() => {})
    }
    sel.clear()
  } finally {
    deleting.value = false
  }
}
</script>

<style scoped>
.fc-bulk-panel {
  position: fixed; top: 0; right: 0;
  /* min(320px, 90vw): on phones the panel never swallows the whole screen —
     leaves a sliver of the gallery visible behind it. */
  width: min(320px, 90vw); height: 100vh; z-index: 1100;
  background: rgb(var(--v-theme-surface));
  border-left: 1px solid rgb(var(--v-theme-surface-light));
  box-shadow: -2px 0 16px rgba(0, 0, 0, 0.4);
  transform: translateX(100%);
  transition: transform 0.3s ease;
  display: flex; flex-direction: column;
  overflow-y: auto;
}
.fc-bulk-panel.active { transform: translateX(0); }
.fc-bulk-panel__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid rgb(var(--v-theme-surface-light));
}
.fc-bulk-panel__head h3 {
  font-family: 'Fraunces', Georgia, serif; font-size: 18px; margin: 0;
}
.fc-bulk-panel__stat {
  padding: 10px 16px; font-size: 13px; opacity: 0.8;
  background: rgba(232, 228, 216, 0.05);
}
.fc-bulk-panel__section {
  padding: 16px;
  border-bottom: 1px solid rgba(232, 228, 216, 0.06);
}
.fc-bulk-panel__section h4 {
  margin: 0 0 10px; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.04em; opacity: 0.7;
}
.fc-bulk-panel__hint { font-size: 12px; opacity: 0.6; }
.fc-bulk-panel__cat-name {
  font-size: 11px; text-transform: uppercase; opacity: 0.5; margin: 8px 0 4px;
}
.fc-bulk-panel__sug {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0; font-size: 13px;
}
.fc-bulk-panel__sug span:first-child { flex: 1; min-width: 0; }
.fc-bulk-panel__cov {
  font-variant-numeric: tabular-nums; opacity: 0.6; font-size: 11px;
}
.fc-bulk-panel__foot { margin-top: auto; padding: 12px 16px; }
</style>
