import { DIVECommunication } from '../Communication';
import '../types';
import { type Actions } from '../actions';
import '../actions/camera/movecamera';
import '../actions/camera/resetcamera';
import '../actions/camera/setcameralayer';
import '../actions/camera/setcameratransform';
import '../actions/camera/zoomcamera';
import '../actions/media/generatemedia';
import '../actions/object/addobject';
import '../actions/object/deleteobject';
import '../actions/object/getallobjects';
import '../actions/object/getobjects';
import '../actions/object/selectobject';
import '../actions/object/updateobject';
import '../actions/object/model/modelloaded';
import '../actions/object/model/placeonfloor';
import '../actions/scene/getallscenedata';
import '../actions/scene/setbackground';
import '../actions/scene/updatescene';
import '../actions/toolbox/select/setgizmomode';
import '../actions/toolbox/transform/setgizmovisible';
import '../actions/camera/getcameratransform';
import { type DIVEScene } from '../../scene/Scene';
import type DIVEToolbox from '../../toolbox/Toolbox';
import type DIVEOrbitControls from '../../controls/OrbitControls';
import { type DIVERenderer } from '../../renderer/Renderer';
import {
    type COMGroup,
    type COMEntity,
    type COMEntityType,
    type COMLight,
    type COMModel,
    type COMPov,
} from '../types';
import { type DIVESceneObject } from '../../types';

const mockModule: Record<string, any> = {
    get: jest.fn().mockReturnValue(Promise.resolve({})),
};
jest.mock('../../module/Module', () => {
    return {
        DIVEModule: jest.fn().mockImplementation(() => {
            return mockModule;
        }),
    };
});

jest.mock('../../mediacreator/MediaCreator', () => {
    return {
        DIVEMediaCreator: jest.fn().mockImplementation(() => {
            return {
                GenerateMedia: jest.fn(),
            };
        }),
    };
});

jest.mock('../../io/IO', () => {
    return {
        DIVEIO: jest.fn().mockImplementation(() => {
            return {
                Import: jest.fn(),
                Export: jest.fn(),
            };
        }),
    };
});

jest.mock('../../ar/AR', () => {
    return {
        DIVEAR: jest.fn().mockImplementation(() => {
            return {
                Launch: jest.fn(),
            };
        }),
    };
});

jest.mock('../../toolbox/select/SelectTool', () => {
    return {
        isSelectTool: jest.fn().mockReturnValue(true),
        DIVESelectTool: jest.fn().mockImplementation(() => {
            return {
                AttachGizmo: jest.fn(),
                DetachGizmo: jest.fn(),
            };
        }),
    };
});

const mockRenderer = {
    render: jest.fn(),
    OnResize: jest.fn(),
    StartRenderer: jest.fn(),
} as unknown as DIVERenderer;

const mockScene = {
    SetBackground: jest.fn(),
    AddSceneObject: jest.fn(),
    UpdateSceneObject: jest.fn(),
    DeleteSceneObject: jest.fn(),
    PlaceOnFloor: jest.fn(),
    GetSceneObject: jest.fn().mockReturnValue({
        attach: jest.fn(),
        DropIt: jest.fn(),
    }),
    background: {
        getHexString: jest.fn().mockReturnValue('ffffff'),
    },
    Root: {
        attach: jest.fn(),
    },
    Floor: {
        isFloor: true,
        visible: true,
        material: {
            color: {
                getHexString: jest.fn().mockReturnValue('ffffff'),
            },
        },
        SetVisibility: jest.fn(),
        SetColor: jest.fn(),
    },
    Grid: {
        SetVisibility: jest.fn(),
    },
    ComputeSceneBB: jest.fn(),
} as unknown as DIVEScene;

const mockController = {
    enableDamping: true,
    dampingFactor: 0.25,
    enableZoom: true,
    enablePan: true,
    minPolarAngle: 0,
    maxPolarAngle: Math.PI,
    minDistance: 0,
    maxDistance: Infinity,
    rotateSpeed: 0.5,
    panSpeed: 0.5,
    zoomSpeed: 0.5,
    keyPanSpeed: 0.5,
    screenSpacePanning: true,
    autoRotate: false,
    autoRotateSpeed: 2.0,
    enableKeys: true,
    keys: {
        LEFT: 37,
        UP: 38,
        RIGHT: 39,
        BOTTOM: 40,
    },
    mouseButtons: {
        LEFT: 0,
        MIDDLE: 1,
        RIGHT: 2,
    },
    target: {
        x: 4,
        y: 5,
        z: 6,
        set: jest.fn(),
        clone: jest.fn().mockReturnValue({ x: 4, y: 5, z: 6 }),
        copy: jest.fn(),
    },
    update: jest.fn(),
    dispose: jest.fn(),
    ZoomIn: jest.fn(),
    ZoomOut: jest.fn(),
    object: {
        position: {
            x: 1,
            y: 2,
            z: 3,
            clone: jest.fn().mockReturnValue({ x: 1, y: 2, z: 3 }),
            copy: jest.fn(),
        },
        quaternion: {
            x: 1,
            y: 2,
            z: 3,
            w: 4,
            clone: jest.fn().mockReturnValue({ x: 1, y: 2, z: 3, w: 4 }),
            copy: jest.fn(),
        },
        SetCameraLayer: jest.fn(),
        OnResize: jest.fn(),
        layers: {
            mask: 1,
        },
    },
    MoveTo: jest.fn(),
    RevertLast: jest.fn(),
    ComputeEncompassingView: jest.fn().mockReturnValue({
        position: { x: 1, y: 2, z: 3 },
        target: { x: 4, y: 5, z: 6 },
    }),
} as unknown as DIVEOrbitControls;

const mockToolBox = {
    UseTool: jest.fn(),
    GetActiveTool: jest.fn().mockReturnValue({
        AttachGizmo: jest.fn(),
        DetachGizmo: jest.fn(),
    }),
    SetGizmoMode: jest.fn(),
    SetGizmoVisibility: jest.fn(),
    SetGizmoScaleLinked: jest.fn(),
} as unknown as DIVEToolbox;

let testCom: DIVECommunication;

describe('dive/communication/DIVECommunication', () => {
    beforeEach(() => {
        jest.clearAllMocks();
        testCom = new DIVECommunication(
            mockRenderer,
            mockScene,
            mockController,
            mockToolBox,
        );
    });

    afterEach(() => {
        testCom.DestroyInstance();
        jest.clearAllMocks();
    });

    it('should instantiate', () => {
        expect(testCom).toBeDefined();
        expect(DIVECommunication['__instances']).toHaveLength(1);
    });

    it('should get instance', () => {
        expect(testCom).toBeDefined();
        expect(DIVECommunication.get(testCom.id)).toBeDefined();

        testCom.PerformAction('ADD_OBJECT', {
            id: 'someID',
        } as COMModel);
        expect(DIVECommunication.get('someID')).toBeDefined();
    });

    it('should destroy instance', () => {
        expect(testCom).toBeDefined();
        testCom.DestroyInstance();
        expect(DIVECommunication['__instances']).toBeDefined();
        expect(DIVECommunication['__instances']).toHaveLength(0);
    });

    it('should subscribe, listen and unsubscribe from action', () => {
        const listener = jest.fn();
        const unsub = testCom.Subscribe('GET_ALL_OBJECTS', listener);
        expect(unsub).toBeDefined();
        expect(unsub).toBeInstanceOf(Function);
        const objects0 = testCom.PerformAction('GET_ALL_OBJECTS', new Map());
        expect(objects0).toBeDefined();
        expect(objects0.size).toBeDefined();
        expect(objects0.size).toBe(0);
        unsub();
        testCom.PerformAction('GET_ALL_OBJECTS', new Map());
        expect(listener).toHaveBeenCalledTimes(1);
    });

    it('should not unsubscribe twice', () => {
        const unsub = testCom.Subscribe('GET_ALL_OBJECTS', () => {});
        expect(unsub()).toBe(true);
        expect(unsub()).toBe(false);
    });

    it('should not unsubscribe if listener does not exist anymore', () => {
        const unsub = testCom.Subscribe('GET_ALL_OBJECTS', () => {});
        testCom['listeners'].clear();
        expect(unsub()).toBe(false);
    });

    it('should tigger onChange callback', () => {
        const payload = {
            name: 'name',
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight;
        expect(() =>
            testCom.PerformAction('ADD_OBJECT', payload),
        ).not.toThrow();
    });

    it('should perform action START_RENDER', () => {
        const success = testCom.PerformAction('START_RENDER');
        expect(mockRenderer.StartRenderer).toHaveBeenCalledTimes(1);
        expect(success).toBe(true);
    });

    it('should perform action ADD_OBJECT', () => {
        const payload = {
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight;
        const success = testCom.PerformAction('ADD_OBJECT', payload);
        expect(mockScene.AddSceneObject).toHaveBeenCalledTimes(1);
        expect(success).toBe(true);
    });

    it('should not perform action ADD_OBJECT with same object', () => {
        const payload = {
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight;
        expect(testCom.PerformAction('ADD_OBJECT', payload)).toBe(true);
        expect(testCom.PerformAction('ADD_OBJECT', payload)).toBe(false);
        expect(mockScene.AddSceneObject).toHaveBeenCalledTimes(1);
    });

    it('should perform action UPDATE_OBJECT with existing oject', () => {
        const payload = {
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight;

        testCom.PerformAction('ADD_OBJECT', payload);

        const successUpdate = testCom.PerformAction('UPDATE_OBJECT', payload);
        expect(mockScene.UpdateSceneObject).toHaveBeenCalledTimes(1);
        expect(successUpdate).toBe(true);
    });

    it('should perform action UPDATE_OBJECT without existing oject', () => {
        const payload = {
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight;
        const successUpdate = testCom.PerformAction('UPDATE_OBJECT', payload);
        expect(mockScene.UpdateSceneObject).toHaveBeenCalledTimes(0);
        expect(successUpdate).toBe(false);
    });

    it('should perform action DELETE_OBJECT with existing object', () => {
        const payload = {
            entityType: 'group',
            id: 'group00',
        } as COMGroup;

        testCom.PerformAction('ADD_OBJECT', payload);

        // additionally add a child to the group
        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
            parentId: 'group00',
        } as COMLight);

        // and one child that has NO parent
        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'light',
            id: 'ambient01',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight);

        // and one child that has A DIFFERENT parent
        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'light',
            id: 'ambient02',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
            parentId: 'group01',
        } as COMLight);

        const successDelete = testCom.PerformAction('DELETE_OBJECT', payload);
        expect(mockScene.DeleteSceneObject).toHaveBeenCalledTimes(1);
        expect(successDelete).toBe(true);
    });

    it('should perform action DELETE_OBJECT without existing object', () => {
        const payload = {
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight;
        const successDelete = testCom.PerformAction('DELETE_OBJECT', payload);
        expect(mockScene.DeleteSceneObject).toHaveBeenCalledTimes(0);
        expect(successDelete).toBe(false);
    });

    it('should perform action SET_BACKGROUND', () => {
        const payload = {
            color: 'white',
        };
        const successSet = testCom.PerformAction('SET_BACKGROUND', payload);
        expect(mockScene.SetBackground).toHaveBeenCalledTimes(1);
        expect(successSet).toBe(true);
    });

    it('should perform action DROP_IT with existing model', () => {
        const payload = {
            entityType: 'model',
            id: 'model',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            scale: { x: 0.01, y: 0.01, z: 0.01 },

            uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
        } as COMModel;

        testCom.PerformAction('ADD_OBJECT', payload);

        const placeSpy = jest.spyOn(mockScene, 'GetSceneObject');

        const successPlace = testCom.PerformAction('DROP_IT', payload);
        expect(successPlace).toBe(true);
        expect(placeSpy).toHaveBeenCalledTimes(1);
    });

    it('should perform action DROP_IT without existing model', () => {
        const payload = {
            entityType: 'model',
            id: 'model',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            scale: { x: 0.01, y: 0.01, z: 0.01 },

            uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
        };
        const placeSpy = jest.spyOn(mockScene, 'GetSceneObject');

        const successPlace = testCom.PerformAction('DROP_IT', payload);
        expect(successPlace).toBe(false);
        expect(placeSpy).toHaveBeenCalledTimes(0);
    });

    it('should perform action PLACE_ON_FLOOR with existing model', () => {
        const payload = {
            entityType: 'model',
            id: 'model',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            scale: { x: 0.01, y: 0.01, z: 0.01 },

            uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
        } as COMModel;

        testCom.PerformAction('ADD_OBJECT', payload);

        const placeSpy = jest.spyOn(mockScene, 'PlaceOnFloor');

        const successPlace = testCom.PerformAction('PLACE_ON_FLOOR', payload);
        expect(successPlace).toBe(true);
        expect(placeSpy).toHaveBeenCalledTimes(1);
    });

    it('should perform action PLACE_ON_FLOOR without existing model', () => {
        const payload = {
            entityType: 'model' as COMEntityType,
            id: 'model',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            scale: { x: 0.01, y: 0.01, z: 0.01 },

            uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
        };
        const placeSpy = jest.spyOn(mockScene, 'PlaceOnFloor');

        const successPlace = testCom.PerformAction('PLACE_ON_FLOOR', payload);
        expect(successPlace).toBe(false);
        expect(placeSpy).toHaveBeenCalledTimes(0);
    });

    it('should perform action GET_ALL_OBJECTS', () => {
        const payload = {
            entityType: 'model',
            id: 'model',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            scale: { x: 0.01, y: 0.01, z: 0.01 },

            uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
        } as COMModel;

        const objects0 = testCom.PerformAction('GET_ALL_OBJECTS', new Map());
        expect(objects0).toBeDefined();
        expect(objects0.size).toBeDefined();
        expect(objects0.size).toBe(0);

        testCom.PerformAction('ADD_OBJECT', payload);

        const objects1 = testCom.PerformAction('GET_ALL_OBJECTS', new Map());
        expect(objects1).toBeDefined();
        expect(objects1.size).toBeDefined();
        expect(objects1.size).toBe(1);
    });

    it('should perform action MOVE_CAMERA', () => {
        const payload = {
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
            duration: 1000,
            locked: true,
        };
        const successSet = testCom.PerformAction('MOVE_CAMERA', payload);
        const moveToSpy = jest.spyOn(mockController, 'MoveTo');
        expect(moveToSpy).toHaveBeenCalledTimes(1);
        expect(successSet).toBe(true);

        moveToSpy.mockClear();
        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'pov',
            id: 'pov',
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        } as COMPov);

        const payloadId = {
            id: 'pov',
            locked: true,
            duration: 1000,
        };
        const successSet1 = testCom.PerformAction('MOVE_CAMERA', payloadId);
        expect(moveToSpy).toHaveBeenCalledTimes(1);
        expect(successSet1).toBe(true);
    });

    it('should perform action RESET_CAMERA', () => {
        const payload = {
            duration: 1000,
        };
        const successSet = testCom.PerformAction('RESET_CAMERA', payload);
        expect(mockController.RevertLast).toHaveBeenCalledTimes(1);
        expect(successSet).toBe(true);
    });

    it('should perform action COMPUTE_ENCOMPASSING_VIEW', () => {
        const payload = {};
        const transform = testCom.PerformAction(
            'COMPUTE_ENCOMPASSING_VIEW',
            payload,
        );
        expect(transform).toStrictEqual({
            position: { x: 1, y: 2, z: 3 },
            target: { x: 4, y: 5, z: 6 },
        });
        expect(payload).toStrictEqual({
            position: { x: 1, y: 2, z: 3 },
            target: { x: 4, y: 5, z: 6 },
        });
    });

    it('should perform action GET_ALL_SCENE_DATA', () => {
        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'pov',
            id: 'pov',
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        } as COMPov);

        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'model',
            id: 'model',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            scale: { x: 0.01, y: 0.01, z: 0.01 },

            uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
        } as COMModel);

        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'light',
            id: 'ambient00',
            type: 'ambient',
            intensity: 0.5,
            color: 'white',
        } as COMLight);

        testCom.PerformAction('ADD_OBJECT', {
            entityType: 'group',
            id: 'group1',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            parentId: null,
        } as COMGroup);

        const success = testCom.PerformAction('GET_ALL_SCENE_DATA', {});
        expect(success).toStrictEqual({
            backgroundColor: '#ffffff',
            cameras: [
                {
                    entityType: 'pov',
                    id: 'pov',
                    position: { x: 0, y: 0, z: 0 },
                    target: { x: 0, y: 0, z: 0 },
                    parentId: null,
                },
            ],
            floorColor: '#ffffff',
            floorEnabled: true,
            lights: [
                {
                    entityType: 'light',
                    id: 'ambient00',
                    type: 'ambient',
                    intensity: 0.5,
                    color: 'white',
                    parentId: null,
                },
            ],
            mediaItem: null,
            name: undefined,
            objects: [
                {
                    entityType: 'model',
                    id: 'model',
                    position: { x: 0, y: 0, z: 0 },
                    rotation: { x: 0, y: 0, z: 0 },
                    scale: { x: 0.01, y: 0.01, z: 0.01 },
                    parentId: null,
                    uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
                },
            ],
            primitives: [],
            spotmarks: [],
            userCamera: {
                position: { x: 1, y: 2, z: 3 },
                target: { x: 4, y: 5, z: 6 },
            },
            groups: [
                {
                    entityType: 'group',
                    id: 'group1',
                    position: { x: 0, y: 0, z: 0 },
                    rotation: { x: 0, y: 0, z: 0 },
                    parentId: null,
                },
            ],
        });
    });

    it('should perform action GET_OBJECTS', () => {
        const mock0 = {
            entityType: 'pov',
            id: 'test0',
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        } as COMPov;
        testCom.PerformAction('ADD_OBJECT', mock0);

        const mock1 = {
            entityType: 'pov',
            id: 'test1',
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        } as COMPov;
        testCom.PerformAction('ADD_OBJECT', mock1);

        const successWithoutIds = testCom.PerformAction('GET_OBJECTS', {
            ids: [],
        });
        expect(Array.from(successWithoutIds.values())).toStrictEqual([]);

        const successWithIds = testCom.PerformAction('GET_OBJECTS', {
            ids: ['test1'],
        });
        expect(Array.from(successWithIds.values())).toStrictEqual([
            {
                entityType: 'pov',
                id: 'test1',
                position: { x: 0, y: 0, z: 0 },
                target: { x: 0, y: 0, z: 0 },
                parentId: null,
            },
        ]);
    });

    it('should perform action SELECT_OBJECT', () => {
        const success0 = testCom.PerformAction('SELECT_OBJECT', {
            id: 'test0',
        });
        expect(success0).toBe(false);

        const mock0 = {
            entityType: 'pov',
            id: 'test0',
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        } as COMPov;
        testCom.PerformAction('ADD_OBJECT', mock0);

        jest.spyOn(mockScene, 'GetSceneObject').mockReturnValueOnce(undefined);
        const success1 = testCom.PerformAction('SELECT_OBJECT', {
            id: 'test0',
        });
        expect(success1).toBe(false);

        jest.spyOn(mockScene, 'GetSceneObject').mockReturnValueOnce(
            {} as unknown as DIVESceneObject,
        );
        const success2 = testCom.PerformAction('SELECT_OBJECT', {
            id: 'test0',
        });
        expect(success2).toBe(false);

        jest.spyOn(mockScene, 'GetSceneObject').mockReturnValueOnce({
            isSelectable: true,
        } as unknown as DIVESceneObject);
        const success3 = testCom.PerformAction('SELECT_OBJECT', {
            id: 'test0',
        });
        expect(success3).toBe(true);
    });

    it('should perform action DESELECT_OBJECT', () => {
        const success0 = testCom.PerformAction('DESELECT_OBJECT', {
            id: 'test0',
        });
        expect(success0).toBe(false);

        const mock0 = {
            entityType: 'pov',
            id: 'test0',
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        } as COMPov;
        testCom.PerformAction('ADD_OBJECT', mock0);

        jest.spyOn(mockScene, 'GetSceneObject').mockReturnValueOnce(undefined);
        const success1 = testCom.PerformAction('DESELECT_OBJECT', {
            id: 'test0',
        });
        expect(success1).toBe(false);

        jest.spyOn(mockScene, 'GetSceneObject').mockReturnValueOnce(
            {} as unknown as DIVESceneObject,
        );
        const success2 = testCom.PerformAction('DESELECT_OBJECT', {
            id: 'test0',
        });
        expect(success2).toBe(false);

        jest.spyOn(mockScene, 'GetSceneObject').mockReturnValueOnce({
            isSelectable: true,
        } as unknown as DIVESceneObject);
        const success3 = testCom.PerformAction('DESELECT_OBJECT', {
            id: 'test0',
        });
        expect(success3).toBe(true);
    });

    it('should perform action SET_CAMERA_TRANSFORM', () => {
        const success = testCom.PerformAction('SET_CAMERA_TRANSFORM', {
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        });
        expect(success).toBe(true);
    });

    it('should perform action GET_CAMERA_TRANSFORM', () => {
        const success = testCom.PerformAction('GET_CAMERA_TRANSFORM', {});
        expect(success).toStrictEqual({
            position: { x: 1, y: 2, z: 3 },
            target: { x: 4, y: 5, z: 6 },
        });
    });

    it('should perform action SET_CAMERA_LAYER', () => {
        const success = testCom.PerformAction('SET_CAMERA_LAYER', {
            layer: 'EDITOR',
        });
        expect(success).toBe(true);
    });

    it('should perform action ZOOM_CAMERA', () => {
        const success0 = testCom.PerformAction('ZOOM_CAMERA', {
            direction: 'IN',
            by: 10,
        });
        expect(success0).toBe(true);
        const success1 = testCom.PerformAction('ZOOM_CAMERA', {
            direction: 'OUT',
            by: 10,
        });
        expect(success1).toBe(true);
    });

    it('should perform action SET_GIZMO_MODE', () => {
        const success = testCom.PerformAction('SET_GIZMO_MODE', {
            mode: 'translate',
        });
        expect(success).toBe(true);
    });

    it('should perform action SET_GIZMO_VISIBILITY', () => {
        let visibility = testCom.PerformAction('SET_GIZMO_VISIBILITY', true);
        expect(visibility).toBe(true);

        visibility = testCom.PerformAction('SET_GIZMO_VISIBILITY', false);
        expect(visibility).toBe(false);
    });

    it('should perform action SET_GIZMO_SCALE_LINKED', () => {
        const success = testCom.PerformAction('SET_GIZMO_SCALE_LINKED', true);
        expect(success).toBe(true);
    });

    it('should perform action USE_TOOL', () => {
        let success = testCom.PerformAction('USE_TOOL', { tool: 'select' });
        expect(success).toBe(true);

        success = testCom.PerformAction('USE_TOOL', { tool: 'none' });
        expect(success).toBe(true);
    });

    it('should perform action MODEL_LOADED', () => {
        const payload = {
            entityType: 'model',
            id: 'modelID',
            position: { x: 0, y: 0, z: 0 },
            rotation: { x: 0, y: 0, z: 0 },
            scale: { x: 0.01, y: 0.01, z: 0.01 },

            uri: 'https://threejs.org/examples/models/gltf/LittlestTokyo.glb',
        } as COMModel;
        testCom.PerformAction('ADD_OBJECT', payload);
        const success = testCom.PerformAction('MODEL_LOADED', {
            id: 'modelID',
        });
        expect(success).toBe(true);
    });

    it('should perform action UPDATE_SCENE', () => {
        const success0 = testCom.PerformAction('UPDATE_SCENE', {
            name: 'scene name',
            backgroundColor: 'ffffff',
            floorEnabled: true,
            floorColor: 'ffffff',
            gridEnabled: true,
        });
        expect(success0).toBe(true);

        const success1 = testCom.PerformAction('UPDATE_SCENE', {
            name: undefined,
            backgroundColor: undefined,
            floorEnabled: undefined,
            floorColor: undefined,
            gridEnabled: undefined,
        });
        expect(success1).toBe(true);
    });

    it('should perform action GENERATE_MEDIA', async () => {
        const blobUri = 'blob:http://localhost:3000/1234';
        jest.spyOn(mockModule, 'get').mockResolvedValue({
            GenerateMedia: jest.fn(),
        });
        const mediaGeneratorModule = await testCom['_mediaGenerator'].get();

        jest.spyOn(mediaGeneratorModule, 'GenerateMedia').mockReturnValue(
            blobUri,
        );

        const mock1 = {
            entityType: 'pov',
            id: 'test1',
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
        } as COMPov;
        testCom.PerformAction('ADD_OBJECT', mock1);

        const success0 = await testCom.PerformAction('GENERATE_MEDIA', {
            id: 'test1',
            width: 800,
            height: 600,
        });
        expect(success0).toBe(blobUri);

        const success1 = await testCom.PerformAction('GENERATE_MEDIA', {
            position: { x: 0, y: 0, z: 0 },
            target: { x: 0, y: 0, z: 0 },
            width: 800,
            height: 600,
        });
        expect(success1).toBe(blobUri);

        jest.restoreAllMocks();
    });

    it('should perform action SET_PARENT', () => {
        const object = {
            id: 'object',
        } as COMEntity;
        testCom.PerformAction('ADD_OBJECT', object);

        const objectNotRegistered = {
            id: 'objectNotRegistered',
        } as COMEntity;

        const parent0 = {
            id: 'parent0',
        } as COMEntity;
        testCom.PerformAction('ADD_OBJECT', parent0);

        const parent1 = {
            id: 'parent1',
        } as COMEntity;
        testCom.PerformAction('ADD_OBJECT', parent1);

        const parentNotRegistered = {
            id: 'parentNotRegistered',
        } as COMEntity;

        const attachNotRegisteredObject = testCom.PerformAction('SET_PARENT', {
            object: objectNotRegistered,
            parent: null,
        });
        expect(attachNotRegisteredObject).toBe(false);

        jest.spyOn(mockScene, 'GetSceneObject').mockImplementationOnce(() => {
            return undefined;
        });
        const attachNonSceneObject = testCom.PerformAction('SET_PARENT', {
            object: object,
            parent: null,
        });
        expect(attachNonSceneObject).toBe(false);

        const attachToNull = testCom.PerformAction('SET_PARENT', {
            object: object,
            parent: null,
        });
        expect(attachToNull).toBe(true);

        const attachToItself = testCom.PerformAction('SET_PARENT', {
            object: object,
            parent: object,
        });
        expect(attachToItself).toBe(false);

        const attachToNotRegsiteredParent = testCom.PerformAction(
            'SET_PARENT',
            {
                object: object,
                parent: parentNotRegistered,
            },
        );
        expect(attachToNotRegsiteredParent).toBe(true);

        jest.spyOn(mockScene, 'GetSceneObject')
            .mockImplementationOnce(() => {
                return {
                    DropIt: jest.fn(),
                    attach: jest.fn(),
                } as unknown as DIVESceneObject;
            })
            .mockImplementationOnce(() => {
                return undefined;
            });

        const attachtoNonSceneParent = testCom.PerformAction('SET_PARENT', {
            object: object,
            parent: parent1,
        });
        expect(attachtoNonSceneParent).toBe(true);

        const attachToValidParent = testCom.PerformAction('SET_PARENT', {
            object: object,
            parent: parent1,
        });
        expect(attachToValidParent).toBe(true);
    });

    it('should perform action EXPORT_SCENE', async () => {
        const url = 'https://example.com';
        jest.spyOn(mockModule, 'get').mockResolvedValue({
            Export: jest.fn(),
        });
        const ioModule = await testCom['_io'].get();

        jest.spyOn(ioModule, 'Export').mockResolvedValueOnce(url);

        const result = await testCom.PerformAction('EXPORT_SCENE', {
            type: 'glb',
        });
        expect(result).toBe(url);
    });

    it('should perform action LAUNCH_AR', async () => {
        jest.spyOn(mockModule, 'get').mockResolvedValue({
            Launch: jest.fn(),
        });
        const arModule = await testCom['_ar'].get();
        const arLaunchSpy = jest
            .spyOn(arModule, 'Launch')
            .mockResolvedValueOnce();

        const result = await testCom.PerformAction('LAUNCH_AR', undefined);
        expect(arLaunchSpy).toHaveBeenCalledTimes(1);
    });

    it('should warn of action is of invalid type ', () => {
        jest.spyOn(console, 'warn').mockImplementationOnce(() => {});
        testCom.PerformAction('INVALID_ACTION' as keyof Actions, {});
        expect(console.warn).toHaveBeenCalledTimes(1);
    });
});
