import { describe, it, expect } from 'vitest'
import {
  ADJECTIVES,
  TREES,
  NAMESPACE_SIZE,
  generateWorkleafName,
} from './nameGenerator.js'

describe('ADJECTIVES + TREES word lists', () => {
  it('contains exactly 20 adjectives and 20 trees', () => {
    expect(ADJECTIVES.length).toBe(20)
    expect(TREES.length).toBe(20)
  })

  it('uses only lowercase single-word tokens (hyphens allowed)', () => {
    for (const list of [ADJECTIVES, TREES]) {
      for (const word of list) {
        expect(word).toMatch(/^[a-z]+(-[a-z]+)?$/)
      }
    }
  })

  it('has no duplicates within each list', () => {
    expect(new Set(ADJECTIVES).size).toBe(ADJECTIVES.length)
    expect(new Set(TREES).size).toBe(TREES.length)
  })

  it('NAMESPACE_SIZE matches the product', () => {
    expect(NAMESPACE_SIZE).toBe(ADJECTIVES.length * TREES.length)
  })
})

describe('generateWorkleafName', () => {
  it('produces an <adjective>-<tree> name from the word lists', () => {
    const name = generateWorkleafName(() => false)
    expect(name).toMatch(/^[a-z]+(-[a-z]+)?-[a-z]+(-[a-z]+)?$/)
    const [adj, ...rest] = name.split('-')
    // Either the adjective is a single token or the tree contains a
    // hyphen — we have no multi-token adjectives currently, so check by
    // walking the suffix until the rest matches a tree.
    let tree = rest.join('-')
    let prefix = adj
    // Try every split point (e.g. "foo-bar-baz" could be adj="foo",
    // tree="bar-baz" or adj="foo-bar", tree="baz").
    const parts = name.split('-')
    let matched = false
    for (let i = 1; i < parts.length; i++) {
      const a = parts.slice(0, i).join('-')
      const t = parts.slice(i).join('-')
      if (ADJECTIVES.includes(a) && TREES.includes(t)) {
        matched = true
        prefix = a
        tree = t
        break
      }
    }
    expect(matched).toBe(true)
    expect(ADJECTIVES).toContain(prefix)
    expect(TREES).toContain(tree)
  })

  it('skips names reported as taken', () => {
    // Mark a specific candidate as taken; with a deterministic random
    // source that would have returned it first, we should get a
    // different name back.
    const sequence = [
      // First pick: ADJECTIVES[0] + TREES[0] — "bright-baobab" — taken
      0, 0,
      // Second pick: ADJECTIVES[1] + TREES[1] — free
      1 / ADJECTIVES.length, 1 / TREES.length,
    ]
    let i = 0
    const random = () => sequence[i++] ?? 0

    const first = `${ADJECTIVES[0]}-${TREES[0]}`
    const name = generateWorkleafName((n) => n === first, { random })
    expect(name).not.toBe(first)
  })

  it('returns null when the entire namespace is exhausted', () => {
    const name = generateWorkleafName(() => true, { maxRandomAttempts: 5 })
    expect(name).toBeNull()
  })

  it('falls back to a sweep when random attempts are exhausted but a slot remains', () => {
    // Block every name except a single deterministic survivor.
    const survivor = `${ADJECTIVES[7]}-${TREES[13]}`
    const name = generateWorkleafName((n) => n !== survivor, { maxRandomAttempts: 3 })
    expect(name).toBe(survivor)
  })

  it('returns the random pick when no names are taken', () => {
    // Force the random source to return a specific pair on the first
    // attempt; the predicate accepts everything, so we should get it.
    const random = () => 0
    const name = generateWorkleafName(() => false, { random })
    expect(name).toBe(`${ADJECTIVES[0]}-${TREES[0]}`)
  })
})
