import {describe, expect, test, vi} from 'vitest';
import {Context} from './context.ts';
import {Texture} from './texture.ts';
import {premultiplyAlpha, RGBAImage} from '../util/image.ts';
import {createNullGL} from '../util/test/null_gl.ts';

describe('Texture', () => {
    describe('glPixelStore state is reset after texture creation', () => {
        const testImage = new RGBAImage({
            width: 2,
            height: 1,
        }, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));

        function getContext(): Context {
            return new Context(createNullGL());
        }

        function expectPixelStoreState(context: Context): void {
            expect(context.pixelStoreUnpack.current).toEqual(context.pixelStoreUnpack.default);
            expect(context.pixelStoreUnpackFlipY.current).toEqual(context.pixelStoreUnpackFlipY.default);
            expect(context.pixelStoreUnpackPremultiplyAlpha.current).toEqual(context.pixelStoreUnpackPremultiplyAlpha.default);
        }

        test('premultiply=false', () => {
            const context = getContext();
            // We test the Texture constructor's side effects
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const _texture = new Texture(context, testImage, context.gl.RGBA, {premultiply: false});
            expectPixelStoreState(context);
        });

        test('premultiply=true', () => {
            const context = getContext();
            // We test the Texture constructor's side effects
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const _texture = new Texture(context, testImage, context.gl.RGBA, {premultiply: true});
            expectPixelStoreState(context);
        });
    });

    test('bind restores handle after corruption (#2811)', () => {
        const gl = createNullGL();
        const context = new Context(gl);
        const image = new RGBAImage({width: 2, height: 1}, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
        const texture = new Texture(context, image, gl.RGBA);

        const originalHandle = texture.texture;
        texture.texture = gl.createTexture();

        texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
        expect(texture.texture).toBe(originalHandle);
    });

    test('RGBA texture recreates on resize via texStorage2D', () => {
        const gl = createNullGL();
        const context = new Context(gl);
        const image1 = new RGBAImage({width: 2, height: 2}, new Uint8Array(2 * 2 * 4));
        const texture = new Texture(context, image1, gl.RGBA);

        const firstHandle = texture.texture;
        expect(texture.size).toEqual([2, 2]);

        const image2 = new RGBAImage({width: 4, height: 4}, new Uint8Array(4 * 4 * 4));
        texture.update(image2);

        expect(texture.size).toEqual([4, 4]);
        expect(texture.texture).not.toBe(firstHandle);
        expect(gl.deleteTexture).toHaveBeenCalled();
    });

    test('resizing re-applies filter and wrap to the new handle', () => {
        const gl = createNullGL();
        const context = new Context(gl);
        const texture = new Texture(context, new RGBAImage({width: 2, height: 2}, new Uint8Array(2 * 2 * 4)), gl.RGBA);
        texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);

        texture.update(new RGBAImage({width: 4, height: 4}, new Uint8Array(4 * 4 * 4)));
        vi.mocked(gl.texParameteri).mockClear();
        texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);

        // Same values as before, but on a new handle, so both have to be written again.
        expect(gl.texParameteri).toHaveBeenCalledWith(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        expect(gl.texParameteri).toHaveBeenCalledWith(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    });

    test('premultiplyAlpha produces correct output', () => {
        // pixel: r=200, g=100, b=50, a=128 (half transparent)
        const data = new Uint8Array([200, 100, 50, 128]);
        const result = premultiplyAlpha(data);
        expect(result[0]).toBe(Math.round(200 * 128 / 255)); // 100
        expect(result[1]).toBe(Math.round(100 * 128 / 255)); // 50
        expect(result[2]).toBe(Math.round(50 * 128 / 255));  // 25
        expect(result[3]).toBe(128);
    });
});
