<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router'
import { useAuthStore } from '../../stores/auth'
import { NInput, NButton, NEmpty, NSelect, NCheckboxGroup, NCheckbox, NPagination, NIcon, NRadioGroup, NRadio, NSlider, NModal, NPopover } from 'naive-ui'
import { SearchOutline, FilterOutline, EyeOffOutline, SettingsOutline } from '@vicons/ionicons5'
import { api } from '../../api/client'
import { useToast } from '../../composables/useToast'
import { useLiteraturePreview } from '../../composables/useLiteraturePreview'
import { usePagination } from '../../composables/usePagination'
import { useDisplaySettings } from '../../composables/useDisplaySettings'
import { trackAction } from '../../composables/useAnalytics'
import PageSkeleton from '../../components/common/PageSkeleton.vue'
import LiteratureCard from '../../components/literature/LiteratureCard.vue'
import LiteraturePreviewDrawer from '../../components/literature/LiteraturePreviewDrawer.vue'
import type { LiteratureItem, TagOption, SearchRequestBody, SavedFilter } from '../../types'

const router = useRouter(); const route = useRoute()
const toast = useToast()
const auth = useAuthStore()
const displaySettings = useDisplaySettings()
const KEYSET_SORTS = new Set(['date', 'cited', 'title', 'journal', 'first_author'])

// ── 搜索参数 ──
const query = ref('')
const field = ref('all')
const sort = ref('date')
const results = ref<LiteratureItem[]>([])
const loading = ref(false)
const searched = ref(false)
const searchError = ref('')
const savedPmids = ref<Set<number>>(new Set())

// ── 筛选参数 ──
const yearFromStr = ref(''); const yearToStr = ref('')
const urlDateFrom = ref(''); const urlDateTo = ref('')  // 来自 URL 的完整日期（保留精度）
const datePreset = ref<string | null>(null)
const selectedTiers = ref<string[]>([])
const selectedTags = ref<string[]>([])
const pubTypes = ref<string[]>([])
const languages = ref<string[]>([])  // [] = all, or language codes like ['en']
const nlmSubsets = ref<string[]>([])
const retracted = ref('')   // '' = all, 'yes', 'no', 'only'
const negativeResult = ref('')  // '' = all, 'yes', 'no', 'only'
const selectedSpecies = ref<string[]>([])
const selectedSex = ref<string[]>([])
const selectedAge = ref<string[]>([])
const booleanOp = ref('and')   // "and" | "or"
const exactPhrase = ref(false)

// ── PubMed 筛选器状态 ──
const hasAbstract = ref(false)
const isFreeFullText = ref(false)
const hasFullText = ref(false)
const hasAssociatedData = ref(false)
const medlineOnly = ref(false)
const excludePreprints = ref(false)

// ── 筛选选项数据 ──
const filterOptions = ref<any>(null)
const allTagsRaw = ref<TagOption[]>([])
const expandedGroups = ref<Record<string, boolean>>({})
const showFilters = ref(localStorage.getItem('search:showFilters') !== 'false')
const yearCounts = ref<{ year: number; count: number }[]>([])
const showCustomYear = ref(false)  // 自定义年份范围展开
const _skipRouteSync = ref(0)  // R31: guard against re-entry from syncSearchToUrl
const pageSize = ref(Number(localStorage.getItem('search:pageSize')) || 20)
// 从 URL 恢复的页码（syncSearchToUrl 写入）
const restoredPage = ref(1)

// ── Keyset 游标分页（所有排序模式通用，跳过 COUNT 和 OFFSET） ──
const keysetPage = ref(1)
const keysetCursors = ref<Array<{cursor_val: string, cursor_id: string} | null>>([null])
const keysetHasMore = ref(false)

const minYear = computed(() => yearCounts.value.length ? Math.min(...yearCounts.value.map(y => y.year)) : 2000)
const maxYear = computed(() => yearCounts.value.length ? Math.max(...yearCounts.value.map(y => y.year)) : 2030)

/** 柱状图最大值 */
const maxYearCount = computed(() => {
  if (!yearCounts.value.length) return 1
  return Math.max(...yearCounts.value.map(y => y.count), 1)
})

/** slider 值 = 与 yearFromStr/yearToStr 同步 */
const yearSliderValue = computed((): [number, number] => {
  const from = yearFromStr.value ? Number(yearFromStr.value) : minYear.value
  const to = yearToStr.value ? Number(yearToStr.value) : maxYear.value
  return [from, to]
})

let _sliderTimer: ReturnType<typeof setTimeout> | null = null
function onYearSliderChange(val: any) {
  yearFromStr.value = String(val[0])
  yearToStr.value = String(val[1])
  datePreset.value = null
  showCustomYear.value = false  // R31: 滑动时收起自定义年份
  urlDateFrom.value = ''; urlDateTo.value = ''  // P6: clear stale URL dates
  if (_sliderTimer) clearTimeout(_sliderTimer)
  _sliderTimer = setTimeout(() => goToPage(1), 250)
}

onUnmounted(() => {
  if (_sliderTimer) clearTimeout(_sliderTimer)
  searchAbortController.value.abort()
})

// ── 常用文献类型（前 8 个常驻显示） ──
const MAX_VISIBLE_PUB_TYPES = 5
const showPubTypeModal = ref(false)

// ── 常用语言（前 5 个常驻显示） ──
const MAX_VISIBLE_LANGUAGES = 5
const showLangModal = ref(false)

// ── AGE 前 5 个常驻显示 ──
const MAX_VISIBLE_AGE = 5
const showAgeModal = ref(false)

// ── My Custom Filters ──
const savedFilters = ref<SavedFilter[]>([])
const showFilterEditModal = ref(false)
const editFilterId = ref<string | null>(null)
const editName = ref('')
const editQuery = ref('')

async function loadSavedFilters() {
  if (!auth.isAuthenticated) return
  try {
    const { data } = await api.get('/features/search/saved-filters')
    savedFilters.value = data.filters || []
  } catch { /* ignore */ }
}

function applySavedFilter(f: SavedFilter) {
  if (!auth.isAuthenticated) {
    router.push('/auth/login?redirect=' + encodeURIComponent(route.fullPath))
    return
  }
  query.value = f.query_string
  goToPage(1)
}

function openEditFilter(f: SavedFilter) {
  editFilterId.value = f.id
  editName.value = f.name
  editQuery.value = f.query_string
  showFilterEditModal.value = true
}

function openNewFilter() {
  editFilterId.value = null
  editName.value = ''
  editQuery.value = ''
}

watch(showFilterEditModal, (val) => {
  if (!val) {
    openNewFilter()
  }
})

async function saveFilter() {
  if (!editName.value.trim() || !editQuery.value.trim()) return
  try {
    if (editFilterId.value) {
      await api.put(`/features/search/saved-filters/${editFilterId.value}`, {
        name: editName.value.trim(),
        query_string: editQuery.value.trim(),
      })
    } else {
      await api.post('/features/search/saved-filters', {
        name: editName.value.trim(),
        query_string: editQuery.value.trim(),
      })
    }
    showFilterEditModal.value = false
    openNewFilter()
    await loadSavedFilters()
  } catch (e) { toast.apiError(e, '保存失败') }
}

async function deleteSavedFilter(id: string) {
  if (!window.confirm('确定删除此筛选器？')) return
  try {
    await api.delete(`/features/search/saved-filters/${id}`)
    await loadSavedFilters()
  } catch (e) { toast.apiError(e, '删除失败') }
}

async function moveFilterDown(index: number) {
  if (index >= savedFilters.value.length - 1) return
  const arr = savedFilters.value.slice()
  const a = arr[index]; const b = arr[index + 1]
  if (!a || !b) return
  arr[index] = b; arr[index + 1] = a
  savedFilters.value = arr
  try {
    await api.put('/features/search/saved-filters/reorder',
      arr.map((f, i) => ({ id: f.id, sort_order: i }))
    )
  } catch (e) {
    await loadSavedFilters()  // 回滚到服务端状态
    toast.apiError(e, '排序失败')
  }
}

async function moveFilterUp(index: number) {
  if (index <= 0) return
  const arr = savedFilters.value.slice()
  const a = arr[index]; const b = arr[index - 1]
  if (!a || !b) return
  arr[index] = b; arr[index - 1] = a
  savedFilters.value = arr
  try {
    await api.put('/features/search/saved-filters/reorder',
      arr.map((f, i) => ({ id: f.id, sort_order: i }))
    )
  } catch (e) {
    await loadSavedFilters()  // 回滚到服务端状态
    toast.apiError(e, '排序失败')
  }
}

/** 收藏 / 取消收藏 */
async function handleSave(item: LiteratureItem) {
  if (!auth.isAuthenticated) {
    router.push('/auth/login?redirect=' + encodeURIComponent(route.fullPath))
    return
  }
  const wasSaved = savedPmids.value.has(item.pmid)
  try {
    if (wasSaved) {
      await api.delete(`/literature/${item.pmid}/save`)
      const s = new Set(savedPmids.value)
      s.delete(item.pmid)
      savedPmids.value = s
      toast.success('已取消收藏')
    } else {
      await api.post(`/literature/${item.pmid}/save`)
      savedPmids.value = new Set([...savedPmids.value, item.pmid])
      toast.success('已收藏')
    }
  } catch (e) {
    toast.apiError(e, wasSaved ? '取消收藏失败' : '收藏失败')
  }
}

/** 按一级分类分组的二级可选标签 */
const groupedTags = computed(() => {
  const l2 = allTagsRaw.value.filter((t: TagOption) => t.level === 2 && t.is_selectable)
  const l1List = allTagsRaw.value.filter((t: TagOption) => t.level === 1)
  const groups: { parentId: string; parentName: string; tags: TagOption[] }[] = []
  for (const l1 of l1List) {
    const children = l2.filter(t => t.parent_id === String(l1.id))
    if (children.length) {
      groups.push({ parentId: String(l1.id), parentName: l1.name_zh, tags: children })
    }
  }
  return groups
})

function toggleGroup(id: string) {
  expandedGroups.value[id] = !expandedGroups.value[id]
}

const { showPreview, previewPmid, openPreview: setPreviewPmid, closePreview } = useLiteraturePreview()
const searchAbortController = ref(new AbortController())
const searchGeneration = ref(0)
const { page, total, goToPage } = usePagination({
  fetchFn: async (p: number) => {
    // 取消上一个进行中的请求，避免竞态
    searchAbortController.value.abort()
    searchAbortController.value = new AbortController()
    const signal = searchAbortController.value.signal
    const gen = ++searchGeneration.value
    loading.value = true
    searched.value = true
    searchError.value = ''
    total.value = 0
    try {
      if (p === 1 && query.value.trim()) {
        trackAction('search', 'search', query.value.trim(), { sort: sort.value })
      }
      const body: SearchRequestBody = {
        query: query.value, page: p, page_size: pageSize.value, sort: sort.value,
        boolean: booleanOp.value,
      }
      if (exactPhrase.value) body.exact_phrase = true
      if (field.value !== 'all') body.field = field.value
      // P3-6: precision_mode 不再发送（后端已忽略）
      // ── Keyset 游标分页（所有排序模式通用，跳过 COUNT + OFFSET） ──
      if (p === 1) {
        delete (body as any).page
        keysetCursors.value = [null]; keysetPage.value = 1
      } else {
        const entry = keysetCursors.value[p]
        if (entry) {
          delete (body as any).page
          body.cursor_val = entry.cursor_val
          body.cursor_id = entry.cursor_id
        }
        // cursor 不存在时保留 body.page，回退到 offset 分页
      }
      // 年份 / 日期（datePreset 与 year_* 互斥）
      const yf = Number(yearFromStr.value)
      const yt = Number(yearToStr.value)
      if (datePreset.value === 'custom') {
        if (yearFromStr.value && !isNaN(yf) && yf >= 1900 && yf <= 2100) body.year_from = yf
        if (yearToStr.value && !isNaN(yt) && yt >= 1900 && yt <= 2100) body.year_to = yt
      } else if (!datePreset.value && (urlDateFrom.value || urlDateTo.value)) {
        // 来自 URL 的完整日期（保留月日精度）
        if (urlDateFrom.value) body.date_from = urlDateFrom.value
        if (urlDateTo.value) body.date_to = urlDateTo.value
      } else {
        if (!datePreset.value && yearFromStr.value && !isNaN(yf) && yf >= 1900 && yf <= 2100) body.year_from = yf
        if (!datePreset.value && yearToStr.value && !isNaN(yt) && yt >= 1900 && yt <= 2100) body.year_to = yt
      }
      if (datePreset.value && datePreset.value !== 'custom') {
        const now = new Date()
        body.date_to = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())).toISOString().slice(0, 10)
        const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()))
        if (datePreset.value === '1y') d.setUTCFullYear(d.getUTCFullYear() - 1)
        else if (datePreset.value === '5y') d.setUTCFullYear(d.getUTCFullYear() - 5)
        else if (datePreset.value === '10y') d.setUTCFullYear(d.getUTCFullYear() - 10)
        body.date_from = d.toISOString().slice(0, 10)
      }
      // Text Availability
      if (hasAbstract.value) body.has_abstract = true
      if (isFreeFullText.value) body.is_free_full_text = true
      if (hasFullText.value) body.has_full_text = true
      // Article Attribute
      if (hasAssociatedData.value) body.has_associated_data = true
      // Article Type
      if (pubTypes.value.length) body.pub_types = pubTypes.value
      // Language
      if (languages.value.length) body.languages = languages.value
      // Species / Sex / Age（mesh_ui 值）
      if (selectedSpecies.value.length) body.species = selectedSpecies.value
      if (selectedSex.value.length) body.sex = selectedSex.value
      if (selectedAge.value.length) body.age = selectedAge.value
      // Journal Categories
      if (medlineOnly.value) body.medline_only = true
      if (excludePreprints.value) body.exclude_preprints = true
      if (nlmSubsets.value.length) body.nlm_subsets = nlmSubsets.value
      // 其他
      if (selectedTiers.value.length) body.journal_tiers = selectedTiers.value
      if (selectedTags.value.length) body.tag_ids = selectedTags.value
      if (retracted.value) body.retracted = retracted.value
      if (negativeResult.value) body.negative_result = negativeResult.value
      const { data } = await api.post('/features/search/advanced', body, { signal })
      if (gen !== searchGeneration.value) return  // 丢弃老旧请求
      results.value = data.items || []
      // 游标分页：首页走 COUNT 存 total，后续页沿用；cursor 和数据一起返回
      if (p === 1) total.value = data.total || 0
      keysetHasMore.value = data.has_more || false
      keysetPage.value = p
      // 从响应保存游标，供下一页使用
      const items = data.items || []
      if (items.length > 0 && data.has_more && data.cursor_val) {
        keysetCursors.value[p + 1] = {
          cursor_val: data.cursor_val,
          cursor_id: data.cursor_id || items[items.length - 1].id,
        }
      } else {
        delete keysetCursors.value[p + 1]
      }
      yearCounts.value = data.year_counts || []
      searchError.value = ''
      syncSearchToUrl()  // 成功时同步 URL
    } catch (e: any) {
      if (e?.name === 'CanceledError' || e?.code === 'ERR_CANCELED') return
      results.value = []
      searchError.value = e?.response?.status === 401
        ? '请登录后使用搜索功能'
        : e?.response?.status === 400
          ? e?.response?.data?.detail || '搜索参数有误，请调整后重试'
          : e?.response?.status === 429
            ? '请求过于频繁，请稍后重试'
            : '搜索失败，请检查网络或稍后重试'
      toast.apiError(e, '搜索失败，请重试')
    }
    finally {
      if (gen === searchGeneration.value) loading.value = false
    }
  },
  pageSize: pageSize.value,
})

/** 从路由 query 恢复搜索参数 */
function restoreFromQuery() {
  if (route.query.q) query.value = String(route.query.q)
  const VALID_FIELDS = new Set(['all', 'title', 'abstract', 'author', 'affiliation', 'journal'])
  const VALID_SORTS = new Set(['date', 'cited', 'best_match', 'relevance', 'first_author', 'journal', 'title'])
  if (route.query.field && VALID_FIELDS.has(String(route.query.field))) field.value = String(route.query.field)
  if (route.query.sort && VALID_SORTS.has(String(route.query.sort))) sort.value = String(route.query.sort)
  if (route.query.date_preset && ['1y','5y','10y','custom'].includes(String(route.query.date_preset))) {
    datePreset.value = String(route.query.date_preset)
    if (datePreset.value === 'custom') {
      if (route.query.year_from) yearFromStr.value = String(route.query.year_from)
      if (route.query.year_to) yearToStr.value = String(route.query.year_to)
    } else {
      yearFromStr.value = ''
      yearToStr.value = ''
    }
  } else if (route.query.date_from || route.query.date_to) {
    datePreset.value = null
    if (route.query.date_from) {
      const df = String(route.query.date_from)
      urlDateFrom.value = df  // 保留完整日期避免精度丢失
      if (df.length >= 4) yearFromStr.value = df.slice(0, 4)
    }
    if (route.query.date_to) {
      const dt = String(route.query.date_to)
      urlDateTo.value = dt
      if (dt.length >= 4) yearToStr.value = dt.slice(0, 4)
    }
  } else {
    if (route.query.year_from) {
      yearFromStr.value = String(route.query.year_from)
      showCustomYear.value = true
    }
    if (route.query.year_to) {
      yearToStr.value = String(route.query.year_to)
      showCustomYear.value = true
    }
  }
  if (route.query.tag) selectedTags.value = String(route.query.tag).split(',')
  if (route.query.tier) selectedTiers.value = String(route.query.tier).split(',')
  if (route.query.pub_type) pubTypes.value = String(route.query.pub_type).split(',')
  if (route.query.lang) languages.value = String(route.query.lang).split(',')
  if (route.query.boolean === 'or') booleanOp.value = 'or'
  if (route.query.exact_phrase === 'true') exactPhrase.value = true
  if (route.query.subset) nlmSubsets.value = String(route.query.subset).split(',')
  if (route.query.retracted) retracted.value = String(route.query.retracted)
  if (route.query.negative) negativeResult.value = String(route.query.negative)
  if (route.query.species) selectedSpecies.value = String(route.query.species).split(',')
  if (route.query.sex) selectedSex.value = String(route.query.sex).split(',')
  if (route.query.age) selectedAge.value = String(route.query.age).split(',')
  if (route.query.has_abstract) hasAbstract.value = route.query.has_abstract === 'true'
  if (route.query.free_full_text) isFreeFullText.value = route.query.free_full_text === 'true'
  if (route.query.full_text) hasFullText.value = route.query.full_text === 'true'
  if (route.query.associated_data) hasAssociatedData.value = route.query.associated_data === 'true'
  if (route.query.medline_only) medlineOnly.value = route.query.medline_only === 'true'
  if (route.query.exclude_preprints) excludePreprints.value = route.query.exclude_preprints === 'true'
  if (route.query.p) restoredPage.value = parseInt(String(route.query.p)) || 1
  if (route.query.page_size) {
    const ps = parseInt(String(route.query.page_size))
    if (ps >= 10 && ps <= 100) pageSize.value = ps
  }
  // keyset 游标不能从 URL 恢复 → page>1 回退首页（仅键集排序模式）
  if (KEYSET_SORTS.has(sort.value) && restoredPage.value > 1) restoredPage.value = 1
}

// 选择预设（1y/5y/10y）时清除自定义年份并自动搜索
watch(datePreset, (val) => {
  if (val && val !== 'custom') {
    yearFromStr.value = ''
    yearToStr.value = ''
    urlDateFrom.value = ''; urlDateTo.value = ''  // 清除 URL 日期
    if (searched.value) goToPage(1)
  }
  if (val === 'custom') {
    urlDateFrom.value = ''; urlDateTo.value = ''  // 自定义年份覆盖 URL 日期
  }
})

// P2-10: 弹窗关闭时（mask-closable 或确定按钮）自动搜索
watch(showPubTypeModal, (val) => { if (!val && searched.value) goToPage(1) })
watch(showLangModal, (val) => { if (!val && searched.value) goToPage(1) })
watch(showAgeModal, (val) => { if (!val && searched.value) goToPage(1) })

// pageSize 变化时持久化并重新搜索
watch(pageSize, (v) => {
  localStorage.setItem('search:pageSize', String(v))
  if (searched.value) goToPage(1)
})

// 筛选面板状态持久化
watch(showFilters, (v) => {
  localStorage.setItem('search:showFilters', String(v))
})

async function loadFilterOptions() {
  try {
    const { data } = await api.get('/features/search/filter-options')
    filterOptions.value = data
  } catch (e) { toast.apiError(e, '加载筛选选项失败') }
}

onMounted(async () => {
  // 并行加载标签和筛选选项
  await Promise.all([
    api.get('/public/tags').then(({ data }) => { allTagsRaw.value = data.tags || [] }).catch(() => {}),
    loadFilterOptions(),
    loadSavedFilters(),
  ])
  // 默认只展开第一个标签分组
  if (allTagsRaw.value.length) {
    const firstL1 = allTagsRaw.value.find((t: TagOption) => t.level === 1)
    if (firstL1) expandedGroups.value[String(firstL1.id)] = true
  }
  restoreFromQuery()
  // 进入搜索页自动触发一次搜索，恢复保存的页码
  await goToPage(restoredPage.value)
})

// R31: 浏览器后退/前进时恢复搜索状态
onBeforeRouteUpdate(() => {
  if (_skipRouteSync.value > 0) return  // syncSearchToUrl 触发的路由变化，跳过
  restoreFromQuery()
  goToPage(restoredPage.value)
})

/** 同步当前搜索参数到 URL query */
function syncSearchToUrl() {
  const q: Record<string, string> = {}
  if (query.value) q.q = query.value
  if (field.value !== 'all') q.field = field.value
  if (sort.value !== 'date') q.sort = sort.value
  if (datePreset.value && datePreset.value !== 'custom') {
    q.date_preset = datePreset.value
    // 同时保存计算出的 date_from/date_to，使分享的 URL 能直接恢复
    const now = new Date()
    q.date_to = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())).toISOString().slice(0, 10)
    const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()))
    if (datePreset.value === '1y') d.setUTCFullYear(d.getUTCFullYear() - 1)
    else if (datePreset.value === '5y') d.setUTCFullYear(d.getUTCFullYear() - 5)
    else if (datePreset.value === '10y') d.setUTCFullYear(d.getUTCFullYear() - 10)
    q.date_from = d.toISOString().slice(0, 10)
  } else {
    if (datePreset.value === 'custom') q.date_preset = 'custom'
    if (urlDateFrom.value) q.date_from = urlDateFrom.value
    else if (yearFromStr.value) q.year_from = yearFromStr.value
    if (urlDateTo.value) q.date_to = urlDateTo.value
    else if (yearToStr.value) q.year_to = yearToStr.value
  }
  if (selectedTags.value.length) q.tag = selectedTags.value.join(',')
  if (selectedTiers.value.length) q.tier = selectedTiers.value.join(',')
  if (pubTypes.value.length) q.pub_type = pubTypes.value.join(',')
  if (languages.value.length) q.lang = languages.value.join(',')
  if (nlmSubsets.value.length) q.subset = nlmSubsets.value.join(',')
  if (retracted.value) q.retracted = retracted.value
  if (negativeResult.value) q.negative = negativeResult.value
  if (selectedSpecies.value.length) q.species = selectedSpecies.value.join(',')
  if (selectedSex.value.length) q.sex = selectedSex.value.join(',')
  if (selectedAge.value.length) q.age = selectedAge.value.join(',')
  if (hasAbstract.value) q.has_abstract = 'true'
  if (isFreeFullText.value) q.free_full_text = 'true'
  if (hasFullText.value) q.full_text = 'true'
  if (hasAssociatedData.value) q.associated_data = 'true'
  if (medlineOnly.value) q.medline_only = 'true'
  if (excludePreprints.value) q.exclude_preprints = 'true'
  if (booleanOp.value !== 'and') q.boolean = booleanOp.value
  if (exactPhrase.value) q.exact_phrase = 'true'
  if (pageSize.value !== 20) q.page_size = String(pageSize.value)
  // 页码持久化到 URL（Keyset 排序使用 keyset 页码，offset 排序使用 offset 页码）
  const currentPage = KEYSET_SORTS.has(sort.value) ? keysetPage.value : page.value
  if (currentPage > 1) q.p = String(currentPage)
  // R24: use push instead of replace to preserve browser back-button history
  _skipRouteSync.value++
  router.push({ query: q }).catch(() => {}).finally(() => _skipRouteSync.value--)
}

function resetAllFilters() {
  yearFromStr.value = ''; yearToStr.value = ''
  urlDateFrom.value = ''; urlDateTo.value = ''  // P6: clear stale URL dates
  datePreset.value = null
  selectedTiers.value = []
  selectedTags.value = []
  pubTypes.value = []
  languages.value = []
  nlmSubsets.value = []
  retracted.value = ''
  negativeResult.value = ''
  selectedSpecies.value = []
  selectedSex.value = []
  selectedAge.value = []
  hasAbstract.value = false
  isFreeFullText.value = false
  hasFullText.value = false
  hasAssociatedData.value = false
  medlineOnly.value = false
  excludePreprints.value = false
  query.value = ''
  field.value = 'all'
  sort.value = 'date'
  booleanOp.value = 'and'
  exactPhrase.value = false
  showCustomYear.value = false
  pageSize.value = Number(localStorage.getItem('search:pageSize')) || 20  // R31: 重置每页条数
  goToPage(1)
}


function goDetail(pmid: number) {
  if (auth.isAuthenticated) {
    router.push(`/app/literature/${pmid}`)
  } else {
    router.push(`/literature/${pmid}`)
  }
}

/** 筛选选项访问器 */
const pubTypeOptions = computed(() => filterOptions.value?.pub_types || [])
const languageOptions = computed(() => filterOptions.value?.languages || [])
const nlmSubsetOptions = computed(() => filterOptions.value?.nlm_subsets || [])
const specialTags = computed(() => filterOptions.value?.special_tags || {})
</script>

<template>
  <div class="search-container" style="max-width:1200px;margin:0 auto;padding:32px 24px">
    <!-- ======== 左侧筛选面板（PubMed 顺序） ======== -->
    <div v-if="showFilters" class="filter-panel">
      <div class="filter-panel-scroll">
        <div style="font-size:15px;font-weight:700;margin-bottom:12px;color:var(--text-primary)">筛选条件</div>

        <!-- My Custom Filters -->
        <div class="filter-section">
          <div class="filter-title">MY CUSTOM FILTERS</div>
          <div v-if="auth.isAuthenticated && savedFilters.length">
            <div v-for="f in savedFilters" :key="f.id"
                 style="display:flex;align-items:center;gap:4px;padding:3px 0;font-size:13px;cursor:pointer"
                 @click="applySavedFilter(f)">
              <span style="color:var(--kw-pill-color);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px">{{ f.name }}</span>
              <span style="font-size:11px;color:var(--text-muted);cursor:pointer;flex-shrink:0" @click.stop="openEditFilter(f)">✎</span>
            </div>
          </div>
          <div style="margin-top:4px">
            <span class="filter-expand-link" @click="auth.isAuthenticated ? (openNewFilter(), showFilterEditModal = true) : router.push('/auth/login?redirect=' + encodeURIComponent(route.fullPath))">Edit custom filters</span>
          </div>
        </div>

        <!-- 1. RESULTS BY YEAR（面积图 + 双向滑块） -->
        <div class="filter-section" v-if="yearCounts.length && searched">
          <div class="filter-title">RESULTS BY YEAR</div>
          <div class="histogram-chart">
            <div v-for="yc in yearCounts" :key="yc.year" class="histogram-bar-wrap"
              :class="{ muted: yearSliderValue && (yc.year < yearSliderValue[0] || yc.year > yearSliderValue[1]) }">
              <div class="histogram-bar" :style="{ height: (yc.count / maxYearCount * 100) + '%' }"
                :title="`${yc.year}: ${yc.count.toLocaleString()}篇`"></div>
            </div>
          </div>
          <NSlider
            range
            :min="minYear"
            :max="maxYear"
            :step="1"
            :value="yearSliderValue"
            @update:value="onYearSliderChange"
            :format-tooltip="(v: number) => String(v)"
          />
          <div class="histogram-slider-labels">
            <span>{{ yearSliderValue[0] }}</span>
            <span>{{ yearSliderValue[1] }}</span>
          </div>
          <div style="margin-top:6px">
            <span class="filter-expand-link" @click="showCustomYear = !showCustomYear">
              {{ showCustomYear ? '收起' : '自定义年份范围' }}
            </span>
            <div v-if="showCustomYear" style="display:flex;gap:6px;margin-top:6px">
              <NInput v-model:value="yearFromStr" placeholder="起始年" size="tiny" :maxlength="4" />
              <span style="color:var(--text-muted);line-height:28px">—</span>
              <NInput v-model:value="yearToStr" placeholder="截止年" size="tiny" :maxlength="4" />
              <NButton size="tiny" :disabled="loading" @click="goToPage(1)" style="flex-shrink:0">确定</NButton>
            </div>
          </div>
        </div>

        <!-- 2. PUBLICATION DATE -->
        <div class="filter-section">
          <div class="filter-title">PUBLICATION DATE</div>
          <NRadioGroup v-model:value="datePreset">
            <div style="display:flex;flex-direction:column;gap:4px">
              <NRadio value="1y" size="small">1 year</NRadio>
              <NRadio value="5y" size="small">5 years</NRadio>
              <NRadio value="10y" size="small">10 years</NRadio>
              <NRadio value="custom" size="small">Custom Range</NRadio>
            </div>
          </NRadioGroup>
          <div v-if="datePreset === 'custom'" style="display:flex;gap:6px;margin-top:6px">
            <NInput v-model:value="yearFromStr" placeholder="起始年" size="tiny" :maxlength="4" />
            <span style="color:var(--text-muted);line-height:28px">—</span>
            <NInput v-model:value="yearToStr" placeholder="截止年" size="tiny" :maxlength="4" />
            <NButton size="tiny" :disabled="loading" @click="goToPage(1)" style="flex-shrink:0">确定</NButton>
          </div>
        </div>

        <!-- 3. TEXT AVAILABILITY -->
        <div class="filter-section">
          <div class="filter-title">TEXT AVAILABILITY</div>
          <div style="display:flex;flex-direction:column;gap:4px">
            <NCheckbox v-model:checked="hasAbstract" style="font-size:13px">
              Abstract <span class="filter-count" v-if="filterOptions?.text_availability">({{ filterOptions.text_availability.has_abstract }})</span>
            </NCheckbox>
            <NCheckbox v-model:checked="isFreeFullText" style="font-size:13px">
              Free full text <span class="filter-count" v-if="filterOptions?.text_availability">({{ filterOptions.text_availability.is_free_full_text }})</span>
            </NCheckbox>
            <NCheckbox v-model:checked="hasFullText" style="font-size:13px">
              Full text <span class="filter-count" v-if="filterOptions?.text_availability">({{ filterOptions.text_availability.has_full_text }})</span>
            </NCheckbox>
          </div>
        </div>

        <!-- 4. ARTICLE ATTRIBUTE -->
        <div class="filter-section">
          <div class="filter-title">ARTICLE ATTRIBUTE</div>
          <div style="display:flex;flex-direction:column;gap:4px">
            <NCheckbox v-model:checked="hasAssociatedData" style="font-size:13px">
              Associated data
            </NCheckbox>
          </div>
        </div>

        <!-- 5. ARTICLE TYPE -->
        <div class="filter-section">
          <div class="filter-title">ARTICLE TYPE</div>
          <NCheckboxGroup v-model:value="pubTypes">
            <template v-for="(pt, i) in pubTypeOptions" :key="pt.name">
              <div v-if="i < MAX_VISIBLE_PUB_TYPES" style="margin:2px 0;display:flex;align-items:center">
                <NCheckbox :value="pt.name" style="font-size:13px">
                  {{ pt.name }} <span class="filter-count">({{ pt.count.toLocaleString() }})</span>
                </NCheckbox>
              </div>
            </template>
            <div v-if="pubTypeOptions.length > MAX_VISIBLE_PUB_TYPES" style="margin-top:2px">
              <span class="filter-expand-link" @click="showPubTypeModal = true">
                更多 ({{ pubTypeOptions.length - MAX_VISIBLE_PUB_TYPES }})
              </span>
            </div>
          </NCheckboxGroup>
        </div>

        <!-- 6. ARTICLE LANGUAGE -->
        <div class="filter-section">
          <div class="filter-title">ARTICLE LANGUAGE</div>
          <NCheckboxGroup v-model:value="languages">
            <div style="display:flex;flex-direction:column;gap:4px">
              <template v-for="(l, i) in languageOptions" :key="l.code">
                <div v-if="i < MAX_VISIBLE_LANGUAGES" style="margin:2px 0;display:flex;align-items:center">
                  <NCheckbox :value="l.code" size="small">
                    {{ l.code.toUpperCase() }} <span class="filter-count">({{ l.count.toLocaleString() }})</span>
                  </NCheckbox>
                </div>
              </template>
              <div v-if="languageOptions.length > MAX_VISIBLE_LANGUAGES" style="margin-top:2px">
                <span class="filter-expand-link" @click="showLangModal = true">
                  更多 ({{ languageOptions.length - MAX_VISIBLE_LANGUAGES }})
                </span>
              </div>
            </div>
          </NCheckboxGroup>
        </div>

        <!-- 7. SPECIES -->
        <div v-if="specialTags.species?.length" class="filter-section">
          <div class="filter-title">SPECIES</div>
          <NCheckboxGroup v-model:value="selectedSpecies">
            <div v-for="t in specialTags.species" :key="t.mesh_ui" style="margin:2px 0;display:flex;align-items:center">
              <NCheckbox :value="t.mesh_ui" style="font-size:13px">{{ t.name_zh || t.name_en }}</NCheckbox>
            </div>
          </NCheckboxGroup>
        </div>

        <!-- 8. SEX -->
        <div v-if="specialTags.sex?.length" class="filter-section">
          <div class="filter-title">SEX</div>
          <NCheckboxGroup v-model:value="selectedSex">
            <div v-for="t in specialTags.sex" :key="t.mesh_ui" style="margin:2px 0;display:flex;align-items:center">
              <NCheckbox :value="t.mesh_ui" style="font-size:13px">{{ t.name_zh || t.name_en }}</NCheckbox>
            </div>
          </NCheckboxGroup>
        </div>

        <!-- 9. AGE -->
        <div v-if="specialTags.age?.length" class="filter-section">
          <div class="filter-title">AGE</div>
          <NCheckboxGroup v-model:value="selectedAge">
            <div v-for="t in specialTags.age.slice(0, MAX_VISIBLE_AGE)" :key="t.key" style="margin:2px 0;display:flex;align-items:center"
                 :style="t.indent === 0 ? {fontWeight:600,marginTop:'6px'} : {}">
              <NCheckbox :value="t.key" style="font-size:13px">{{ t.label }}</NCheckbox>
            </div>
            <div v-if="specialTags.age.length > MAX_VISIBLE_AGE" style="margin-top:2px">
              <span class="filter-expand-link" @click="showAgeModal = true">
                更多 ({{ specialTags.age.length - MAX_VISIBLE_AGE }})
              </span>
            </div>
          </NCheckboxGroup>
        </div>

        <!-- 10. OTHER -->
        <div class="filter-section">
          <div class="filter-title">OTHER</div>
          <!-- MEDLINE + Exclude Preprints -->
          <div style="margin-bottom:8px">
            <NCheckbox v-model:checked="medlineOnly" style="font-size:13px">MEDLINE</NCheckbox>
          </div>
          <div style="margin-bottom:8px">
            <NCheckbox v-model:checked="excludePreprints" style="font-size:13px">Exclude preprints</NCheckbox>
          </div>
          <!-- 期刊子集 -->
          <div v-if="nlmSubsetOptions.length" style="margin-bottom:8px">
            <div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:4px">期刊子集</div>
            <NCheckboxGroup v-model:value="nlmSubsets">
              <div v-for="s in nlmSubsetOptions" :key="s.code" style="margin:2px 0;display:flex;align-items:center">
                <NCheckbox :value="s.code" style="font-size:13px">
                  {{ s.label }} <span class="filter-count">({{ s.count.toLocaleString() }})</span>
                </NCheckbox>
              </div>
            </NCheckboxGroup>
          </div>
          <!-- 撤稿 -->
          <div style="margin-bottom:8px">
            <div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:4px">撤稿</div>
            <NRadioGroup v-model:value="retracted" name="retractedGroup">
              <div style="display:flex;flex-direction:column;gap:4px">
                <NRadio value="" size="small">全部</NRadio>
                <NRadio value="no" size="small">未撤稿</NRadio>
                <NRadio value="only" size="small">仅撤稿</NRadio>
              </div>
            </NRadioGroup>
          </div>
          <!-- 结果类型 -->
          <div style="margin-bottom:8px">
            <div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:4px">结果类型</div>
            <NRadioGroup v-model:value="negativeResult" name="negativeGroup">
              <div style="display:flex;flex-direction:column;gap:4px">
                <NRadio value="" size="small">全部</NRadio>
                <NRadio value="no" size="small">阳性结果</NRadio>
                <NRadio value="only" size="small">阴性结果</NRadio>
              </div>
            </NRadioGroup>
          </div>
          <!-- 期刊等级 -->
          <div style="margin-bottom:8px">
            <div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:4px">期刊等级</div>
            <NCheckboxGroup v-model:value="selectedTiers">
              <div v-for="t in [{v:'1',l:'四大综合'},{v:'2',l:'肿瘤顶刊'},{v:'3',l:'专科顶刊'},{v:'4',l:'其他SCI'}]" :key="t.v" style="margin:2px 0;display:flex;align-items:center">
                <NCheckbox :value="t.v" style="font-size:13px">{{ t.l }}</NCheckbox>
              </div>
            </NCheckboxGroup>
          </div>
        </div>

        <!-- MeSH 标签（扩展筛选） -->
        <div class="filter-section">
          <div class="filter-title">MeSH 标签</div>
          <NCheckboxGroup v-model:value="selectedTags">
            <div v-for="g in groupedTags" :key="g.parentId" style="margin-bottom:6px">
              <div style="display:flex;align-items:center;cursor:pointer;font-size:13px;font-weight:600;color:var(--text-muted);padding:2px 0;user-select:none" @click="toggleGroup(g.parentId)">
                <span style="display:inline-block;width:12px;font-size:12px;transition:transform .15s" :style="{transform: expandedGroups[g.parentId] ? 'rotate(90deg)' : ''}">▶</span>
                {{ g.parentName }}
                <span style="margin-left:4px;font-size:12px;color:var(--text-muted)">({{ g.tags.length }})</span>
              </div>
              <template v-if="expandedGroups[g.parentId]">
                <div v-for="t in g.tags" :key="t.id" style="display:flex;align-items:center;margin:1px 0;padding-left:16px">
                  <NCheckbox :value="t.id" style="font-size:13px">{{ t.name_zh }}</NCheckbox>
                </div>
              </template>
            </div>
          </NCheckboxGroup>
        </div>

        <NButton class="sky-btn" block size="small" :loading="loading" :disabled="loading" @click="goToPage(1)">
          <template #icon><NIcon size="14"><FilterOutline /></NIcon></template>应用筛选
        </NButton>
        <div style="text-align:center;margin-top:6px">
          <span style="font-size:13px;color:var(--text-muted);cursor:pointer" @click="resetAllFilters">重置所有筛选</span>
        </div>
      </div>
    </div>

    <!-- ======== 右侧搜索结果 ======== -->
    <div style="flex:1;min-width:0">
      <div class="search-bar">
        <div class="search-input-wrap"><NInput v-model:value="query" placeholder='搜索标题、摘要、作者、MeSH词... 支持PubMed语法如 "lung cancer"[TI]' size="small" clearable aria-label="搜索文献" @keyup.enter="goToPage(1)" /></div>
        <NSelect v-model:value="booleanOp" :options="[{label:'AND',value:'and'},{label:'OR',value:'or'}]" size="small" style="width:75px;flex-shrink:0" />
        <NCheckbox v-model:checked="exactPhrase" size="small" style="flex-shrink:0;font-size:13px;white-space:nowrap">精确短语</NCheckbox>
        <NSelect v-model:value="field" :options="[{label:'全部',value:'all'},{label:'标题',value:'title'},{label:'摘要',value:'abstract'},{label:'作者',value:'author'},{label:'机构',value:'affiliation'},{label:'期刊',value:'journal'}]" size="small" style="width:90px;flex-shrink:0" />
        <NButton class="sky-btn" size="small" :loading="loading" :disabled="loading" @click="goToPage(1)"><template #icon><NIcon size="14"><SearchOutline /></NIcon></template>搜索</NButton>
        <NButton class="sky-btn" :ghost="showFilters" size="small" @click="showFilters=!showFilters"><template #icon><NIcon size="14"><component :is="showFilters ? EyeOffOutline : FilterOutline" /></NIcon></template>{{ showFilters?'隐藏筛选':'筛选' }}</NButton>
      </div>

      <!-- 结果工具栏：计数 + 排序 + 显示设置 -->
      <div v-if="searched" class="results-toolbar">
        <span class="results-count">
          找到 {{ total }} 条结果
        </span>
        <div class="results-toolbar-right">
          <span class="toolbar-label">排序:</span>
          <NSelect v-model:value="sort" :options="[
            {label:'Best Match',value:'best_match'},
            {label:'Most Recent',value:'date'},
            {label:'Most Cited',value:'cited'},
            {label:'Relevance',value:'relevance'},
            {label:'First author',value:'first_author'},
            {label:'Journal',value:'journal'},
            {label:'Title',value:'title'},
          ]" size="tiny" style="width:150px" @update:value="goToPage(1)" />
          <span class="toolbar-label">每页:</span>
          <NSelect v-model:value="pageSize" :options="[
            {label:'10条/页',value:10},
            {label:'20条/页',value:20},
            {label:'50条/页',value:50},
          ]" size="tiny" style="width:100px" />
          <NPopover trigger="click" placement="bottom-end" :width="200">
            <template #trigger>
              <NButton size="tiny" quaternary title="显示设置"><template #icon><NIcon size="14"><SettingsOutline /></NIcon></template></NButton>
            </template>
            <div style="font-size:13px;font-weight:600;margin-bottom:8px">显示设置</div>
            <div style="display:flex;flex-direction:column;gap:4px;font-size:13px">
              <NCheckbox v-model:checked="displaySettings.showAuthors">作者</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showAffiliation">机构</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showJournal">期刊+分区</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showPmid">PMID</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showDoi">DOI</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showStudyTypes">研究类型+OA+NCT</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showTags">MeSH 标签</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showCitedBy">被引次数</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showAiSummary">AI 摘要</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showBadges">徽章</NCheckbox>
              <NCheckbox v-model:checked="displaySettings.showActions">操作按钮</NCheckbox>
            </div>
          </NPopover>
        </div>
      </div>

	    <PageSkeleton :loading="loading && !results.length">
      <NResult v-if="searchError" status="error" :title="searchError" description="可稍后重试或联系管理员" />
      <NEmpty v-else-if="searched&&!loading&&!results.length" description="未找到匹配文献，尝试修改搜索条件" />

      <LiteratureCard
        v-for="item in results"
        :key="item.pmid"
        :item="item"
        :searchQuery="query"
        :savedPmids="savedPmids"
        :displaySettings="displaySettings"
        @detail="(i) => goDetail(i.pmid)"
        @preview="(item: LiteratureItem) => setPreviewPmid(item.pmid)"
        @save="handleSave"
      />

      <!-- Keyset 分页（sort=date，不做 COUNT，纯翻页） -->
      <div v-if="searched && KEYSET_SORTS.has(sort) && results.length > 0" style="display:flex;justify-content:center;align-items:center;gap:12px;padding:20px">
        <NButton size="small" :disabled="keysetPage <= 1 || loading" @click="goToPage(keysetPage - 1)">← 上一页</NButton>
        <span style="font-size:13px;color:var(--text-muted)">第 {{ keysetPage }} 页</span>
        <NButton size="small" :disabled="!keysetHasMore || loading" @click="goToPage(keysetPage + 1)">下一页 →</NButton>
      </div>
      <!-- Offset 分页（其他排序，需 COUNT） -->
      <div v-else-if="searched && total > 0" style="display:flex;justify-content:center;padding:20px">
        <NPagination
          :page="page"
          :item-count="total"
          :page-size="pageSize"
          @update:page="goToPage"
          :simple="true"
        />
      </div>
    </PageSkeleton>
    </div>
  </div>

  <NModal v-model:show="showPubTypeModal" :mask-closable="true" preset="card" :segmented="{content:true,footer:true}" :bordered="true" style="width:95vw;max-width:900px">
    <template #header><div style="font-size:16px;font-weight:700">选择文献类型</div></template>
    <div style="max-height:60vh;overflow-y:auto;padding:8px 0">
      <NCheckboxGroup v-model:value="pubTypes">
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:2px 16px">
          <div v-for="pt in pubTypeOptions" :key="pt.name" style="margin:3px 0;display:flex;align-items:center">
            <NCheckbox :value="pt.name" style="font-size:13px">
              {{ pt.name }} <span class="filter-count">({{ pt.count.toLocaleString() }})</span>
            </NCheckbox>
          </div>
        </div>
      </NCheckboxGroup>
    </div>
    <template #footer>
      <div style="display:flex;justify-content:space-between;align-items:center">
        <span style="font-size:13px;color:var(--text-muted)">已选择 {{ pubTypes.length }} 项</span>
        <NButton size="small" type="primary" @click="showPubTypeModal = false">确定</NButton>
      </div>
    </template>
  </NModal>

  <NModal v-model:show="showLangModal" :mask-closable="true" preset="card" :segmented="{content:true,footer:true}" :bordered="true" style="width:80vw;max-width:700px">
    <template #header><div style="font-size:16px;font-weight:700">选择语言</div></template>
    <div style="max-height:60vh;overflow-y:auto;padding:8px 0">
      <NCheckboxGroup v-model:value="languages">
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:2px 16px">
          <div v-for="l in languageOptions" :key="l.code" style="margin:3px 0;display:flex;align-items:center">
            <NCheckbox :value="l.code" size="small">
              {{ l.code.toUpperCase() }} <span class="filter-count">({{ l.count.toLocaleString() }})</span>
            </NCheckbox>
          </div>
        </div>
      </NCheckboxGroup>
    </div>
    <template #footer>
      <div style="display:flex;justify-content:flex-end">
        <NButton size="small" type="primary" @click="showLangModal = false">确定</NButton>
      </div>
    </template>
  </NModal>

  <NModal v-model:show="showAgeModal" :mask-closable="true" preset="card" :segmented="{content:true,footer:true}" :bordered="true" style="width:80vw;max-width:740px">
    <template #header><div style="font-size:16px;font-weight:700">选择年龄分组</div></template>
    <div style="max-height:60vh;overflow-y:auto;padding:8px 0">
      <NCheckboxGroup v-model:value="selectedAge">
        <div v-for="t in specialTags.age" :key="t.key" style="margin:3px 0;display:flex;align-items:center">
          <NCheckbox :value="t.key" style="font-size:13px">{{ t.label }}</NCheckbox>
        </div>
      </NCheckboxGroup>
    </div>
    <template #footer>
      <div style="display:flex;justify-content:flex-end">
        <NButton size="small" type="primary" @click="showAgeModal = false">确定</NButton>
      </div>
    </template>
  </NModal>

  <!-- My Custom Filters 管理弹窗 -->
  <NModal v-model:show="showFilterEditModal" :mask-closable="true" preset="card" :segmented="{content:true,footer:true}" :bordered="true" style="width:95vw;max-width:700px">
    <template #header><div style="font-size:16px;font-weight:700">My Custom Filters</div></template>
    <div style="max-height:60vh;overflow-y:auto;padding:8px 0">
      <!-- 已有筛选器列表 -->
      <div v-for="(f, i) in savedFilters" :key="f.id"
           style="display:flex;align-items:center;gap:8px;padding:8px 4px;border-bottom:1px solid var(--border-color)">
        <div style="flex:1;min-width:0">
          <div style="font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ f.name }}</div>
          <div style="font-size:12px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ f.query_string }}</div>
        </div>
        <NButton size="tiny" quaternary @click="openEditFilter(f)">编辑</NButton>
        <NButton size="tiny" quaternary @click="moveFilterUp(i)" :disabled="i===0">↑</NButton>
        <NButton size="tiny" quaternary @click="moveFilterDown(i)" :disabled="i===savedFilters.length-1">↓</NButton>
        <NButton size="tiny" quaternary type="error" @click="deleteSavedFilter(f.id)">删除</NButton>
      </div>
      <div v-if="!savedFilters.length" style="text-align:center;padding:24px 0;color:var(--text-muted);font-size:13px">
        暂无自定义筛选器
      </div>
    </div>
    <template #footer>
      <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
        <NInput v-model:value="editName" placeholder="筛选器名称" size="small" style="width:150px" :maxlength="100" />
        <NInput v-model:value="editQuery" placeholder="搜索查询（如 cancer[Title]）" size="small" style="flex:1;min-width:160px" :maxlength="500" />
        <NButton size="small" type="primary" @click="saveFilter()" :disabled="!editName.trim() || !editQuery.trim()">{{ editFilterId ? '更新' : '添加' }}</NButton>
      </div>
    </template>
  </NModal>

  <LiteraturePreviewDrawer
    :show="showPreview"
    :pmid="previewPmid"
    @close="closePreview"
    @go-detail="(item) => goDetail(item.pmid)"
  />
</template>

<style scoped>
/* Base layout classes */
.search-container {
  display: flex;
  gap: 20px;
}
.filter-panel {
  width: 270px;
  flex-shrink: 0;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 12px;
}
.filter-panel-scroll {
  overflow-y: auto;
  padding-right: 4px;
}
.filter-panel-scroll::-webkit-scrollbar { width: 4px; }
.filter-panel-scroll::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 2px; }

.filter-section {
  margin-bottom: 14px;
}
.filter-title {
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--text-secondary);
}
.filter-count {
  font-size: 12px;
  color: var(--text-muted);
}
.filter-expand-link {
  font-size: 12px;
  color: var(--kw-pill-color);
  cursor: pointer;
  user-select: none;
}
html.dark .filter-expand-link { color: #6ab0e0; }
.filter-expand-link:hover { text-decoration: underline; }

/* 发表年份分布 — 面积图 + 双向滑块 */
.histogram-chart {
  height: 70px;
  display: flex;
  align-items: flex-end;
  gap: 1px;
  position: relative;
  margin-bottom: 2px;
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 1px;
}
.histogram-bar-wrap {
  flex: 1;
  display: flex;
  align-items: flex-end;
  height: 100%;
  min-width: 0;
  transition: opacity .15s;
}
.histogram-bar-wrap.muted {
  opacity: .3;
}
.histogram-bar {
  width: 100%;
  min-height: 1px;
  border-radius: 1px 1px 0 0;
  background: linear-gradient(to top, var(--kw-pill-color) 0%, color-mix(in srgb, var(--kw-pill-color) 40%, transparent) 100%);
  transition: opacity .15s;
}
html.dark .histogram-bar {
  background: linear-gradient(to top, #6ab0e0 0%, color-mix(in srgb, #6ab0e0 35%, transparent) 100%);
}
.histogram-slider-labels {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

.search-bar {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: nowrap;
  margin-bottom: 16px;
}
.search-input-wrap {
  flex: 1;
  min-width: 80px;
}
.search-bar :deep(.n-select) {
  width: 105px;
  flex-shrink: 0;
}
.search-bar :deep(.n-input__wrapper) {
  height: 32px !important;
}
.search-bar :deep(.n-input) {
  height: 32px !important;
}
.search-bar :deep(.n-base-selection) {
  height: 32px !important;
}
.search-bar :deep(.n-base-selection-label) {
  height: 32px !important;
}
.search-bar :deep(.n-button) {
  height: 32px !important;
  flex-shrink: 0;
}

@media (max-width: 768px) {
  .search-container {
    flex-direction: column;
    padding: 12px;
  }
  .filter-panel {
    width: 100%;
  }
  .filter-panel-scroll {
    max-height: none;
  }
  .search-bar {
    flex-wrap: wrap;
    gap: 8px;
  }
  .search-input-wrap {
    flex: 1 1 100%;
  }
  .results-toolbar {
    flex-wrap: wrap;
    gap: 8px;
  }
  .results-toolbar-right {
    flex: 1;
    justify-content: flex-end;
  }
}

/* 结果工具栏 */
.results-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  gap: 12px;
}
.results-count {
  color: var(--text-secondary);
  font-size: 13px;
  white-space: nowrap;
}
.results-toolbar-right {
  display: flex;
  align-items: center;
  gap: 8px;
}
.toolbar-label {
  font-size: 13px;
  color: var(--text-muted);
  white-space: nowrap;
}
</style>
