import {describe, expect, test, vi, afterEach} from 'vitest';
import {Sky} from './sky.ts';
import {latest as styleSpec} from '@maplibre/maplibre-gl-style-spec';
import {type EvaluationParameters} from './evaluation_parameters.ts';
import {type TransitionParameters} from './properties.ts';

const spec = styleSpec.sky;

test('Sky with defaults', () => {
    const sky = new Sky({}, {});
    sky.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters);

    expect(sky.properties.get('atmosphere-blend')).toEqual(spec['atmosphere-blend'].default);
});

test('Sky with options', () => {
    const sky = new Sky({
        'atmosphere-blend': 0.4
    }, {});
    sky.recalculate({zoom: 0, zoomHistory: {}} as EvaluationParameters);

    expect(sky.properties.get('atmosphere-blend')).toBe(0.4);
});

test('Sky with interpolate function', () => {
    const sky = new Sky({
        'atmosphere-blend': [
            'interpolate',
            ['linear'],
            ['zoom'],
            0, 1,
            5, 1,
            7, 0
        ]
    }, {});
    sky.recalculate({zoom: 6, zoomHistory: {}} as EvaluationParameters);

    expect(sky.properties.get('atmosphere-blend')).toBe(0.5);
});

test('Sky.getSky', () => {
    const defaults = {'atmosphere-blend': 0.8};

    expect(new Sky(defaults, {}).getSky()).toEqual(defaults);
});

describe('Sky.setSky', () => {
    test('sets Sky', () => {
        const sky = new Sky({}, {});
        sky.setSky({'atmosphere-blend': 1});
        sky.updateTransitions({
            now: 0,
            transition: {
                duration: 3000,
                delay: 0
            }
        });
        sky.recalculate({zoom: 16, zoomHistory: {}, now: 1500} as EvaluationParameters);
        expect(sky.properties.get('atmosphere-blend')).toBe(0.9);
    });

    test('validates by default', () => {
        const sky = new Sky({}, {});
        const skySpy = vi.spyOn(sky, '_validate');
        vi.spyOn(console, 'error').mockImplementation(() => {});
        sky.setSky({'atmosphere-blend': -1});
        sky.updateTransitions({transition: false} as any as TransitionParameters);
        sky.recalculate({zoom: 16, zoomHistory: {}, now: 10} as EvaluationParameters);
        expect(skySpy).toHaveBeenCalledTimes(1);
        expect(console.error).toHaveBeenCalledTimes(1);
        expect(skySpy.mock.calls[0][2]).toEqual({});
    });

    test('respects validation option', () => {
        const sky = new Sky({}, {});

        const skySpy = vi.spyOn(sky, '_validate');
        sky.setSky({'atmosphere-blend': -1}, {validate: false});
        sky.updateTransitions({transition: false} as any as TransitionParameters);
        sky.recalculate({zoom: 16, zoomHistory: {}, now: 10} as EvaluationParameters);

        expect(skySpy).toHaveBeenCalledTimes(1);
        expect(skySpy.mock.calls[0][2]).toEqual({validate: false});
        expect(sky.properties.get('atmosphere-blend')).toBe(-1);
    });
});

describe('Sky runtime error logging', () => {
    afterEach(() => {
        vi.restoreAllMocks();
    });

    test('warns with the sky property location when an expression errors at runtime', () => {
        const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});

        const sky = new Sky({}, {});
        // global-state defeats constant-folding, so this fails at evaluation time (not parse time).
        sky.setSky({'sky-color': ['to-color', ['global-state', 'missing']]} as any, {validate: false});
        sky.updateTransitions({transition: false} as any as TransitionParameters);
        sky.recalculate({zoom: 16, zoomHistory: {}, now: 10} as EvaluationParameters);

        expect(warn).toHaveBeenCalledTimes(1);
        expect(warn.mock.calls[0][0]).toBe('sky.sky-color: Could not parse color from value \'null\' Falling back to rgba(136,198,252,1).');
    });
});
