<template>
  <div class="fc-filterbar-wrap">
   <div class="fc-filterbar">
    <v-autocomplete
      v-model="selected"
      :items="searchItems"
      :loading="searchLoading"
      item-title="name" item-value="value"
      no-filter clearable hide-details density="compact" variant="outlined"
      placeholder="Filter by tag or artist…"
      prepend-inner-icon="mdi-filter-variant"
      class="fc-filterbar__search"
      @update:search="onSearch"
      @update:model-value="onPick"
    >
      <template #item="{ props: itemProps, item }">
        <v-list-item v-bind="itemProps" :title="item.raw.name">
          <template #prepend>
            <v-icon size="small">{{ iconFor(item.raw) }}</v-icon>
          </template>
          <template #subtitle>
            {{ item.raw.kind === 'artist' ? 'artist'
              : (item.raw.fandom_name ? `character · ${item.raw.fandom_name}` : item.raw.kind) }}
          </template>
        </v-list-item>
      </template>
    </v-autocomplete>

    <div class="fc-filterbar__chips">
      <v-chip
        v-for="id in store.filter.tag_ids" :key="`t${id}`"
        size="small" closable :color="chipColor(id)" variant="tonal"
        @click:close="removeTag(id)"
      >{{ store.tagLabels[id] || `#${id}` }}</v-chip>
      <v-chip
        v-if="store.filter.artist_id"
        size="small" closable color="accent" variant="tonal"
        prepend-icon="mdi-account"
        @click:close="clearArtist"
      >{{ store.artistLabel || `Artist #${store.filter.artist_id}` }}</v-chip>
      <v-chip
        v-if="store.filter.similar_to"
        size="small" closable color="accent" variant="tonal"
        prepend-icon="mdi-image-multiple"
        @click:close="clearSimilar"
      >Similar to #{{ store.filter.similar_to }}</v-chip>
    </div>

    <v-spacer />

    <v-btn-toggle
      :model-value="store.filter.media_type ?? 'all'"
      density="compact" mandatory variant="outlined" divided
      @update:model-value="(v) => setMedia(v === 'all' ? null : v)"
    >
      <v-btn value="all" size="small">All</v-btn>
      <v-btn value="image" size="small">Images</v-btn>
      <v-btn value="video" size="small">Videos</v-btn>
    </v-btn-toggle>

    <!-- Sort is meaningless in similar-mode (results are distance-ranked). -->
    <v-select
      v-if="!store.filter.similar_to"
      :model-value="store.filter.sort"
      :items="SORTS"
      density="compact" hide-details variant="outlined"
      class="fc-filterbar__sort"
      @update:model-value="setSort"
    />

    <v-btn
      :color="refineOpen ? 'accent' : undefined"
      :variant="refineOpen || hasRefineFilters ? 'tonal' : 'text'"
      size="small"
      :append-icon="refineOpen ? 'mdi-chevron-up' : 'mdi-chevron-down'"
      @click="toggleRefine"
    >Refine{{ refineCount ? ` (${refineCount})` : '' }}</v-btn>

    <v-btn
      v-if="hasActiveFilters" variant="text" size="small"
      prepend-icon="mdi-close" @click="clearAll"
    >Clear</v-btn>
   </div>

   <GalleryFacetPanel v-if="refineOpen" />
  </div>
</template>

<script setup>
import { computed, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useApi } from '../../composables/useApi.js'
import { cloneFilter, filterToQuery, useGalleryStore } from '../../stores/gallery.js'
import { useTagStore } from '../../stores/tags.js'
import GalleryFacetPanel from './GalleryFacetPanel.vue'

const store = useGalleryStore()
const tagStore = useTagStore()
const api = useApi()
const router = useRouter()

const SORTS = [
  { title: 'Newest first', value: 'newest' },
  { title: 'Oldest first', value: 'oldest' },
]

const selected = ref(null)
const searchItems = ref([])
const searchLoading = ref(false)
let debounce = null

// The faceted-refine sub-filters (platform / curation flags / date range).
const refineCount = computed(() => {
  const f = store.filter
  return (f.platform ? 1 : 0) + (f.untagged ? 1 : 0) + (f.no_artist ? 1 : 0)
    + (f.date_from ? 1 : 0) + (f.date_to ? 1 : 0)
})
const hasRefineFilters = computed(() => refineCount.value > 0)

const hasActiveFilters = computed(() =>
  store.filter.tag_ids.length > 0 ||
  store.filter.artist_id != null ||
  store.filter.media_type != null ||
  store.filter.sort !== 'newest' ||
  store.filter.similar_to != null ||
  hasRefineFilters.value
)

// Auto-open the panel when refine filters are present in the URL (deep-link /
// back button). The parent applies the query in its onMounted — after this
// child has set up — so watch for the transition rather than reading the
// initial (still-default) filter state.
const refineOpen = ref(false)
watch(hasRefineFilters, (v) => { if (v) refineOpen.value = true })
function toggleRefine() {
  // The panel fetches facets itself on mount (and refetches on filter change);
  // opening it is enough.
  refineOpen.value = !refineOpen.value
}

function iconFor(raw) {
  if (raw.kind === 'artist') return 'mdi-account'
  return { character: 'mdi-account-circle', fandom: 'mdi-book-open-page-variant',
           series: 'mdi-bookshelf' }[raw.kind] || 'mdi-tag'
}
function chipColor(id) {
  // Tag chips use the same per-kind palette as the rest of the app; we only
  // know the kind from the autocomplete pick, so fall back to a neutral tone.
  return tagStore.colorFor(pickedKind.value[id] || 'general')
}
const pickedKind = ref({})

function onSearch(q) {
  if (debounce) clearTimeout(debounce)
  if (!q || !q.trim()) { searchItems.value = []; return }
  debounce = setTimeout(async () => {
    searchLoading.value = true
    try {
      const [tags, artists] = await Promise.all([
        api.get('/api/tags/autocomplete', { params: { q, limit: 10 } }),
        api.get('/api/artists/autocomplete', { params: { q, limit: 10 } }),
      ])
      searchItems.value = [
        ...(artists || []).map((a) => ({
          kind: 'artist', id: a.id, name: a.name, value: `artist:${a.id}`,
        })),
        ...(tags || []).map((t) => ({
          kind: t.kind, id: t.id, name: t.name, value: `tag:${t.id}`,
          fandom_name: t.fandom_name,
        })),
      ]
    } catch {
      searchItems.value = []
    } finally {
      searchLoading.value = false
    }
  }, 250)
}

function onPick(value) {
  const item = searchItems.value.find((i) => i.value === value)
  selected.value = null
  searchItems.value = []
  if (!item) return
  if (item.kind === 'artist') {
    store.noteArtistLabel(item.name)
    pushFilter((n) => { n.artist_id = item.id })
  } else {
    store.noteTagLabel(item.id, item.name)
    pickedKind.value = { ...pickedKind.value, [item.id]: item.kind }
    pushFilter((n) => { if (!n.tag_ids.includes(item.id)) n.tag_ids.push(item.id) })
  }
}

function removeTag(id) {
  pushFilter((n) => { n.tag_ids = n.tag_ids.filter((t) => t !== id) })
}
function clearArtist() {
  store.noteArtistLabel(null)
  pushFilter((n) => { n.artist_id = null })
}
function clearSimilar() { pushFilter((n) => { n.similar_to = null }) }
function setMedia(m) { pushFilter((n) => { n.media_type = m }) }
function setSort(s) { pushFilter((n) => { n.sort = s }) }
function clearAll() { router.push({ name: 'gallery', query: {} }) }

// Single write path: clone the current filter, mutate, serialize to the URL.
// The route watcher in GalleryView applies it to the store and reloads.
// cloneFilter/filterToQuery are shared with GalleryFacetPanel so the refine
// sub-filters survive a bar push and vice versa.
function pushFilter(mutate) {
  const n = cloneFilter(store.filter)
  mutate(n)
  router.push({ name: 'gallery', query: filterToQuery(n) })
}
</script>

<style scoped>
/* The whole chrome (bar row + expandable refine panel) is one sticky,
   frosted block pinned directly under the 64px TopNav and continuous with it. */
.fc-filterbar-wrap {
  position: sticky;
  top: 64px;
  z-index: 5;
  /* Attach to the TopNav: cancel the v-container's top padding (pt-2 = 8px)
     so the bar sits flush at 64px even at scroll 0 — without this it detaches
     and a gap shows through when scrolled to the top. */
  margin-top: -8px;
  margin-bottom: 12px;
  /* EXACT same gradiated obsidian (#14171A = 20,23,26) frost as the TopNav so
     the two read as one continuous piece of chrome — images scroll visibly
     under both. The nav's gradient fades to transparent at ITS bottom; this
     bar re-darkens at its top, so a faint seam (the page/image showing through
     the nav's transparent edge) separates them when scrolled to the very top,
     while under-scroll they frost as one. */
  background: linear-gradient(
    to bottom,
    rgba(20, 23, 26, 0.92) 0%,
    rgba(20, 23, 26, 0.65) 60%,
    rgba(20, 23, 26, 0) 100%
  );
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}
.fc-filterbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 12px;
}
/* The inputs/toggles float on the haze: still translucent, but a touch more
   opaque than the bar/nav so the controls stay legible. */
.fc-filterbar-wrap :deep(.v-field),
.fc-filterbar-wrap :deep(.v-btn-group) {
  background-color: rgba(20, 23, 26, 0.72);
}
.fc-filterbar__search { max-width: 320px; min-width: 200px; }
.fc-filterbar__chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.fc-filterbar__sort { max-width: 150px; }

/* Phones: the search's 200px min-width jams the wrapping bar. Give search its
   own full-width row and let sort grow; everything else wraps under it. */
@media (max-width: 600px) {
  .fc-filterbar { gap: 8px; }
  .fc-filterbar__search { min-width: 100%; max-width: none; }
  .fc-filterbar__sort { max-width: none; flex: 1 1 auto; }
}
</style>
