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]}]
            }
        ]);
    });
});
