const events = [
    'contextmenu',
    'map.enable',
    'map.disable',
    'map.ready',
    'map.redraw',
    'map.refresh',
    'mode.change',
    'theme.change',
    'sources.add',
    'source.add',
    'source.hot',
    'layers.add',
    'layers.remove',
    'layer.add',
    'control.add',
    'control.remove',
    'features.zoom',
    'features.add',
    'features.remove',
    'features.update',
    'features.import',
    'features.export',
    'features.offset',
    'features.hide',
    'feature.add',
    'feature.remove',
    'feature.select',
    'feature.deselect',
    'feature.update',
    'feature.history',
    'select.load',
    'select.add',
    'select.remove',
    'select.activate',
    'select.deactivate',
    'draw.activate',
    'draw.deactivate',
    'draw.start',
    'draw.finish',
    'draw.cancel',
    'mesh.load',
    'text.add',
    'icon.add',
    'vertex.find',
    'vertex.on',
    'vertex.off',
    'vertex.add',
    'vertex.drag',
    'vertex.dragsnap',
    'vertex.delete',
    'overpass.add',
    'snapping.activate',
    'snapping.deactivate',
    'snapping.add',
    'snapping.refresh',
    'snapping.delete',
    'pinning.activate',
    'pinning.deactivate',
    'pinning.add',
    'pinning.update',
    'routing.activate',
    'routing.deactivate',
    'routing.add',
    'painting.activate',
    'painting.deactivate',
    'painting.start',
    'painting.update',
    'gamepad.init',
    'gamepad.add',
    'gamepad.remove',
    'gamepad.hold',
    'gamepad.press',
    'gamepad.release',
    'gamepad.connect',
    'gamepad.disconnect',
    'locate.on',
    'locate.off',
    'locate.update'
]

/**
 * @mixin
 * @memberof module:geoflo
 * @name Events
 * @description This module handles various map and user interaction events for the geoflo application.
 * @param {Object} geoflo - The geoflo instance to which the events are bound.
 * @returns {Object} An object containing methods to add and remove event listeners.
 * @function addEventListeners - Adds event listeners to the map and other elements.
 * @function removeEventListeners - Removes event listeners from the map and other elements.
 */

const Events = function (geoflo) {
    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mapMoveStart
     * @description Handles the `movestart` event on the map.
     * @param {Object} event - The event object containing map movement details.
     */
    const mapMoveStart = function (event) {
        geoflo.mapMoving = event;
        if (geoflo.settingExtent) return;
        if (geoflo.locate) geoflo.locate.onMapMove(event);
        geoflo.setIcon(event);
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mapMoving
     * @description Handles the `move` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing map movement details.
     */
    const mapMoving = function (event) {
        geoflo.mapMoving = event;
        if (geoflo.settingExtent) return;
        geoflo.setIcon(event);
        geoflo.setCenterMarker();
        geoflo.locate && geoflo.locate.following ? geoflo.setMapClass('disable') : false;
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mapMoveEnd
     * @description Handles the `moveend` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing map movement details.
     */
    const mapMoveEnd = function (event) {
        geoflo.mapMoving = false;
        if (geoflo.settingExtent) return;
        if (geoflo.locate) geoflo.locate.onMapMove(event);
        geoflo.setIcon(event);
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mouseMove
     * @description Handles the `mousemove` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing mouse movement details.
     */
    const mouseMove = function (event) {
        var painting = geoflo.options.painting && geoflo.options.painting.enable;
        painting = painting && geoflo.currentMode && geoflo.currentMode.id === 'draw';
        
        geoflo.locate && geoflo.locate.following ? geoflo.setMapClass('disable') :
        geoflo.dragMoving ? geoflo.setMapClass('grabbing') :
        geoflo.addingVertexOnLine || geoflo.canAddVertex ? geoflo.setMapClass('pointer') :
        geoflo.canDragMove ? geoflo.setMapClass('grab') :
        painting? geoflo.setMapClass('painting') :
        geoflo.setMapClass('pointer');

        geoflo.lastMouseEvent = event;
        if (!geoflo.currentMode) return;
        geoflo.currentMode.handleMove ? geoflo.currentMode.handleMove(event) : false;
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mouseDown
     * @description Handles the `mousedown` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing mouse movement details.
     */
    const mouseDown = function (event) {
        var painting = geoflo.options.painting && geoflo.options.painting.enable;
        painting = painting && geoflo.currentMode && geoflo.currentMode.id === 'draw';
        
        geoflo.locate && geoflo.locate.following ? geoflo.setMapClass('disable') :
        painting? geoflo.setMapClass('painting') :
        geoflo.dragMoving ? geoflo.setMapClass('grabbing') :
        geoflo.addingVertexOnLine ? geoflo.setMapClass('grabbing') :
        geoflo.canDragMove ? geoflo.setMapClass('grab') :
        geoflo.lastClick ? geoflo.setMapClass('grabbing') :
        geoflo.setMapClass('pointer');
        
        geoflo.mouseIsDown = [event.lngLat.lng, event.lngLat.lat];
        geoflo.currentMode.handleDown ? geoflo.currentMode.handleDown(event) : false;
    };    
    
    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mouseUp
     * @description Handles the `mouseup` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing mouse movement details.
     */
    const mouseUp = function (event) {
        geoflo.mouseIsDown = false;
        geoflo.locate && geoflo.locate.following ? geoflo.setMapClass('disable') : geoflo.setMapClass('pointer');
        geoflo.currentMode.handleUp ? geoflo.currentMode.handleUp(event) : false;
    };

    
    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mouseClick
     * @description Handles the `click` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing mouse movement details.
     */
    const mouseClick = async function (event) {
        if (event.type.includes('preclick')) return geoflo.currentMode.handlePreclick ? geoflo.currentMode.handlePreclick(event) : false;
        geoflo.currentMode.handleClick ? geoflo.currentMode.handleClick(event) : false;
    };

    
    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mouseDrag
     * @description Handles the `drag` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing mouse movement details.
     */
    const mouseDrag = function (event) {
        geoflo.locate && geoflo.locate.following ? geoflo.setMapClass('disable') :
        geoflo.dragMoving ? geoflo.setMapClass('grabbing') :
        geoflo.canDragMove ? geoflo.setMapClass('grab') :
        geoflo.setMapClass('move');

        geoflo.currentMode.handleDrag ? geoflo.currentMode.handleDrag(event) : false;
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mouseleave
     * @description Handles the `mouseleave` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing mouse movement details.
     */
    const mouseleave = function (event) {
        if (!geoflo.currentMode) return;
        geoflo.currentMode.handleOffMap ? geoflo.currentMode.handleOffMap(event) : false;
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name mouseover
     * @description Handles the `mouseover` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing mouse movement details.
     */
    const mouseover = function (event) {
        if (!geoflo.currentMode) return;
        geoflo.currentMode.handleOnMap ? geoflo.currentMode.handleOnMap(event) : false;
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name keypress
     * @description Handles the `keypress` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing keypress details.
     */
    const keypress = function (event) {
        if (geoflo.textMarker) return;
        
        console.log("keycode: ", event.keyCode, " =>", event.key, " | Code:", event.code, " Event:", event);
        const buttonOptions = geoflo.getButtons();

        let keyHandled = false;

        Object.keys(buttonOptions).forEach((buttonId) => {
            const option = buttonOptions[buttonId];
            const key = geoflo.options.keys[buttonId];

            if (key === event.key) {
                option.button.click();
                keyHandled = true;
            } else if (key === event.keyCode) {
                option.button.click();
                keyHandled = true;
            } else if (option.keycode && option.keycode === event.keyCode) {
                option.button.click();
                keyHandled = true;
            }
        });

        if (geoflo.options.commands) {
            geoflo.options.commands.forEach(function (c) {
                if (c.key === event.keyCode || c.key === event.key || c.key === event.code) {
                    if (!c.command || typeof c.command !== 'function') return;
                    c.command(event, geoflo, c);
                    keyHandled = true;
                }
            })
        }

        if (keyHandled) return keyHandled;

        if (event.key.includes('Arrow')) {}

        geoflo.currentKeyPress = event.key;

        switch (event.code) {
            case "Enter": {
                geoflo.setMode();
                break;
            }
            case "NumpadEnter": {
                geoflo.setMode();
                break;
            }
            case "Escape": {
                geoflo.hotFeature = null;
                geoflo.setMode();
                break;
            }
            case "Home": {
                geoflo.moveSelectedFeatures(1);
                break;
            }
            case "End": {
                geoflo.moveSelectedFeatures(-1);
                break;
            }
            case "Delete": {
                if (geoflo.mode === geoflo.statics.constants.modes.SELECT) {
                    if (geoflo.hasSelection()) { geoflo.removeSelection(); }
                } else if (geoflo.mode === geoflo.statics.constants.modes.DRAW) {
                    geoflo.currentMode.deleteVertex();
                }
                break;
            }
        }
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name keyrelease
     * @description Handles the `keyrelease` event on the map, updating icons and tracking movements.
     * @param {Object} event - The event object containing keypress details.
     */
    const keyrelease = function (event) {
        console.log("keyrelease: ", event);
        geoflo.currentKeyPress = false;
    };

    /**
     * @function
     * @memberof module:geoflo.Events
     * @name touchstart
     * @description Handles the touchstart event by delegating to the current mode's handleTouch method if it exists.
     * @param {Event} event - The touchstart event object.
     */
    const touchstart = function (event) {
        geoflo.currentMode.handleTouch ? geoflo.currentMode.handleTouch(event) : false;
    };

    /**
     * @function
     * @name touchend
     * @memberof module:geoflo.Events
     * @description Handles the touchend event by invoking the handleTouch method of the current mode if it exists.
     *
     * @param {Event} event - The touchend event object.
     * @returns {boolean} Returns false if the handleTouch method does not exist; otherwise, it returns undefined.
     */
    const touchend = function (event) {
        geoflo.currentMode.handleTouch ? geoflo.currentMode.handleTouch(event) : false;
    };
    
    /**
     * @function
     * @name touchmove
     * @memberof module:geoflo.Events
     * @description Handles the touchmove event by invoking the handleTouch method of the current mode if it exists.
     *
     * @param {Event} event - The touchmove event object.
     * @returns {boolean} Returns false if the handleTouch method does not exist.
     */
    const touchmove = function (event) {
        geoflo.currentMode.handleTouch ? geoflo.currentMode.handleTouch(event) : false;
    };

    /**
     * @function
     * @name touchcancel
     * @memberof module:geoflo.Events
     * @description Handles the touch cancel event by invoking the handleTouch method of the current mode if it exists.
     *
     * @param {Event} event - The touch cancel event object.
     * @returns {boolean} Returns false if the handleTouch method does not exist; otherwise, it returns the result of the handleTouch method.
     */
    const touchcancel = function (event) {
        geoflo.currentMode.handleTouch ? geoflo.currentMode.handleTouch(event) : false;
    };

    /**
     * @function
     * @name gamepadconnected
     * @memberof module:geoflo.Events
     * @description Handles the event when a gamepad is connected and adds it to the geoflo instance.
     *
     * @param {Event} event - The event object containing information about the connected gamepad.
     * @returns {void} This function does not return a value.
     */
    const gamepadconnected = function (event) {
        const gamepad = event.gamepad || event.detail.gamepad;
        if (!geoflo.license || !geoflo.license.enabled || !geoflo.premiumModules) throw new Error('GeoFlo Premium Required!');
        geoflo.gamepads[gamepad.index] = new geoflo.premiumModules.Gaming(gamepad);
        geoflo.fire('gamepad.add', { gamepad: gamepad });
    };

    /**
     * @function
     * @name gamepaddisconnected
     * @memberof module:geoflo.Events
     * @description Handles the event when a gamepad is disconnected and removes it from the geoflo system.
     *
     * @param {Event} event - The event object containing information about the disconnected gamepad.
     * @returns {void} This function does not return a value.
     */
    const gamepaddisconnected = function (event) {
        const gamepad = event.gamepad || event.detail.gamepad;
        if (!geoflo.gamepads[gamepad.index]) return false;
        geoflo.gamepads[gamepad.index].onDisconnect(gamepad);
        delete geoflo.gamepads[gamepad.index]
        geoflo.fire('gamepad.remove', { gamepad: gamepad });
    };

    /**
     * @function
     * @name handleOrientation
     * @memberof module:geoflo.Events
     * @description Updates the orientation of the geoflo object based on the device's orientation event.
     *
     * @param {Event} event - The orientation event containing alpha, beta, and gamma values.
     * @returns {void} This function does not return a value.
     */
    const handleOrientation = function (event) {
        geoflo.updateOrientation({
            alpha: event.alpha,
            beta: event.beta,
            gamma: event.gamma
        })
    };

    /**
     * @function
     * @name dragStart
     * @memberof module:geoflo.Events
     * @description Handles the drag start event for an element.
     *
     * @param {Event} event - The event object representing the drag start event.
     * @returns {void} This function does not return a value.
     */
    const dragStart = function (event) {
        //if (geoflo.locate && geoflo.locate.following) return event.originalEvent.preventDefault();
    };

    /**
     * @function
     * @name rotatePitch
     * @memberof module:geoflo.Events
     * @description Adjusts the map's pitch based on the provided event, setting the map class to 'grabbing', updating the icon, and repositioning the center marker.
     *
     * @param {Object} event - The event object that contains information about the interaction.
     * @returns {void} This function does not return a value.
     */
    const rotatePitch = function (event) {
        geoflo.setMapClass('grabbing');
        geoflo.setIcon(event);
        geoflo.setCenterMarker({ transform: true });
    };



    /**
     * @function
     * @name fireEvent
     * @memberof module:geoflo.Events
     * @description Triggers a custom event on the geoflo map based on the provided event object.
     *
     * @param {Object} event - The event object containing details about the event.
     * @param {string} event.type - The type of the event, which includes the action and target information.
     * @param {Object} event.detail - Additional details associated with the event.
     * @returns {void} This function does not return a value.
     */
    const fireEvent = function fireEvent (event) {
        const name = event.type.split(':')[1];
        
        const detail = {
            data: event.detail,
            type: name.split('.')[0],
            action: name.split('.')[1],
            target: geoflo,
        }

        const details = { name: name, detail: detail };
        geoflo.map.fire(geoflo.id, details);
    }

    /**
     * @function
     * @name contextMenu
     * @memberof module:geoflo.Events
     * @description Handles the context menu event by invoking the current mode's context handling function if it exists.
     *
     * @param {Event} event - The event object representing the context menu event.
     * @returns {boolean} Returns false if the current mode does not have a context handler.
     */
    const contextMenu = function (event) {
        geoflo.currentMode.handleContext ? geoflo.currentMode.handleContext(event) : false;
        geoflo.fire('contextmenu', event);
    }

    /**
     * @function
     * @name sourceData
     * @memberof module:geoflo.Events
     * @description Processes the source data from an event and triggers an action based on the source ID.
     *
     * @param {Object} event - The event object containing source data.
     * @param {string} event.sourceDataType - The type of the source data.
     * @param {string} event.sourceId - The ID of the source.
     * @param {Object} event.source - The source object containing data.
     * @param {string} event.type - The type of the event.
     * @returns {boolean} Returns false if the source data type is invalid or if the source ID is not present; otherwise, it triggers an event based on the source ID.
     */
    const sourceData = function (event) {
        if (!event.sourceDataType || !event.sourceDataType === 'content' || !event.sourceId) return false;
        const id = event.sourceId;

        const detail = {
            id: id,
            data: event.source.data,
            type: event.type,
            target: geoflo,
        }

        id === geoflo.statics.constants.sources.HOT ? geoflo.fire('source.hot', detail) : false;
    }

    

    /**
     * @function
     * @name addEventListeners
     * @memberof module:geoflo.Events
     * @description Attaches various event listeners to the map and container for handling user interactions and map events.
     *
     * @returns {void} This function does not return a value.
     */
    function addEventListeners () {
        geoflo.map.on("movestart", mapMoveStart);
        geoflo.map.on("move", mapMoving);
        geoflo.map.on("moveend", mapMoveEnd);
        geoflo.map.on("mousemove", mouseMove);

        geoflo.map.on('preclick', mouseClick);
        geoflo.map.on('click', mouseClick);
        geoflo.map.on('tap', mouseClick);

        geoflo.map.on('dragstart', dragStart);
        geoflo.map.on('drag', mouseDrag);
        geoflo.map.on('mousedown', mouseDown);
        geoflo.map.on('mouseup', mouseUp);

        geoflo.map.on('rotatestart', rotatePitch);
        geoflo.map.on('pitchstart', rotatePitch);
        geoflo.map.on('rotate', rotatePitch);
        geoflo.map.on('pitch', rotatePitch);

        geoflo.map.on('touchstart', touchstart);
        geoflo.map.on('touchend', touchend);
        geoflo.map.on('touchmove', touchmove);
        geoflo.map.on('touchcancel', touchcancel);

        geoflo.map.on('contextmenu', contextMenu);
        geoflo.map.on('sourcedata', sourceData);

        events.forEach(function(event) { geoflo.map.on(geoflo.id + ':' + event, fireEvent); })

        geoflo.container.addEventListener('keydown', keypress);
        geoflo.container.addEventListener('keyup', keyrelease);
        geoflo.container.addEventListener('mouseover', mouseover);
        geoflo.container.addEventListener('mouseleave', mouseleave);

        window.addEventListener("gamepadconnected", gamepadconnected);
        window.addEventListener("gamepaddisconnected", gamepaddisconnected);
        window.addEventListener("deviceorientation", handleOrientation, true);
    }

    /**
     * @function
     * @name removeEventListeners
     * @memberof module:geoflo.Events
     * @description Removes various event listeners from the map and container to prevent further interactions.
     *
     * @returns {void} This function does not return a value.
     */
    function removeEventListeners () {
        geoflo.map.off("movestart", mapMoveStart);
        geoflo.map.off("move", mapMoving);
        geoflo.map.off("moveend", mapMoveEnd);
        geoflo.map.off("mousemove", mouseMove);

        geoflo.map.off('preclick', mouseClick);
        geoflo.map.off('click', mouseClick);
        geoflo.map.off('tap', mouseClick);

        geoflo.map.off('dragstart', dragStart);
        geoflo.map.off('drag', mouseDrag);
        geoflo.map.off('mousedown', mouseDown);
        geoflo.map.off('mouseup', mouseUp);

        geoflo.map.off('rotatestart', rotatePitch);
        geoflo.map.off('pitchstart', rotatePitch);

        geoflo.map.off('touchstart', touchstart);
        geoflo.map.off('touchend', touchend);
        geoflo.map.off('touchmove', touchmove);
        geoflo.map.off('touchcancel', touchcancel);
        
        geoflo.map.off('contextmenu', contextMenu);
        geoflo.map.off('sourcedata', sourceData);

        events.forEach(function(event) { geoflo.map.off(geoflo.id + ':' + event, fireEvent); })

        geoflo.container.removeEventListener('keydown', keypress);
        geoflo.container.removeEventListener('mouseover', mouseover);
        geoflo.container.removeEventListener('mouseleave', mouseleave);

        window.removeEventListener("gamepadconnected", gamepadconnected);
        window.removeEventListener("gamepaddisconnected", gamepaddisconnected);
        window.removeEventListener("deviceorientation", handleOrientation, true);
    }

    return {
        addEventListeners,
        removeEventListeners
    }
}

export default Events;