import { describe, expect, it } from 'vitest';
import { CloneObject } from './CloneObject';
import { ConvertSeconds } from './ConvertSeconds';
import { LocalizeShortNumber } from './LocalizeShortNumber';
import { GetWiredTimeLocale } from '../wired/GetWiredTimeLocale';
import { WiredDateToString } from '../wired/WiredDateToString';
import { getPrefixFontStyle, parsePrefixColors, PRESET_PREFIX_FONTS } from './PrefixUtils';

describe('ConvertSeconds', () =>
{
    it('formats zero seconds as the dd:hh:mm:ss zero string', () =>
    {
        expect(ConvertSeconds(0)).toBe('00:00:00:00');
    });

    it('formats one minute correctly', () =>
    {
        expect(ConvertSeconds(60)).toBe('00:00:01:00');
    });

    it('formats one hour correctly', () =>
    {
        expect(ConvertSeconds(3600)).toBe('00:01:00:00');
    });

    it('formats one day correctly', () =>
    {
        expect(ConvertSeconds(86400)).toBe('01:00:00:00');
    });

    it('formats a mixed value (1d 2h 3m 4s)', () =>
    {
        expect(ConvertSeconds(86400 + 2 * 3600 + 3 * 60 + 4)).toBe('01:02:03:04');
    });

    it('pads single-digit components with a leading zero', () =>
    {
        expect(ConvertSeconds(9)).toBe('00:00:00:09');
    });
});

describe('LocalizeShortNumber', () =>
{
    it('returns "0" for zero, null, undefined, and NaN', () =>
    {
        expect(LocalizeShortNumber(0)).toBe('0');
        expect(LocalizeShortNumber(NaN)).toBe('0');
        expect(LocalizeShortNumber(null)).toBe('0');
        expect(LocalizeShortNumber(undefined as unknown as number)).toBe('0');
    });

    it('keeps numbers safely under 1000 unchanged (returns as-is)', () =>
    {
        expect(LocalizeShortNumber(42)).toBe('42');
        // Anything that rounds to >= 1.0K (i.e. >= 950) crosses into the K bucket
        expect(LocalizeShortNumber(949)).toBe('949');
    });

    it('rounds 950..999 up into the K bucket (documented quirk)', () =>
    {
        expect(LocalizeShortNumber(950)).toBe('1K');
        expect(LocalizeShortNumber(999)).toBe('1K');
    });

    it('uses K for thousands', () =>
    {
        expect(LocalizeShortNumber(1500)).toBe('1.5K');
        expect(LocalizeShortNumber(12_345)).toBe('12.3K');
    });

    it('uses M for millions', () =>
    {
        expect(LocalizeShortNumber(2_500_000)).toBe('2.5M');
    });

    it('uses B for billions', () =>
    {
        expect(LocalizeShortNumber(3_700_000_000)).toBe('3.7B');
    });

    it('preserves the sign for negative values', () =>
    {
        expect(LocalizeShortNumber(-1500)).toBe('-1.5K');
        expect(LocalizeShortNumber(-2_500_000)).toBe('-2.5M');
    });
});

describe('CloneObject', () =>
{
    it('returns primitives unchanged', () =>
    {
        expect(CloneObject(42)).toBe(42);
        expect(CloneObject('hello')).toBe('hello');
        expect(CloneObject(null)).toBe(null);
        expect(CloneObject(undefined)).toBe(undefined);
    });

    it('returns a new object instance for object inputs', () =>
    {
        const original = { a: 1, b: 'two' };
        const copy = CloneObject(original);

        expect(copy).not.toBe(original);
        expect(copy).toEqual(original);
    });

    it('preserves enumerable own keys', () =>
    {
        const original = { x: 1, y: 2, z: 3 };
        const copy = CloneObject(original);

        expect(copy.x).toBe(1);
        expect(copy.y).toBe(2);
        expect(copy.z).toBe(3);
    });
});

describe('GetWiredTimeLocale', () =>
{
    // The renderer encodes time as `value = seconds * 2` so even values
    // are whole seconds, odd values are half-seconds.

    it('returns "0" for value 0', () =>
    {
        expect(GetWiredTimeLocale(0)).toBe('0');
    });

    it('returns whole seconds for even values', () =>
    {
        expect(GetWiredTimeLocale(2)).toBe('1');
        expect(GetWiredTimeLocale(10)).toBe('5');
        expect(GetWiredTimeLocale(60)).toBe('30');
    });

    it('returns half-second formatting for odd values', () =>
    {
        expect(GetWiredTimeLocale(1)).toBe('0.5');
        expect(GetWiredTimeLocale(3)).toBe('1.5');
        expect(GetWiredTimeLocale(11)).toBe('5.5');
    });
});

describe('WiredDateToString', () =>
{
    it('zero-pads single-digit month / day / hour / minute', () =>
    {
        const d = new Date(2024, 0, 5, 7, 9); // Jan 5, 2024, 07:09
        expect(WiredDateToString(d)).toBe('2024/01/05 07:09');
    });

    it('formats two-digit values without extra padding', () =>
    {
        const d = new Date(2024, 11, 31, 23, 59); // Dec 31, 2024, 23:59
        expect(WiredDateToString(d)).toBe('2024/12/31 23:59');
    });
});

describe('PrefixUtils.parsePrefixColors', () =>
{
    it('returns an empty array when text or colors are empty', () =>
    {
        expect(parsePrefixColors('', '#fff')).toEqual([]);
        expect(parsePrefixColors('abc', '')).toEqual([]);
    });

    it('maps each text character to the nth color', () =>
    {
        expect(parsePrefixColors('ab', '#f00,#0f0')).toEqual([ '#f00', '#0f0' ]);
    });

    it('reuses the last color when the text is longer than the color list', () =>
    {
        expect(parsePrefixColors('abcd', '#f00,#0f0')).toEqual([ '#f00', '#0f0', '#0f0', '#0f0' ]);
    });
});

describe('PrefixUtils.getPrefixFontStyle', () =>
{
    it('returns an empty object for the default (empty) font id', () =>
    {
        expect(getPrefixFontStyle('')).toEqual({});
    });

    it('returns a fontFamily for a known preset', () =>
    {
        const out = getPrefixFontStyle('pixel');
        expect(out.fontFamily).toBe(PRESET_PREFIX_FONTS.find(p => p.id === 'pixel')?.family);
    });

    it('returns an empty object for an unknown font id', () =>
    {
        expect(getPrefixFontStyle('does-not-exist')).toEqual({});
    });
});
