import { describe, it, expect } from 'vitest';
import { encodeMap, parseMap, rleEncode, rleDecode } from '../mapFormat';
import type { TileMapData } from '../IsometricTileMap';

const sample: TileMapData = {
    width: 3,
    height: 2,
    tiles: [0, 0, 0, 1, 1, 2],
    blocked: [2],
    tall: { 3: { height: 1.5, ground: 0 } }
};

describe('RLE', () => {
    it('кодирует серии одинаковых id', () => {
        expect(rleEncode([0, 0, 0, 1, 1, 2])).toEqual([
            [0, 3],
            [1, 2],
            [2, 1]
        ]);
    });

    it('декодирует обратно', () => {
        expect(rleDecode([[0, 3], [1, 2], [2, 1]])).toEqual([0, 0, 0, 1, 1, 2]);
    });

    it('пустой массив', () => {
        expect(rleEncode([])).toEqual([]);
        expect(rleDecode([])).toEqual([]);
    });
});

describe('encodeMap/parseMap', () => {
    it('roundtrip с RLE сохраняет карту', () => {
        const file = encodeMap(sample);
        expect(file.format).toBe('rpg-map');
        expect(file.encoding).toBe('rle');
        const parsed = parseMap(JSON.parse(JSON.stringify(file)));
        expect(parsed).toEqual(sample);
    });

    it('roundtrip с raw', () => {
        const file = encodeMap(sample, 'raw');
        expect(file.tiles).toEqual([0, 0, 0, 1, 1, 2]);
        const parsed = parseMap(file);
        expect(parsed.tiles).toEqual(sample.tiles);
    });

    it('RLE реально сжимает однородную карту', () => {
        const big: TileMapData = { ...sample, width: 28, height: 28, tiles: new Array(784).fill(0) };
        const file = encodeMap(big);
        expect(JSON.stringify(file.tiles).length).toBeLessThan(20);
    });

    it('roundtrip сохраняет пропы с footprint', () => {
        const withProps: TileMapData = {
            ...sample,
            props: [{ id: 7, x: 0, y: 0, w: 2, h: 2, ground: 1, height: 3 }]
        };
        const parsed = parseMap(JSON.parse(JSON.stringify(encodeMap(withProps))));
        expect(parsed.props).toEqual(withProps.props);
    });

    it('roundtrip сохраняет variants (строковые ключи файла -> числовые)', () => {
        const withVariants: TileMapData = {
            ...sample,
            variants: { 0: [0, 5, 9], 12: [12] }
        };
        const parsed = parseMap(JSON.parse(JSON.stringify(encodeMap(withVariants))));
        expect(parsed.variants).toEqual(withVariants.variants);
    });

    it('roundtrip сохраняет decor', () => {
        const withDecor: TileMapData = {
            ...sample,
            decor: [{ id: 7, x: 1, y: 0, dx: 2 }, { id: 9, x: 2, y: 1 }]
        };
        const parsed = parseMap(JSON.parse(JSON.stringify(encodeMap(withDecor))));
        expect(parsed.decor).toEqual(withDecor.decor);
        // без декора поле опускается (старые карты)
        expect(parseMap(encodeMap(sample)).decor).toBeUndefined();
    });

    it('parseMap отвергает decor вне границ карты и не с целыми числами', () => {
        // карта 3×2: тайл (3, 0) вне карты по X
        expect(() => parseMap({ ...encodeMap(sample), decor: [{ id: 7, x: 3, y: 0 }] })).toThrow(/границ/);
        expect(() => parseMap({ ...encodeMap(sample), decor: [{ id: 7.5, x: 0, y: 0 }] })).toThrow(/целыми/);
        expect(() => parseMap({ ...encodeMap(sample), decor: [{ id: 7, x: 0, y: 0, dx: 'a' }] })).toThrow(/числами/);
        expect(() => parseMap({ ...encodeMap(sample), decor: [] })).not.toThrow();
    });

    it('parseMap отвергает пустой/нечисловой variants-массив', () => {
        expect(() => parseMap({ ...encodeMap(sample), variants: { 0: [] } })).toThrow(/variants/);
        expect(() => parseMap({ ...encodeMap(sample), variants: { 0: [1.5] } })).toThrow(/variants/);
        expect(() => parseMap({ ...encodeMap(sample), variants: { 0: ['a'] } })).toThrow(/variants/);
        // отсутствует/пустая таблица — ок (старые карты)
        expect(parseMap(encodeMap(sample)).variants).toBeUndefined();
        expect(parseMap({ ...encodeMap(sample), variants: {} }).variants).toBeUndefined();
    });

    it('parseMap отвергает проп за границей карты', () => {
        // карта 3×2, footprint 2×2 от (2, 1) вылезает вправо и вниз
        const file = encodeMap({ ...sample, props: [{ id: 7, x: 2, y: 1, w: 2, h: 2 }] });
        expect(() => parseMap(file)).toThrow(/границ/);
    });

    it('parseMap отвергает дробный/нулевой footprint', () => {
        const bad = (props: unknown) =>
            parseMap({ ...encodeMap(sample), props });
        expect(() => bad([{ id: 7, x: 0, y: 0, w: 1.5, h: 1 }])).toThrow(/w\/h/);
        expect(() => bad([{ id: 7, x: 0, y: 0, w: 0, h: 1 }])).toThrow(/w\/h/);
    });

    it('parseMap отвергает высоту пропа в пикселях', () => {
        const file = encodeMap({ ...sample, props: [{ id: 7, x: 0, y: 0, height: 96 }] });
        expect(() => parseMap(file)).toThrow(/пикселях/);
    });

    it('parseMap отвергает не-объект', () => {
        expect(() => parseMap('nope')).toThrow();
        expect(() => parseMap(null)).toThrow();
    });

    it('parseMap отвергает высоту в пикселях (tall.height > 16)', () => {
        expect(() => parseMap({ ...encodeMap(sample), tall: { 3: { height: 48, ground: 0 } } })).toThrow(/пикселях/);
    });

    it('parseMap отвергает чужой format/версию', () => {
        expect(() => parseMap({ ...encodeMap(sample), format: 'other' })).toThrow(/format/);
        expect(() => parseMap({ ...encodeMap(sample), version: 99 })).toThrow(/версия|версию|version/);
    });

    it('parseMap отвергает неверное число тайлов', () => {
        const file = encodeMap(sample, 'raw');
        (file as { tiles: number[] }).tiles = [0, 1, 2];
        expect(() => parseMap(file)).toThrow(/784|тайлов|6/);
    });
});