import {beforeEach, test, expect, vi} from 'vitest';
import {createMap, beforeMapTest, createTerrain} from '../../util/test/util';
import simulate from '../../../test/unit/lib/simulate_interaction';

beforeEach(() => {
    beforeMapTest();
    global.fetch = null;
});

test('setMinZoom', () => {
    const map = createMap({zoom: 5});
    map.setMinZoom(3.5);
    map.setZoom(1);
    expect(map.getZoom()).toBe(3.5);
});

test('unset minZoom', () => {
    const map = createMap({minZoom: 5});
    map.setMinZoom(null);
    map.setZoom(1);
    expect(map.getZoom()).toBe(1);
});

test('getMinZoom', () => {
    const map = createMap({zoom: 0});
    expect(map.getMinZoom()).toBe(-2);
    map.setMinZoom(10);
    expect(map.getMinZoom()).toBe(10);
});

test('ignore minZooms over maxZoom', () => {
    const map = createMap({zoom: 2, maxZoom: 5});
    expect(() => {
        map.setMinZoom(6);
    }).toThrow();
    map.setZoom(0);
    expect(map.getZoom()).toBe(0);
});

test('setMaxZoom', () => {
    const map = createMap({zoom: 0});
    map.setMaxZoom(3.5);
    map.setZoom(4);
    expect(map.getZoom()).toBe(3.5);
});

test('unset maxZoom', () => {
    const map = createMap({maxZoom: 5});
    map.setMaxZoom(null);
    map.setZoom(6);
    expect(map.getZoom()).toBe(6);
});

test('getMaxZoom', () => {
    const map = createMap({zoom: 0});
    expect(map.getMaxZoom()).toBe(22);
    map.setMaxZoom(10);
    expect(map.getMaxZoom()).toBe(10);
});

test('ignore maxZooms over minZoom', () => {
    const map = createMap({minZoom: 5});
    expect(() => {
        map.setMaxZoom(4);
    }).toThrow();
    map.setZoom(5);
    expect(map.getZoom()).toBe(5);
});

test('throw on maxZoom smaller than minZoom at init', () => {
    expect(() => {
        createMap({minZoom: 10, maxZoom: 5});
    }).toThrow(new Error('maxZoom must be greater than or equal to minZoom'));
});

test('throw on maxZoom smaller than minZoom at init with falsey maxZoom', () => {
    expect(() => {
        createMap({minZoom: 1, maxZoom: 0});
    }).toThrow(new Error('maxZoom must be greater than or equal to minZoom'));
});

test('recalculate zoom is done on the camera update transform', async () => {
    const map = createMap({
        interactive: true,
        clickTolerance: 4,
        transformCameraUpdate: ({zoom}) => ({zoom: zoom + 0.1})
    });
    await map.once('style.load');
    map.terrain = createTerrain();
    const canvas = map.getCanvas();
    simulate.dragWithMove(canvas, {x: 100, y: 100}, {x: 100, y: 150});
    map._renderTaskQueue.run();
    expect(map.getZoom()).toBeCloseTo(0.20007702699730118, 10);
});

test('transformCameraUpdate is called after changing min or max zoom', async () => {
    const map = createMap({
        interactive: true,
        clickTolerance: 4,
    });
    map.setMinZoom(5);
    map.setMaxZoom(18);
    map.setZoom(18);
    await map.once('style.load');
    expect(map.getZoom()).toEqual(18);
    expect(map.getMinZoom()).toEqual(5);
    expect(map.getMaxZoom()).toEqual(18);
    const transformCameraUpdate = vi.fn(t => t);
    map.transformCameraUpdate = transformCameraUpdate;

    map.setMaxZoom(16);
    expect(map.getZoom()).toEqual(16);
    expect(map.getMinZoom()).toEqual(5);
    expect(map.getMaxZoom()).toEqual(16);
    expect(transformCameraUpdate.mock.calls[0][0]).toMatchObject({
        zoom: 16,
        minZoom: 5,
        maxZoom: 16,
    });

    map.setZoom(5);
    expect(transformCameraUpdate.mock.calls[1][0]).toMatchObject({
        zoom: 5,
        minZoom: 5,
        maxZoom: 16,
    });

    map.setMinZoom(6);
    map.transformCameraUpdate = transformCameraUpdate;
    expect(map.getZoom()).toEqual(6);
    expect(map.getMinZoom()).toEqual(6);
    expect(map.getMaxZoom()).toEqual(16);
    expect(transformCameraUpdate.mock.calls[2][0]).toMatchObject({
        zoom: 6,
        minZoom: 6,
        maxZoom: 16,
    });
});

test('fire move and zoom events when zoom is changed due to minZoom change', () => {
    const map = createMap({minZoom: 0, zoom: 10});
    const handleEvent = vi.fn();
    map.on('movestart', handleEvent);
    map.on('move', handleEvent);
    map.on('moveend', handleEvent);
    map.on('zoomstart', handleEvent);
    map.on('zoom', handleEvent);
    map.on('zoomend', handleEvent);
    map.setMinZoom(11);
    expect(map.getZoom()).toEqual(11);
    expect(map.getMinZoom()).toEqual(11);
    expect(handleEvent).toHaveBeenCalledTimes(6);
});

test('fire move and zoom events when zoom is changed due to maxZoom change', () => {
    const map = createMap({maxZoom: 20, zoom: 20});
    const handleEvent = vi.fn();
    map.on('movestart', handleEvent);
    map.on('move', handleEvent);
    map.on('moveend', handleEvent);
    map.on('zoomstart', handleEvent);
    map.on('zoom', handleEvent);
    map.on('zoomend', handleEvent);
    map.setMaxZoom(10);
    expect(map.getZoom()).toEqual(10);
    expect(map.getMaxZoom()).toEqual(10);
    expect(handleEvent).toHaveBeenCalledTimes(6);
});
