import {
  getListColor,
  listPaletteSize,
  reconcileListColorIndices,
} from 'src/lib/lists/listColors'

const HEX = /^#[0-9a-f]{6}$/

describe('listColors palette', () => {
  test('palette is non-empty and capped at 96', () => {
    const size = listPaletteSize()
    expect(size).toBeGreaterThan(0)
    expect(size).toBeLessThanOrEqual(96)
  })

  test('getListColor returns a valid #RRGGBB hex for assigned indices', () => {
    const assignments = reconcileListColorIndices(['a', 'b', 'c'], {})
    for (const id of ['a', 'b', 'c']) {
      expect(getListColor(id, assignments)).toMatch(HEX)
    }
  })

  test('fallback colour is a valid, deterministic hex for unassigned lists', () => {
    const first = getListColor('unassigned-list', {})
    const second = getListColor('unassigned-list', {})
    expect(first).toMatch(HEX)
    expect(first).toBe(second)
    // Different ids generally hash to different fallback hues.
    expect(getListColor('another-list', {})).toMatch(HEX)
  })
})

describe('reconcileListColorIndices', () => {
  test('empty input yields empty output, stable across calls', () => {
    expect(reconcileListColorIndices([], {})).toEqual({})
    const once = reconcileListColorIndices([], {})
    const twice = reconcileListColorIndices([], once)
    expect(twice).toEqual({})
  })

  test('assigns a distinct palette index to each list', () => {
    const assignments = reconcileListColorIndices(['1', '2', '3', '4', '5'], {})
    const indices = Object.values(assignments)
    expect(indices).toHaveLength(5)
    expect(new Set(indices).size).toBe(5)
    for (const idx of indices) {
      expect(idx).toBeGreaterThanOrEqual(0)
      expect(idx).toBeLessThan(listPaletteSize())
    }
  })

  test('is deterministic — same inputs produce same assignments', () => {
    const a = reconcileListColorIndices(['x', 'y', 'z'], {})
    const b = reconcileListColorIndices(['x', 'y', 'z'], {})
    expect(a).toEqual(b)
  })

  test('retains existing assignments for lists that still exist', () => {
    const initial = reconcileListColorIndices(['1', '2', '3'], {})
    // Add two new lists; the three originals must keep their indices.
    const grown = reconcileListColorIndices(['1', '2', '3', '4', '5'], initial)
    expect(grown['1']).toBe(initial['1'])
    expect(grown['2']).toBe(initial['2'])
    expect(grown['3']).toBe(initial['3'])
    expect(grown['4']).toBeDefined()
    expect(grown['5']).toBeDefined()
    // No collisions across the grown set.
    expect(new Set(Object.values(grown)).size).toBe(5)
  })

  test('drops assignments for lists that no longer exist', () => {
    const initial = reconcileListColorIndices(['1', '2', '3'], {})
    const shrunk = reconcileListColorIndices(['1'], initial)
    expect(Object.keys(shrunk)).toEqual(['1'])
    expect(shrunk['1']).toBe(initial['1'])
  })

  test('drops out-of-range stored indices but keeps the list', () => {
    const reconciled = reconcileListColorIndices(['1'], { '1': 99999 })
    // The bogus index is dropped, then '1' is re-assigned a valid one.
    expect(reconciled['1']).toBeGreaterThanOrEqual(0)
    expect(reconciled['1']).toBeLessThan(listPaletteSize())
    expect(reconciled['1']).not.toBe(99999)
  })

  test('newly added list takes an index distinct from the existing set', () => {
    // Farthest-first means the 6th list must not collide with the first five.
    const five = reconcileListColorIndices(['a', 'b', 'c', 'd', 'e'], {})
    const six = reconcileListColorIndices(['a', 'b', 'c', 'd', 'e', 'f'], five)
    expect(six['f']).toBeDefined()
    expect(Object.values(five)).not.toContain(six['f'])
  })
})
