/**
 * Unit tests для catalog list-view pure helpers (parseScopeFilter +
 * matchesQuery + groupByBundle).
 *
 * См. design_handoff_dictionary_catalog/ для полной spec'и (List view D).
 */
import { describe, it, expect } from 'vitest'
import type { DictionaryDefinition } from '@/api/client'
import {
  parseScopeFilter,
  matchesQuery,
  groupByBundle,
  uniqueRefBy,
} from './dictionaries.index'
import type { SchemaDependent } from '@/api/client'

const mkDict = (overrides: Partial<DictionaryDefinition>): DictionaryDefinition => ({
  id: 'id-' + (overrides.name ?? 'x'),
  name: overrides.name ?? 'test',
  displayName: overrides.displayName,
  description: overrides.description,
  scope: overrides.scope ?? 'PUBLIC',
  schemaVersion: overrides.schemaVersion ?? '1.0.0',
  bundle: overrides.bundle ?? 'cuod',
  supportedLocales: overrides.supportedLocales ?? ['ru-RU'],
  defaultLocale: overrides.defaultLocale ?? 'ru-RU',
  redisProjectionEnabled: overrides.redisProjectionEnabled ?? false,
  approvalRequired: overrides.approvalRequired ?? false,
  approvalMinRole: overrides.approvalMinRole ?? null,
  recordCount: overrides.recordCount,
  createdAt: overrides.createdAt ?? '2026-01-01T00:00:00Z',
  updatedAt: overrides.updatedAt ?? '2026-01-01T00:00:00Z',
})

describe('parseScopeFilter', () => {
  it('returns empty Set for undefined / empty input', () => {
    expect(parseScopeFilter(undefined).size).toBe(0)
    expect(parseScopeFilter('').size).toBe(0)
  })

  it('parses single valid scope', () => {
    const out = parseScopeFilter('PUBLIC')
    expect(out.size).toBe(1)
    expect(out.has('PUBLIC')).toBe(true)
  })

  it('parses CSV with multiple scopes', () => {
    const out = parseScopeFilter('PUBLIC,INTERNAL,RESTRICTED')
    expect(out.size).toBe(3)
  })

  it('case-insensitive — lowercase + mixed case normalized to UPPER', () => {
    const out = parseScopeFilter('public,Internal')
    expect(out.has('PUBLIC')).toBe(true)
    expect(out.has('INTERNAL')).toBe(true)
  })

  it('skips invalid scopes (bookmark protection)', () => {
    const out = parseScopeFilter('FOO,PUBLIC,BAR')
    expect(out.size).toBe(1)
    expect(out.has('PUBLIC')).toBe(true)
  })

  it('deduplicates repeated scopes via Set', () => {
    const out = parseScopeFilter('PUBLIC,PUBLIC,public')
    expect(out.size).toBe(1)
  })

  it('handles whitespace', () => {
    const out = parseScopeFilter(' PUBLIC , INTERNAL ')
    expect(out.size).toBe(2)
  })
})

describe('matchesQuery', () => {
  const dict = mkDict({
    name: 'spacecraft',
    displayName: 'Космические аппараты',
    description: 'Реестр спутников',
  })

  it('empty query matches anything', () => {
    expect(matchesQuery(dict, '')).toBe(true)
  })

  it('matches name substring', () => {
    expect(matchesQuery(dict, 'space')).toBe(true)
  })

  it('matches displayName (RU)', () => {
    expect(matchesQuery(dict, 'космич')).toBe(true)
  })

  it('matches description', () => {
    expect(matchesQuery(dict, 'спутник')).toBe(true)
  })

  it('returns false on miss', () => {
    expect(matchesQuery(dict, 'абракадабра')).toBe(false)
  })
})

describe('groupByBundle', () => {
  it('groups dicts by bundle', () => {
    const list = [
      mkDict({ name: 'a', bundle: 'cuod' }),
      mkDict({ name: 'b', bundle: 'geo' }),
      mkDict({ name: 'c', bundle: 'cuod' }),
    ]
    const out = groupByBundle(list)
    expect(out.size).toBe(2)
    expect(out.get('cuod')).toHaveLength(2)
    expect(out.get('geo')).toHaveLength(1)
  })

  it('sorts dicts within group by displayName/name', () => {
    const list = [
      mkDict({ name: 'zebra', displayName: 'Зебра', bundle: 'cuod' }),
      mkDict({ name: 'alpha', displayName: 'Альфа', bundle: 'cuod' }),
      mkDict({ name: 'beta', displayName: 'Бета', bundle: 'cuod' }),
    ]
    const out = groupByBundle(list)
    expect(out.get('cuod')!.map((d) => d.name)).toEqual(['alpha', 'beta', 'zebra'])
  })

  it('empty input returns empty Map', () => {
    expect(groupByBundle([]).size).toBe(0)
  })

  it('falls back to "default" bundle если bundle пуст', () => {
    const list = [mkDict({ name: 'x', bundle: '' })]
    const out = groupByBundle(list)
    expect(out.has('default')).toBe(true)
  })
})

describe('uniqueRefBy', () => {
  const mkDep = (sourceDict: string, sourceField = 'fk'): SchemaDependent => ({
    sourceDict,
    sourceDisplayName: `${sourceDict}-display`,
    sourceField,
    targetField: 'id',
    onClose: 'BLOCK',
    activeRecordsInSourceDict: 0,
  })

  it('empty input returns empty array', () => {
    expect(uniqueRefBy([])).toEqual([])
  })

  it('preserves single entry as-is', () => {
    const out = uniqueRefBy([mkDep('missions')])
    expect(out).toHaveLength(1)
    expect(out[0].sourceDict).toBe('missions')
  })

  it('dedups by sourceDict — multiple fields → один chip', () => {
    const out = uniqueRefBy([
      mkDep('satellites', 'type_id'),
      mkDep('satellites', 'mission_id'),
      mkDep('missions', 'fk'),
    ])
    expect(out.map((d) => d.sourceDict)).toEqual(['satellites', 'missions'])
  })

  it('preserves first-occurrence displayName', () => {
    const out = uniqueRefBy([
      { ...mkDep('x'), sourceDisplayName: 'First' },
      { ...mkDep('x'), sourceDisplayName: 'Second' },
    ])
    expect(out).toHaveLength(1)
    expect(out[0].sourceDisplayName).toBe('First')
  })
})
