import {
  MAX_BODY_BYTES,
  TAPESTRY_LETTERS,
  isLetter,
  isValidBody,
} from 'src/lib/tapestry/grammar'

// Pinned literal of the alphabet as it ships. A typo or accidental
// reorder in `grammar.ts` will fail this test before it reaches the
// server contract — the alphabet is frozen once shipped.
const EXPECTED_ALPHABET = [
  'P',
  'C',
  'M',
  'V',
  'L',
  'R',
  'B',
  'F',
  'D',
  'N',
  'E',
  'T',
  's',
  'v',
  'e',
  'u',
  'd',
  'x',
] as const

describe('tapestry grammar — alphabet pin', () => {
  test('TAPESTRY_LETTERS keys match the pinned literal exactly', () => {
    expect(Object.keys(TAPESTRY_LETTERS)).toEqual(EXPECTED_ALPHABET)
  })

  test('every letter in the pinned literal is accepted by isLetter', () => {
    for (const letter of EXPECTED_ALPHABET) {
      expect(isLetter(letter)).toBe(true)
    }
  })

  test('every letter has a non-empty short label', () => {
    for (const letter of EXPECTED_ALPHABET) {
      expect(typeof TAPESTRY_LETTERS[letter]).toBe('string')
      expect(TAPESTRY_LETTERS[letter].length).toBeGreaterThan(0)
    }
  })
})

describe('isLetter', () => {
  test('rejects empty string', () => {
    expect(isLetter('')).toBe(false)
  })

  test('rejects multi-character strings', () => {
    expect(isLetter('PP')).toBe(false)
    expect(isLetter('LR')).toBe(false)
  })

  test('rejects reserved letters not yet in the grammar', () => {
    // `n` is reserved server-side for Updates-tab focus, deferred until
    // a coordinated client+server commit. Do not accept it yet.
    expect(isLetter('n')).toBe(false)
    // Letters not assigned at all.
    expect(isLetter('Z')).toBe(false)
    expect(isLetter('a')).toBe(false)
    expect(isLetter('z')).toBe(false)
  })

  test('rejects digits, whitespace, and punctuation', () => {
    expect(isLetter('0')).toBe(false)
    expect(isLetter('1')).toBe(false)
    expect(isLetter(' ')).toBe(false)
    expect(isLetter('\n')).toBe(false)
    expect(isLetter('\t')).toBe(false)
    expect(isLetter('-')).toBe(false)
    expect(isLetter(',')).toBe(false)
  })
})

describe('isValidBody', () => {
  test('accepts a single valid letter', () => {
    expect(isValidBody('L')).toBe(true)
  })

  test('accepts a body composed entirely of valid letters', () => {
    expect(isValidBody('LLLLPPCC')).toBe(true)
    expect(isValidBody('PCMVLRBFDNETsveudx')).toBe(true)
  })

  test('rejects empty body', () => {
    expect(isValidBody('')).toBe(false)
  })

  test('accepts the boundary length (256 bytes)', () => {
    const body = 'L'.repeat(MAX_BODY_BYTES)
    expect(isValidBody(body)).toBe(true)
  })

  test('rejects bodies one byte over the cap (257 bytes)', () => {
    const body = 'L'.repeat(MAX_BODY_BYTES + 1)
    expect(isValidBody(body)).toBe(false)
  })

  test('rejects bodies containing whitespace', () => {
    expect(isValidBody('L L')).toBe(false)
    expect(isValidBody('LL\n')).toBe(false)
    expect(isValidBody(' L')).toBe(false)
  })

  test('rejects bodies containing non-ASCII bytes', () => {
    expect(isValidBody('Lé')).toBe(false)
    expect(isValidBody('日')).toBe(false)
    expect(isValidBody('L L')).toBe(false)
  })

  test('rejects bodies containing reserved letters', () => {
    expect(isValidBody('Ln')).toBe(false)
    expect(isValidBody('LZ')).toBe(false)
    expect(isValidBody('n')).toBe(false)
  })

  test('rejects bodies containing digits', () => {
    expect(isValidBody('L1')).toBe(false)
    expect(isValidBody('1L')).toBe(false)
  })
})
