<script setup lang="ts">
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
import { breakpointsTailwind, useDebounce } from '@vueuse/core'
import EntrelineaDetail from '~/components/entrelineas/EntrelineaDetail.vue'
import { useFavoritesStore } from '~/stores/favorites'
import { useSettingsStore } from '~/stores/settings'
import type { SearchHit } from '~/types'

const COLLECTION = 'entrelineas'
const QUERY_BY = 'text'
const INCLUDE_FIELDS = '*'
const EXTRA_FILTER_BY = ''
const FAVORITES_COLLECTION = 'entrelineas'

const { $i18n } = useNuxtApp()
const t = $i18n.t
const { locale } = useI18n()

const { unlocked } = useDevMode()

const filterBy = computed(() => {
  const localeFilter = `locale:=${locale.value}`
  return EXTRA_FILTER_BY ? `${localeFilter} && ${EXTRA_FILTER_BY}` : localeFilter
})

const REQUEST_TIMEOUT_MS = 15000

const settings = useSettingsStore()

const { query: q0, page: p0, scroll: s0, selectedId: sid0 } = useSearchUrlState()

const query = ref(q0)
const debouncedQuery = useDebounce(query, 150)

const loading = ref(false)
const loadingMore = ref(false)
const errorMsg = ref<string | null>(null)
const exactSearch = ref(false)

interface Study {
  id?: number
  title?: string
  date?: string
  place?: string
  link?: string
}

interface EntrelineaDoc {
  id?: string
  image?: string
  link?: string
  locale?: string
  description?: string
  page?: number | string
  text?: string
  html?: string
  draft?: boolean
  studies?: Study[]
  [key: string]: unknown
}

interface TypesenseHighlight {
  field?: string
  snippet?: string
  value?: string
  matched_tokens?: string[]
}

interface TypesenseHit {
  document: EntrelineaDoc
  highlights?: TypesenseHighlight[]
  highlight?: Record<string, { snippet?: string, value?: string }>
  text_match?: number
}

interface TypesenseSearchResponse {
  found: number
  out_of?: number
  page?: number
  hits?: TypesenseHit[]
}

const hits = ref<TypesenseHit[]>([])
const total = ref(0)
const currentPage = ref(1)
const activePage = ref(p0)

const totalPages = computed(() => Math.max(1, Math.ceil(total.value / settings.pageSize)))

const hasMore = computed(() =>
  settings.paginationType === 'infinite_scroll' ? hits.value.length < total.value : false
)

const { documentsApi } = useTypesenseApi()

let searchSeq = 0
let timeoutId: ReturnType<typeof setTimeout> | null = null

async function runSearch(q: string, page = 1, append = false) {
  const seq = ++searchSeq
  if (append) loadingMore.value = true
  else loading.value = true
  errorMsg.value = null

  if (timeoutId) clearTimeout(timeoutId)
  timeoutId = setTimeout(() => {
    if (seq === searchSeq) {
      loading.value = false
      loadingMore.value = false
      errorMsg.value = 'La búsqueda tardó demasiado. Inténtalo de nuevo.'
    }
  }, REQUEST_TIMEOUT_MS)

  const isInfinite = settings.paginationType === 'infinite_scroll'
  const typePage = isInfinite
    ? (append ? currentPage.value + 1 : 1)
    : page

  try {
    const multi = await documentsApi.multiSearch({
      multiSearchParameters: {},
      multiSearchSearchesParameter: {
        searches: [{
          collection: COLLECTION,
          q: exactSearch.value && q ? `"${q}"` : q || '*',
          queryBy: QUERY_BY,
          includeFields: INCLUDE_FIELDS,
          filterBy: filterBy.value,
          perPage: settings.pageSize,
          page: typePage,
          highlightFullFields: QUERY_BY,
          highlightFields: QUERY_BY,
          highlightStartTag: '<mark class="search-match">',
          highlightEndTag: '</mark>'
        }]
      }
    })

    if (seq !== searchSeq) return

    const res = (multi?.results?.[0] ?? {}) as TypesenseSearchResponse
    const newHits = res?.hits ?? []

    hits.value = append ? hits.value.concat(newHits) : newHits
    total.value = res?.found ?? hits.value.length
    currentPage.value = typePage
    if (!append) activePage.value = page
  } catch (err: unknown) {
    if (seq !== searchSeq) return
    console.error('Typesense error', err)
    errorMsg.value = (err as Error)?.message || 'Error al buscar.'
    if (!append) {
      hits.value = []
      total.value = 0
    }
  } finally {
    if (seq === searchSeq) {
      if (timeoutId) clearTimeout(timeoutId)
      loading.value = false
      loadingMore.value = false
    }
  }
}

function loadMore() {
  if (settings.paginationType !== 'infinite_scroll') return
  if (loadingMore.value || loading.value || !hasMore.value) return
  runSearch(query.value, currentPage.value, true)
}

function goToPage(p: number) {
  activePage.value = p
  hits.value = []
  runSearch(query.value, p, false)
}

function retry() {
  runSearch(query.value, activePage.value, false)
}

onBeforeUnmount(() => {
  if (timeoutId) clearTimeout(timeoutId)
})

watch(debouncedQuery, (q) => {
  hits.value = []
  total.value = 0
  currentPage.value = 1
  activePage.value = 1
  runSearch(q, 1, false)
})

watch(exactSearch, () => {
  if (query.value.trim()) runSearch(query.value, 1, false)
})

const selected = ref<EntrelineaDoc | null>(null)

const isPanelOpen = computed({
  get() { return !!selected.value },
  set(v: boolean) { if (!v) selected.value = null }
})

watch(hits, () => {
  if (!selected.value) return
  const stillThere = hits.value.find(h => h.document.id === selected.value?.id)
  if (!stillThere) selected.value = null
})

const selectedId = computed(() => selected.value?.id?.toString() ?? null)

const listEl = ref<HTMLElement | null>(null)

useSearchUrlSync({ query, page: activePage, selectedId, scrollEl: listEl })

onMounted(async () => {
  await runSearch(q0, p0, false)

  restoreScrollPosition(listEl.value, s0)

  if (sid0) {
    const found = hits.value.find(h => h.document.id === sid0)
    if (found) selected.value = found.document
  }
})

const breakpoints = useBreakpoints(breakpointsTailwind)
const isMobile = breakpoints.smaller('lg')

useDetailHistory(isPanelOpen, isMobile)

const favorites = useFavoritesStore()
const toast = useToast()

function toSearchHit(doc: EntrelineaDoc): SearchHit {
  const id = doc.id || ''
  return {
    _id: id,
    id,
    title: doc.text?.slice(0, 100) || 'Entrelínea',
    body: doc.text,
    ...doc
  }
}

function isFav(doc: EntrelineaDoc): boolean {
  if (!doc?.id) return false
  return favorites.isFavorite(FAVORITES_COLLECTION, doc.id)
}

function toggleFavorite(doc: EntrelineaDoc, ev?: Event) {
  ev?.stopPropagation()
  if (!doc?.id) return
  const wasFav = isFav(doc)
  favorites.toggle(FAVORITES_COLLECTION, toSearchHit(doc))
  toast.add({
    title: wasFav ? 'Eliminado de tu lista' : 'Guardado en tu lista',
    description: doc.text?.slice(0, 100),
    icon: wasFav ? 'i-lucide-bookmark-x' : 'i-lucide-bookmark-check',
    color: wasFav ? 'neutral' : 'primary',
    duration: 1800
  })
}

function highlightedFor(hit: TypesenseHit, field: string): string | null {
  const fromArr = hit.highlights?.find(h => h.field === field)
  if (fromArr?.snippet) return fromArr.snippet
  if (fromArr?.value) return fromArr.value
  const fromObj = hit.highlight?.[field]
  if (fromObj?.snippet) return fromObj.snippet
  if (fromObj?.value) return fromObj.value
  return null
}
</script>

<template>
  <UDashboardPanel
    id="entrelineas-list"
    :default-size="32"
    :min-size="24"
    :max-size="45"
    resizable
  >
    <UDashboardNavbar :title="t('nav.between_the_lines')">
      <template #leading>
        <UDashboardSidebarCollapse />
      </template>
    </UDashboardNavbar>

    <!-- Banner: se muestra cuando NO hay clave de desarrollador -->
    <template v-if="!unlocked">
      <div class="flex-1 flex items-center justify-center p-8 bg-gradient-to-br from-amber-50 via-white to-amber-100/60">
        <div class="flex flex-col items-center gap-6 text-center max-w-sm">
          <div class="size-28 rounded-full bg-gradient-to-br from-amber-300 to-amber-500 flex items-center justify-center shadow-lg shadow-amber-200/50 ring-4 ring-white">
            <UIcon name="i-lucide-flask-conical" class="size-14 text-white" />
          </div>

          <div class="space-y-2">
            <h2 class="text-2xl font-bold text-highlighted">
              {{ t('entrelineas.development_banner') }}
            </h2>
            <p class="text-sm text-toned leading-relaxed">
              {{ t('entrelineas.development_subtitle_line1') }}<br>
              {{ t('entrelineas.development_subtitle_line2') }}
            </p>
          </div>

          <div class="flex items-center gap-2 pt-2">
            <span class="size-2 rounded-full bg-amber-300 animate-pulse" />
            <span class="size-2 rounded-full bg-amber-400 animate-pulse" style="animation-delay: 0.2s" />
            <span class="size-2 rounded-full bg-amber-500 animate-pulse" style="animation-delay: 0.4s" />
          </div>
        </div>
      </div>
    </template>

    <!-- Funcional: se muestra cuando la clave de desarrollador es correcta -->
    <template v-else>
      <div class="px-4 sm:px-6 py-3 border-b border-default">
        <div class="flex items-center gap-2">
          <UInput
            v-model="query"
            icon="i-lucide-search"
            :placeholder="t('Search.placeholder')"
            :loading="loading"
            size="md"
            class="flex-1 min-w-0"
          />
          <div
            class="flex rounded-full p-0.5 shrink-0 transition-colors duration-200"
            :class="exactSearch ? 'bg-primary' : 'bg-gray-200 dark:bg-gray-700'"
          >
            <button
              class="px-2.5 py-0.5 rounded-full text-xs transition-all duration-200 whitespace-nowrap"
              :class="!exactSearch ? 'bg-white dark:bg-gray-900 text-gray-900 dark:text-white font-semibold shadow-sm' : 'text-white/40 font-normal'"
              @click.stop="exactSearch = false"
            >Palabra</button>
            <button
              class="px-2.5 py-0.5 rounded-full text-xs transition-all duration-200 whitespace-nowrap"
              :class="exactSearch ? 'bg-white text-primary font-semibold shadow-sm' : 'text-gray-400 dark:text-gray-400 font-normal'"
              @click.stop="exactSearch = true"
            >Frase</button>
          </div>
        </div>
        <p class="mt-1.5 flex items-center gap-1 text-[11px] text-dimmed">
          <UIcon name="i-lucide-database" class="size-3" />
          <span>
            <code class="text-toned">{{ COLLECTION }}</code>
            ·
            <code class="text-toned">{{ QUERY_BY }}</code>
            ·
            <code class="text-toned">{{ filterBy }}</code>
          </span>
        </p>
      </div>

      <UAlert
        v-if="errorMsg"
        :title="errorMsg"
        color="error"
        variant="subtle"
        icon="i-lucide-triangle-alert"
        class="mx-4 my-2"
        :actions="[{ label: 'Reintentar', color: 'neutral', variant: 'outline', onClick: retry }]"
      />

      <div ref="listEl" class="overflow-y-auto divide-y divide-default flex-1">
        <div
          v-if="loading && !hits.length"
          class="flex items-center justify-center gap-2 py-16 text-sm text-muted"
        >
          <UIcon name="i-lucide-loader-circle" class="size-4 animate-spin" />
          Buscando...
        </div>

        <div
          v-else-if="!hits.length"
          class="flex flex-col items-center justify-center gap-2 py-16 text-dimmed text-sm"
        >
          <UIcon name="i-lucide-inbox" class="size-10" />
          <p>{{ query ? `Sin coincidencias para "${query}"` : 'Sin resultados' }}</p>
        </div>

        <div
          v-for="(hit, index) in hits"
          :key="hit.document.id ?? index"
          class="p-4 sm:px-6 text-sm cursor-pointer transition-colors border-b-2"
          :class="[
            selected && selected.id === hit.document.id
              ? 'bg-primary/10 text-highlighted'
              : 'border-gray-200 text-toned hover:bg-primary/5'
          ]"
          @click="selected = hit.document"
        >
          <div class="flex items-start justify-between gap-2 mb-1">
            <div class="min-w-0 flex-1 flex gap-2">
              <UBadge
                v-if="hit.document?.filter"
                :label="hit.document?.filter"
                size="sm"
                variant="subtle"
                color="neutral"
                class="mb-1 uppercase"
              />
              <UBadge
                v-if="hit.document?.type"
                :label="hit.document?.type"
                size="sm"
                variant="subtle"
                color="neutral"
                class="mb-1 uppercase"
              />
              <UBadge
                v-if="hit.document?.draft"
                label="Borrador"
                size="sm"
                variant="subtle"
                color="warning"
                class="mb-1"
              />
            </div>
            <UTooltip :text="isFav(hit.document) ? 'Quitar de mi lista' : 'Guardar en mi lista'">
              <UButton
                :icon="isFav(hit.document) ? 'i-lucide-bookmark-check' : 'i-lucide-bookmark-plus'"
                :color="isFav(hit.document) ? 'primary' : 'neutral'"
                variant="ghost"
                size="xs"
                :aria-label="isFav(hit.document) ? 'Quitar de mi lista' : 'Guardar en mi lista'"
                @click="(ev: MouseEvent) => toggleFavorite(hit.document, ev)"
              />
            </UTooltip>
          </div>

          <div
            v-if="highlightedFor(hit, 'text') || hit.document.text"
            class="snippet-html text-sm text-toned"
            v-html="highlightedFor(hit, 'text') || hit.document.text"
          />

          <USeparator class="my-2"/>
        </div>

        <div
          v-if="settings.paginationType === 'infinite_scroll' && hasMore && !loading"
          class="p-4 flex justify-center"
        >
          <UButton
            variant="outline"
            color="neutral"
            size="sm"
            :loading="loadingMore"
            @click="loadMore"
          >
            Cargar más
          </UButton>
        </div>

        <div
          v-else-if="settings.paginationType === 'infinite_scroll' && hits.length && !hasMore && !loading"
          class="py-3 text-center text-xs text-dimmed"
        >
          No hay más resultados
        </div>
      </div>

      <div
        v-if="settings.paginationType === 'numbered' && totalPages > 1 && !loading"
        class="px-4 py-3 border-t border-default flex justify-center shrink-0"
      >
        <UPagination
          :page="activePage"
          :total="total"
          :items-per-page="settings.pageSize"
          size="sm"
          @update:page="goToPage"
        />
      </div>
    </template>
  </UDashboardPanel>

  <template v-if="unlocked">
    <EntrelineaDetail
      v-if="selected && !isMobile"
      :document="selected"
      :collection="FAVORITES_COLLECTION"
      :highlighted-text="selected.id ? (hits.find(h => h.document.id === selected!.id)?.highlights?.find(hl => hl.field === 'text')?.value ?? null) : null"
      @close="selected = null"
    />
    <div v-else-if="!isMobile" class="hidden lg:flex flex-1 items-center justify-center">
      <div class="flex flex-col items-center gap-2 text-dimmed">
        <UIcon name="i-lucide-mouse-pointer-click" class="size-16" />
        <p class="text-sm">
          Selecciona una entrelínea para ver el detalle
        </p>
      </div>
    </div>

    <ClientOnly>
      <USlideover v-if="isMobile" v-model:open="isPanelOpen">
        <template #content>
          <EntrelineaDetail
            v-if="selected"
            :document="selected"
            :collection="FAVORITES_COLLECTION"
            :highlighted-text="selected.id ? (hits.find(h => h.document.id === selected!.id)?.highlights?.find(hl => hl.field === 'text')?.value ?? null) : null"
            @close="selected = null"
          />
        </template>
      </USlideover>
    </ClientOnly>
  </template>
</template>

<style scoped>
.snippet-html :deep(p) {
  display: inline;
  margin: 0;
}
.snippet-html :deep(br) {
  display: none;
}
</style>
