import {encodeShortId, decodeShortId, deserializeShortId, serializeShortId} from '../short-id.js'

test('encodes the worked example from docs/handles-and-shortids.md', () => {
    const token = encodeShortId([0, 7, 42])
    expect(token).toBe('~DyU')
})

test('round-trips every kind as a flat [kindIndex, ...fieldValues] list', () => {
    const cases = [
        [0, 7, 42],
        [1, 3, 1000],
        [2, 0],
    ]
    for (const ints of cases) {
        const token = encodeShortId(ints)
        expect(decodeShortId(token)).toEqual(ints)
    }
})

test('round-trips small and large values, including zero', () => {
    const values = [0, 1, 7, 42, 100, 10000, 100000, 123456789]
    for (const value of values) {
        const token = encodeShortId([2, value])
        expect(decodeShortId(token)).toEqual([2, value])
    }
})

test('tokens are URL-safe: only unreserved characters, plus the leading ~', () => {
    const token = encodeShortId([0, 123456, 987654])
    expect(token[0]).toBe('~')
    expect(token.slice(1)).toMatch(/^[A-Za-z0-9\-_]+$/)
})

test('deserializeShortId names a decoded list into a flat shape, serializeShortId reverses it', () => {
    const cases = [
        [[0, 7, 42], {kind: 'item', owner_identity_id: 7, item_local_id: 42}],
        [[1, 3, 1000], {kind: 'storage_location', owner_identity_id: 3, storage_location_id: 1000}],
        [[2, 0], {kind: 'category', category_id: 0}],
        [[3, 2, 9], {kind: 'workflow', owner_identity_id: 2, workflow_id: 9}],
        [[4, 11], {kind: 'group', group_id: 11}],
        [[5, 123], {kind: 'file', file_id: 123}],
    ]
    for (const [ints, named] of cases) {
        expect(deserializeShortId(ints)).toEqual(named)
        expect(serializeShortId(named)).toEqual(ints)
    }
})

test('kinds 3+ round-trip through the escape tag, not just the direct 0-2 range', () => {
    for (const named of [
        {kind: 'workflow', owner_identity_id: 2, workflow_id: 9},
        {kind: 'group', group_id: 11},
        {kind: 'file', file_id: 123},
    ]) {
        const token = encodeShortId(serializeShortId(named))
        expect(deserializeShortId(decodeShortId(token))).toEqual(named)
    }
})

test('serializeShortId then deserializeShortId round-trips through encode/decode', () => {
    const token = encodeShortId(serializeShortId({kind: 'item', owner_identity_id: 7, item_local_id: 42}))
    expect(token).toBe('~DyU')
    expect(deserializeShortId(decodeShortId(token))).toEqual({
        kind: 'item',
        owner_identity_id: 7,
        item_local_id: 42
    })
})

test('rejects a token missing the leading ~', () => {
    expect(() => decodeShortId('DyU')).toThrow()
})

test('rejects an unknown kind index', () => {
    expect(() => encodeShortId([99, 1])).toThrow()
})

test('deserializeShortId is keyed by (kind, fields.length), not kind alone', () => {
    // kind=1 with the arity storage_location actually has (2 fields) is fine...
    expect(deserializeShortId([1, 3, 1000])).toEqual({
        kind: 'storage_location',
        owner_identity_id: 3,
        storage_location_id: 1000
    })
    // ...but kind=1 with a different arity (1 field) isn't a registered schema at all, and must
    // not be silently misread as a truncated storage_location.
    expect(() => deserializeShortId([1, 359])).toThrow()
})

test('rejects an unknown kind name', () => {
    expect(() => serializeShortId({kind: 'not_a_kind'})).toThrow()
})

test('rejects a field count mismatch', () => {
    expect(() => encodeShortId([0, 7])).toThrow()
})

test('rejects a missing field', () => {
    expect(() => serializeShortId({kind: 'item', owner_identity_id: 7})).toThrow()
})

test('rejects a negative field value', () => {
    expect(() => encodeShortId([2, -1])).toThrow()
})
