/**
 * [[INFRA-F111]] ②a — 顶栏搜索的匹配判据。
 *
 * 只测 `searchNavigationPages` 这个纯函数：它是「搜到什么」的唯一定义，
 * 交互（Cmd+K / ↑↓ / Esc / 焦点归还）在浏览器里实测，不在这里假装用 jsdom 覆盖。
 *
 * ⚠️ 判据本身要验，不只验覆盖面：每条断言都配了会让它失效的反向样本
 * （能命中 ≠ 判据对；还得证明不该命中的确实没命中）。
 */
import { describe, it, expect } from 'vitest'
import { navigationGroups, searchNavigationPages, HIDDEN_PAGE_IDS } from '../playground/docs/navigation'

const ids = (query: string, locale: 'en-US' | 'zh-CN' = 'en-US') =>
  searchNavigationPages(query, locale).map((hit) => hit.id)

describe('searchNavigationPages — 基本判据', () => {
  it('空查询返回空（不是「返回全部」）——收起态别把整份目录塞给用户', () => {
    expect(searchNavigationPages('', 'en-US')).toEqual([])
    expect(searchNavigationPages('   ', 'zh-CN')).toEqual([])
  })

  it('按组件名命中，且不误伤无关页', () => {
    expect(ids('badge')).toContain('badge')
    expect(ids('badge')).not.toContain('button')
  })

  it('大小写与首尾空格无关', () => {
    expect(ids('  BaDgE  ')).toEqual(ids('badge'))
  })

  it('查不到的词返回空数组（不是抛错、也不是回退到全部）', () => {
    expect(ids('zzzz-not-a-component')).toEqual([])
  })
})

describe('跨语言匹配（这是本项的主要价值）', () => {
  it('中文界面下输入英文组件名也命中', () => {
    const hits = searchNavigationPages('button', 'zh-CN')
    expect(hits.map((h) => h.id)).toContain('button')
    // 展示用的是当前语言的名字，不是匹配用的那一份
    expect(hits.find((h) => h.id === 'button')?.label).not.toBe('')
  })

  it('英文界面下输入中文名也命中', () => {
    const zhLabel = navigationGroups
      .flatMap((g) => g.items)
      .find((item) => item.id === 'button')?.label['zh-CN']
    expect(zhLabel).toBeTruthy()
    expect(ids(zhLabel!, 'en-US')).toContain('button')
  })
})

describe('排序：前缀命中优先，同档保持导航原始顺序', () => {
  // 查询 'n' 是**特意选的**：前缀命中只有 notification，而 button/icon/input… 是「仅包含」命中
  // 且它们在导航里排在 notification **之前**、字母序也排在它之前。
  // ⇒ 这个样本同时能抓住「没做前缀优先」和「按字母重排」两种坏法。
  // （早先我用的是 'form'，那是个**空过**样本：命中全是前缀，尾段为空数组，排序断言恒真。
  //   故障注入「改成字母序」时它没转红，才发现。判据本身要验，不只验覆盖面。）
  const DISCRIMINATING = 'n'

  it('前缀命中排在「仅包含」命中之前', () => {
    const hits = ids(DISCRIMINATING)
    const firstNonPrefix = hits.findIndex((id) => !id.startsWith(DISCRIMINATING))
    const lastPrefix = hits.reduce(
      (acc, id, index) => (id.startsWith(DISCRIMINATING) ? index : acc),
      -1
    )
    expect(lastPrefix, '样本里必须同时存在两类命中，否则本条是空过').toBeGreaterThanOrEqual(0)
    expect(firstNonPrefix, '样本里必须同时存在两类命中，否则本条是空过').toBeGreaterThanOrEqual(0)
    expect(lastPrefix).toBeLessThan(firstNonPrefix)
  })

  it('同档内保持导航原始顺序，不按字母重排', () => {
    const all = navigationGroups.flatMap((g) => g.items).map((item) => item.id)
    const hits = ids(DISCRIMINATING)
    const tail = hits.filter((id) => !id.startsWith(DISCRIMINATING)).map((id) => all.indexOf(id))
    expect(tail.length, '尾段为空则本条是空过').toBeGreaterThan(1)
    expect([...tail].sort((a, b) => a - b)).toEqual(tail)
    // 反向锚：这批 id 的字母序与导航序**确实不同**，所以上一条断言有区分力
    const alphabetical = [...hits.filter((id) => !id.startsWith(DISCRIMINATING))].sort((a, b) =>
      a.localeCompare(b)
    )
    expect(alphabetical).not.toEqual(hits.filter((id) => !id.startsWith(DISCRIMINATING)))
  })
})

describe('范围边界：②a 是名字过滤，不是全文检索', () => {
  it('只在 summary 里出现的词**不**命中 —— 否则结果行说不清为什么命中', () => {
    const item = navigationGroups.flatMap((g) => g.items).find((entry) => entry.summary['en-US'].length > 20)
    expect(item).toBeTruthy()
    // 取一个只可能出现在 summary、不在 id/label/title 里的词
    const summaryOnlyWord = item!.summary['en-US']
      .toLowerCase()
      .split(/[^a-z]+/)
      .find(
        (word) =>
          word.length > 5 &&
          !item!.id.toLowerCase().includes(word) &&
          !item!.label['en-US'].toLowerCase().includes(word) &&
          !item!.title['en-US'].toLowerCase().includes(word)
      )
    expect(summaryOnlyWord, '没找到 summary 专属词，样本选择失败').toBeTruthy()
    expect(ids(summaryOnlyWord!)).not.toContain(item!.id)
  })
})

describe('隐藏页天然不在结果里（靠数据源，不靠过滤分支）', () => {
  it('a11y-report / changelog 不出现在任何查询结果中', () => {
    for (const hidden of HIDDEN_PAGE_IDS) {
      expect(ids(hidden)).not.toContain(hidden)
    }
  })

  it('阳性对照：它们确实不在 navigationGroups 里（本条一旦变红，上面那条就成了空过）', () => {
    const navIds = navigationGroups.flatMap((g) => g.items).map((item) => item.id)
    for (const hidden of HIDDEN_PAGE_IDS) {
      expect(navIds).not.toContain(hidden)
    }
  })
})

describe('不设结果上限（静默截断比列表长更糟）', () => {
  it('能匹配全部页面的查询返回全部，不被截断', () => {
    const all = navigationGroups.flatMap((g) => g.items)
    // 每个 id 都含有的字符串没有，改用「逐个 id 查得到」来证明无上限：
    // 取一个能命中最多项的公共子串，断言返回数 = 实际匹配数
    const needle = 'o'
    const expected = all.filter((item) =>
      [item.id, item.label['en-US'], item.label['zh-CN'], item.title['en-US'], item.title['zh-CN']]
        .some((value) => value.toLowerCase().includes(needle))
    ).length
    expect(expected).toBeGreaterThan(8)
    expect(ids(needle).length).toBe(expected)
  })
})
