import {diff} from './diff';
import {StyleSpecification} from './types.g';
import {describe, test, expect} from 'vitest';

describe('diff', () => {
    test('layers id equal', () => {
        expect(diff({
            layers: [{id: 'a'}]
        } as StyleSpecification, {
            layers: [{id: 'a'}]
        } as StyleSpecification)).toEqual([]);
    });

    test('version not equal', () => {
        expect(diff({
            version: 7,
            layers: [{id: 'a'}]
        } as any as StyleSpecification, {
            version: 8,
            layers: [{id: 'a'}]
        } as StyleSpecification)).toEqual([
            {command: 'setStyle', args: [{version: 8, layers: [{id: 'a'}]}]}
        ]);
    });

    test('add layer at the end', () => {
        expect(diff({
            layers: [{id: 'a'}]
        } as StyleSpecification, {
            layers: [{id: 'a'}, {id: 'b'}]
        } as StyleSpecification)).toEqual([
            {command: 'addLayer', args: [{id: 'b'}, undefined]}
        ]);
    });

    test('add layer at the beginning', () => {
        expect(diff({
            layers: [{id: 'b'}]
        } as StyleSpecification, {
            layers: [{id: 'a'}, {id: 'b'}]
        } as StyleSpecification)).toEqual([
            {command: 'addLayer', args: [{id: 'a'}, 'b']}
        ]);
    });

    test('remove layer', () => {
        expect(diff({
            layers: [{id: 'a'}, {id: 'b', source: 'foo', nested: [1]}]
        } as StyleSpecification, {
            layers: [{id: 'a'}]
        } as StyleSpecification)).toEqual([
            {command: 'removeLayer', args: ['b']}
        ]);
    });

    test('remove and add layer', () => {
        expect(diff({
            layers: [{id: 'a'}, {id: 'b'}]
        } as StyleSpecification, {
            layers: [{id: 'b'}, {id: 'a'}]
        } as StyleSpecification)).toEqual([
            {command: 'removeLayer', args: ['a']},
            {command: 'addLayer', args: [{id: 'a'}, undefined]}
        ]);
    });

    test('set paint property', () => {
        expect(diff({
            layers: [{id: 'a', paint: {foo: 1}}]
        } as any as StyleSpecification, {
            layers: [{id: 'a', paint: {foo: 2}}]
        } as any as StyleSpecification)).toEqual([
            {command: 'setPaintProperty', args: ['a', 'foo', 2, null]}
        ]);
    });

    test('set paint property with light', () => {
        expect(diff({
            layers: [{id: 'a', 'paint.light': {foo: 1}}]
        } as any as StyleSpecification, {
            layers: [{id: 'a', 'paint.light': {foo: 2}}]
        } as any as StyleSpecification)).toEqual([
            {command: 'setPaintProperty', args: ['a', 'foo', 2, 'light']}
        ]);
    });

    test('set paint property with ramp', () => {
        expect(diff({
            layers: [{id: 'a', paint: {foo: {ramp: [1, 2]}}}]
        } as any as StyleSpecification, {
            layers: [{id: 'a', paint: {foo: {ramp: [1]}}}]
        } as any as StyleSpecification)).toEqual([
            {command: 'setPaintProperty', args: ['a', 'foo', {ramp: [1]}, null]}
        ]);
    });

    test('set layout property', () => {
        expect(diff({
            layers: [{id: 'a', layout: {foo: 1}}]
        } as any as StyleSpecification, {
            layers: [{id: 'a', layout: {foo: 2}}]
        } as any as StyleSpecification)).toEqual([
            {command: 'setLayoutProperty', args: ['a', 'foo', 2, null]}
        ]);
    });

    test('set filter', () => {
        expect(diff({
            layers: [{id: 'a', filter: ['==', 'foo', 'bar']}]
        } as StyleSpecification, {
            layers: [{id: 'a', filter: ['==', 'foo', 'baz']}]
        }  as StyleSpecification)).toEqual([
            {command: 'setFilter', args: ['a', ['==', 'foo', 'baz']]}
        ]);
    });

    test('remove source', () => {
        expect(diff({
            sources: {foo: 1}
        } as any as StyleSpecification, {
            sources: {}
        } as StyleSpecification)).toEqual([
            {command: 'removeSource', args: ['foo']}
        ]);
    });

    test('add source', () => {
        expect(diff({
            sources: {}
        } as StyleSpecification, {
            sources: {foo: 1}
        } as any as StyleSpecification)).toEqual([
            {command: 'addSource', args: ['foo', 1]}
        ]);
    });

    test('set goejson source data', () => {
        expect(diff({
            sources: {
                foo: {
                    type: 'geojson',
                    data: {type: 'FeatureCollection', features: []}
                }
            }
        } as any as StyleSpecification, {
            sources: {
                foo: {
                    type: 'geojson',
                    data: {
                        type: 'FeatureCollection',
                        features: [{
                            type: 'Feature',
                            geometry: {type: 'Point', coordinates: [10, 20]}
                        }]
                    }
                }
            }
        } as any as StyleSpecification)).toEqual([
            {command: 'setGeoJSONSourceData', args: ['foo', {
                type: 'FeatureCollection',
                features: [{
                    type: 'Feature',
                    geometry: {type: 'Point', coordinates: [10, 20]}
                }]
            }]}
        ]);
    });

    test('remove and add source', () => {
        expect(diff({
            sources: {
                foo: {
                    type: 'geojson',
                    data: {type: 'FeatureCollection', features: []}
                }
            }
        } as any as StyleSpecification, {
            sources: {
                foo: {
                    type: 'geojson',
                    data: {type: 'FeatureCollection', features: []},
                    cluster: true
                }
            }
        } as any as StyleSpecification)).toEqual([
            {command: 'removeSource', args: ['foo']},
            {command: 'addSource', args: ['foo', {
                type: 'geojson',
                cluster: true,
                data: {type: 'FeatureCollection', features: []}
            }]}
        ]);
    });

    test('remove and add source with clusterRadius', () => {
        expect(diff({
            sources: {
                foo: {
                    type: 'geojson',
                    data: {type: 'FeatureCollection', features: []},
                    cluster: true
                }
            }
        } as any as StyleSpecification, {
            sources: {
                foo: {
                    type: 'geojson',
                    data: {type: 'FeatureCollection', features: []},
                    cluster: true,
                    clusterRadius: 100
                }
            }
        } as any as StyleSpecification)).toEqual([
            {command: 'removeSource', args: ['foo']},
            {command: 'addSource', args: ['foo', {
                type: 'geojson',
                cluster: true,
                clusterRadius: 100,
                data: {type: 'FeatureCollection', features: []}
            }]}
        ]);
    });

    test('remove and add source without clusterRadius', () => {
        expect(diff({
            sources: {
                foo: {
                    type: 'geojson',
                    data: {type: 'FeatureCollection', features: []},
                    cluster: true,
                    clusterRadius: 100
                }
            }
        } as any as StyleSpecification, {
            sources: {
                foo: {
                    type: 'geojson',
                    data: {type: 'FeatureCollection', features: []},
                    cluster: true
                }
            }
        } as any as StyleSpecification)).toEqual([
            {command: 'removeSource', args: ['foo']},
            {command: 'addSource', args: ['foo', {
                type: 'geojson',
                cluster: true,
                data: {type: 'FeatureCollection', features: []}
            }]}
        ]);
    });

    test('global metadata', () => {
        expect(diff({} as StyleSpecification, {
            metadata: {'maplibre:author': 'nobody'}
        } as StyleSpecification)).toEqual([]);
    });

    test('layer metadata', () => {
        expect(diff({
            layers: [{id: 'a', metadata: {'maplibre:group': 'Group Name'}}]
        } as StyleSpecification, {
            layers: [{id: 'a', metadata: {'maplibre:group': 'Another Name'}}]
        } as StyleSpecification)).toEqual([]);
    });

    test('set state', () => {
        expect(diff({
            state: {foo: 1}
        } as any as StyleSpecification, {
            state: {foo: 2}
        } as any as StyleSpecification)).toEqual([
            {command: 'setGlobalState', args: [{foo: 2}]}
        ]);
    });

    test('set center', () => {
        expect(diff({
            center: [0, 0]
        } as StyleSpecification, {
            center: [1, 1]
        } as StyleSpecification)).toEqual([
            {command: 'setCenter', args: [[1, 1]]}
        ]);
    });

    test('set centerAltitude to undefined', () => {
        expect(diff({
            centerAltitude: 1
        } as StyleSpecification, {
        } as StyleSpecification)).toEqual([
            {command: 'setCenterAltitude', args: [undefined]}
        ]);
    });

    test('set centerAltitude', () => {
        expect(diff({
            centerAltitude: 0
        } as StyleSpecification, {
            centerAltitude: 1
        } as StyleSpecification)).toEqual([
            {command: 'setCenterAltitude', args: [1]}
        ]);
    });

    test('set zoom', () => {
        expect(diff({
            zoom: 12
        } as StyleSpecification, {
            zoom: 15
        } as StyleSpecification)).toEqual([
            {command: 'setZoom', args: [15]}
        ]);
    });

    test('set bearing', () => {
        expect(diff({
            bearing: 0
        } as StyleSpecification, {
            bearing: 180
        } as StyleSpecification)).toEqual([
            {command: 'setBearing', args: [180]}
        ]);
    });

    test('set pitch', () => {
        expect(diff({
            pitch: 0
        } as StyleSpecification, {
            pitch: 1
        } as StyleSpecification)).toEqual([
            {command: 'setPitch', args: [1]}
        ]);
    });

    test('set roll to undefined', () => {
        expect(diff({
            roll: 1
        } as StyleSpecification, {
        } as StyleSpecification)).toEqual([
            {command: 'setRoll', args: [undefined]}
        ]);
    });

    test('set roll', () => {
        expect(diff({
            roll: 0
        } as StyleSpecification, {
            roll: 1
        } as StyleSpecification)).toEqual([
            {command: 'setRoll', args: [1]}
        ]);
    });

    test('no changes in light', () => {
        expect(diff({
            light: {
                anchor: 'map',
                color: 'white',
                position: [0, 1, 0],
                intensity: 1
            }
        } as StyleSpecification, {
            light: {
                anchor: 'map',
                color: 'white',
                position: [0, 1, 0],
                intensity: 1
            }
        } as StyleSpecification)).toEqual([
        ]);
    });

    test('set light anchor', () => {
        expect(diff({
            light: {anchor: 'map'}
        } as StyleSpecification, {
            light: {anchor: 'viewport'}
        } as StyleSpecification)).toEqual([
            {command: 'setLight', args: [{'anchor': 'viewport'}]}
        ]);
    });

    test('set light color', () => {
        expect(diff({
            light: {color: 'white'}
        } as StyleSpecification, {
            light: {color: 'red'}
        } as StyleSpecification)).toEqual([
            {command: 'setLight', args: [{'color': 'red'}]}
        ]);
    });

    test('set light position', () => {
        expect(diff({
            light: {position: [0, 1, 0]}
        } as StyleSpecification, {
            light: {position: [1, 0, 0]}
        } as StyleSpecification)).toEqual([
            {command: 'setLight', args: [{'position': [1, 0, 0]}]}
        ]);
    });

    test('set light intensity', () => {
        expect(diff({
            light: {intensity: 1}
        } as StyleSpecification, {
            light: {intensity: 10}
        } as StyleSpecification)).toEqual([
            {command: 'setLight', args: [{'intensity': 10}]}
        ]);
    });

    test('set light anchor and color', () => {
        expect(diff({
            light: {
                anchor: 'map',
                color: 'orange',
                position: [2, 80, 30],
                intensity: 1.0
            }
        } as StyleSpecification, {
            light: {
                anchor: 'map',
                color: 'red',
                position: [1, 40, 30],
                intensity: 1.0
            }
        } as StyleSpecification)).toEqual([
            {command: 'setLight', args: [{
                anchor: 'map',
                color: 'red',
                position: [1, 40, 30],
                intensity: 1.0
            }]}
        ]);
    });

    test('add and remove layer on source change', () => {
        expect(diff({
            layers: [{id: 'a', source: 'source-one'}]
        } as StyleSpecification, {
            layers: [{id: 'a', source: 'source-two'}]
        } as StyleSpecification)).toEqual([
            {command: 'removeLayer', args: ['a']},
            {command: 'addLayer', args: [{id: 'a', source: 'source-two'}, undefined]}
        ]);
    });

    test('add and remove layer on type change', () => {
        expect(diff({
            layers: [{id: 'a', type: 'fill'}]
        } as StyleSpecification, {
            layers: [{id: 'a', type: 'line'}]
        } as StyleSpecification)).toEqual([
            {command: 'removeLayer', args: ['a']},
            {command: 'addLayer', args: [{id: 'a', type: 'line'}, undefined]}
        ]);
    });

    test('add and remove layer on source-layer change', () => {
        expect(diff({
            layers: [{id: 'a', source: 'a', 'source-layer': 'layer-one'}]
        } as StyleSpecification, {
            layers: [{id: 'a', source: 'a', 'source-layer': 'layer-two'}]
        } as StyleSpecification)).toEqual([
            {command: 'removeLayer', args: ['a']},
            {command: 'addLayer', args: [{id: 'a', source: 'a', 'source-layer': 'layer-two'}, undefined]}
        ]);
    });

    test('add and remove layers on different order and type', () => {
        expect(diff({
            layers: [
                {id: 'b'},
                {id: 'c'},
                {id: 'a', type: 'fill'}
            ]
        } as StyleSpecification, {
            layers: [
                {id: 'c'},
                {id: 'a', type: 'line'},
                {id: 'b'}
            ]
        } as StyleSpecification)).toEqual([
            {command: 'removeLayer', args: ['b']},
            {command: 'addLayer', args: [{id: 'b'}, undefined]},
            {command: 'removeLayer', args: ['a']},
            {command: 'addLayer', args: [{id: 'a', type: 'line'}, 'b']}
        ]);
    });

    test('add and remove layer and source on source data change', () => {
        expect(diff({
            sources: {foo: {data: 1}, bar: {}},
            layers: [
                {id: 'a', source: 'bar'},
                {id: 'b', source: 'foo'},
                {id: 'c', source: 'bar'}
            ]
        } as any as StyleSpecification, {
            sources: {foo: {data: 2}, bar: {}},
            layers: [
                {id: 'a', source: 'bar'},
                {id: 'b', source: 'foo'},
                {id: 'c', source: 'bar'}
            ]
        } as any as StyleSpecification)).toEqual([
            {command: 'removeLayer', args: ['b']},
            {command: 'removeSource', args: ['foo']},
            {command: 'addSource', args: ['foo', {data: 2}]},
            {command: 'addLayer', args: [{id: 'b', source: 'foo'}, 'c']}
        ]);
    });

    test('set transition', () => {
        expect(diff({
            sources: {foo: {data: 1}, bar: {}},
            layers: [
                {id: 'a', source: 'bar'}
            ]
        } as any as StyleSpecification, {
            sources: {foo: {data: 1}, bar: {}},
            layers: [
                {id: 'a', source: 'bar'}
            ],
            transition: 'transition'
        } as any as StyleSpecification)).toEqual([
            {command: 'setTransition', args: ['transition']}
        ]);
    });

    test('no sprite change', () => {
        expect(diff({
            sprite: 'a'
        } as StyleSpecification, {
            sprite: 'a'
        } as StyleSpecification)).toEqual([]);
    });

    test('set sprite', () => {
        expect(diff({
            sprite: 'a'
        } as StyleSpecification, {
            sprite: 'b'
        } as StyleSpecification)).toEqual([
            {command: 'setSprite', args: ['b']},
        ]);
    });

    test('set sprite for multiple sprites', () => {
        expect(diff({
            sprite: 'a'
        } as StyleSpecification, {
            sprite: [{'id': 'default', 'url': 'b'}]
        } as StyleSpecification)).toEqual([
            {command: 'setSprite', args: [[{'id': 'default', 'url': 'b'}]]},
        ]);
    });

    test('no glyphs change', () => {
        expect(diff({
            glyphs: 'a'
        } as StyleSpecification, {
            glyphs: 'a'
        } as StyleSpecification)).toEqual([]);
    });

    test('set glyphs', () => {
        expect(diff({
            glyphs: 'a'
        } as StyleSpecification, {
            glyphs: 'b'
        } as StyleSpecification)).toEqual([
            {command: 'setGlyphs', args: ['b']},
        ]);
    });

    test('remove terrain', () => {
        expect(diff({
            terrain: {
                source: 'maplibre-dem',
                exaggeration: 1.5
            }
        } as StyleSpecification, {
        } as StyleSpecification)).toEqual([
            {command: 'setTerrain', args: [undefined]},
        ]);
    });

    test('add terrain', () => {
        expect(diff({
        } as StyleSpecification,
        {
            terrain: {
                source: 'maplibre-dem',
                exaggeration: 1.5
            }
        } as StyleSpecification)).toEqual([
            {command: 'setTerrain', args: [{source: 'maplibre-dem', exaggeration: 1.5}]},
        ]);
    });

    test('set sky', () => {
        expect(diff({
        } as StyleSpecification,
        {
            sky: {
                'fog-color': 'green',
                'fog-ground-blend': 0.2
            }
        } as StyleSpecification)).toEqual([
            {command: 'setSky', args: [{'fog-color': 'green', 'fog-ground-blend': 0.2}]},
        ]);
    });

    test('set projection', () => {
        expect(diff({
        } as StyleSpecification,
        {
            projection: {type: ['vertical-perspective', 'mercator', 0.5]}

        } as StyleSpecification)).toEqual([
            {command: 'setProjection', args: [{type: ['vertical-perspective', 'mercator', 0.5]}]},
        ]);
    });
});
