import { describe, expect, it } from 'vitest'
import {
  difficultyLevels,
  parseWordlist,
  parseWordlistLine,
  pickWords,
  wordKeyOf,
  type Rng,
  type Word,
} from '../logic/wordlist'

function mulberry32(seed: number): Rng {
  let a = seed
  return {
    next() {
      a |= 0
      a = (a + 0x6d2b79f5) | 0
      let t = a
      t = Math.imul(t ^ (t >>> 15), t | 1)
      t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296
    },
  }
}

describe('parseWordlistLine', () => {
  it('parses a valid easy line', () => {
    expect(parseWordlistLine('easy - run - 跑')).toEqual({
      difficulty: 'easy',
      english: 'run',
      chinese: '跑',
    })
  })

  it('parses medium / hard', () => {
    expect(parseWordlistLine('medium - hop - 单脚跳')?.difficulty).toBe('medium')
    expect(parseWordlistLine('hard - perambulate - 漫步')?.difficulty).toBe('hard')
  })

  it('rejects empty / whitespace lines', () => {
    expect(parseWordlistLine('')).toBeNull()
    expect(parseWordlistLine('   ')).toBeNull()
  })

  it('rejects wrong field count', () => {
    expect(parseWordlistLine('easy - run')).toBeNull()
    expect(parseWordlistLine('easy - run - 跑 - extra')).toBeNull()
  })

  it('rejects unknown difficulty', () => {
    expect(parseWordlistLine('insane - run - 跑')).toBeNull()
  })

  it('rejects empty english/chinese', () => {
    expect(parseWordlistLine('easy -  - 跑')).toBeNull()
    expect(parseWordlistLine('easy - run - ')).toBeNull()
  })
})

describe('parseWordlist', () => {
  it('parses multi-line text and skips bad lines', () => {
    const text = ['easy - run - 跑', '', 'invalid line', 'medium - hop - 单脚跳', 'easy - walk - 走'].join('\n')
    const out = parseWordlist(text)
    expect(out).toHaveLength(3)
    expect(out.map((w) => w.english)).toEqual(['run', 'hop', 'walk'])
  })

  it('handles CRLF line endings', () => {
    const text = 'easy - run - 跑\r\neasy - walk - 走\r\n'
    expect(parseWordlist(text)).toHaveLength(2)
  })
})

describe('difficultyLevels', () => {
  it('1 = easy only', () => {
    expect(difficultyLevels(1)).toEqual(['easy'])
  })
  it('2 = easy + medium', () => {
    expect(difficultyLevels(2)).toEqual(['easy', 'medium'])
  })
  it('3 = all three', () => {
    expect(difficultyLevels(3)).toEqual(['easy', 'medium', 'hard'])
  })
})

describe('pickWords', () => {
  const pool: Word[] = [
    { difficulty: 'easy', english: 'run', chinese: '跑' },
    { difficulty: 'easy', english: 'walk', chinese: '走' },
    { difficulty: 'easy', english: 'jump', chinese: '跳' },
    { difficulty: 'medium', english: 'hop', chinese: '单脚跳' },
    { difficulty: 'hard', english: 'perambulate', chinese: '漫步' },
  ]

  it('respects difficulty level filter', () => {
    const out = pickWords(pool, 1, 10, new Set(), mulberry32(1))
    expect(out.every((w) => w.difficulty === 'easy')).toBe(true)
    expect(out).toHaveLength(3)
  })

  it('includes easy + medium for difficulty 2', () => {
    const out = pickWords(pool, 2, 10, new Set(), mulberry32(1))
    expect(out).toHaveLength(4)
    expect(out.some((w) => w.difficulty === 'hard')).toBe(false)
  })

  it('prefers unseen words', () => {
    const seen = new Set(['walk|走', 'jump|跳'])
    const out = pickWords(pool, 1, 1, seen, mulberry32(1))
    // 唯一未见过的 easy 词是 'run'
    expect(out).toHaveLength(1)
    expect(out[0].english).toBe('run')
  })

  it('falls back to seen words when unseen pool runs out', () => {
    const seen = new Set(['run|跑', 'walk|走', 'jump|跳'])
    const out = pickWords(pool, 1, 3, seen, mulberry32(1))
    expect(out).toHaveLength(3) // all from seen since no unseen left
  })

  it('returns at most `count` words', () => {
    const out = pickWords(pool, 3, 2, new Set(), mulberry32(1))
    expect(out).toHaveLength(2)
  })

  it('handles empty pool', () => {
    const out = pickWords([], 2, 10, new Set(), mulberry32(1))
    expect(out).toEqual([])
  })

  it('is deterministic given a deterministic rng', () => {
    const a = pickWords(pool, 3, 3, new Set(), mulberry32(7))
    const b = pickWords(pool, 3, 3, new Set(), mulberry32(7))
    expect(a).toEqual(b)
  })
})

describe('wordKeyOf', () => {
  it('produces stable english|chinese key', () => {
    expect(wordKeyOf({ difficulty: 'easy', english: 'run', chinese: '跑' })).toBe('run|跑')
  })
})
