/**
 * @mixin
 * @memberof module:geoflo
 * @name Select
 * @description This module provides the select functionality for the Geoflo application. It allows users to select features on the map by clicking on them.
 * @returns {Object} Returns the Select object.
 */
const Select = function () {
    const geoflo = this.geoflo;

    var lastKnownSelectIds = [];
    var removedFeatures = [];
    var nearFeatures = [];
    let animationRunning = false;
    let step = 0;
    var clickCoords;
    var multipleSelect;
    var selectedId;

    const dashArraySequence = [
        [0, 4, 3],
        [0.5, 4, 2.5],
        [1, 4, 2],
        [1.5, 4, 1.5],
        [2, 4, 1],
        [2.5, 4, 0.5],
        [3, 4, 0],
        [0, 0.5, 3, 3.5],
        [0, 1, 3, 3],
        [0, 1.5, 3, 2.5],
        [0, 2, 3, 2],
        [0, 2.5, 3, 1.5],
        [0, 3, 3, 1],
        [0, 3.5, 3, 0.5]
    ];

    this.id = 'select';

    /**
	 * @function
     * @memberof module:geoflo.Select
	 * @name activate
	 * @description This function activates the select feature functionality by enabling drag pan, setting buttons, and setting the active button to 'select'. It also triggers a 'select.activate' event with the provided options.
	 * @param {Object} options - The options object for activation.
	 * @param {string} [options.id] - The ID of the feature to select.
	 * @param {Object} [options.feature] - The feature object to select.
	 * @returns {boolean} Returns false if already activated.
	 */
    this.activate = function (options={}) {
        if (this.activated) return false;
        if (geoflo.currentMode.id !== this.id) return options.mode = this.id, geoflo.setMode(options);

        this.activated = true;
        geoflo.map.dragPan.enable();
        geoflo.setButtons();
        geoflo.setActiveButton('select');
        geoflo.fire('select.activate', { activated: true, options: options })
        if (this.gamepad) {}
        geoflo.map.getSource(geoflo.statics.constants.sources.SELECT).setData(turf.featureCollection([]));
        setTimeout(function(e) { e.selectFeature(options.id ? options.id : options.feature ? options.feature.id : false) }, 5, this)
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name deactivate
	 * @description This function deactivates the current feature by setting the 'activated' flag to false and triggering necessary actions.
	 * @returns {boolean} Returns false if the feature is not activated.
	 */
    this.deactivate = function () {
        if (!this.activated) return false;

        this.activated = false;
        this.deselectCurrentFeature();
        
        geoflo.setButtons();
        geoflo.fire('select.deactivate', { activated: true });
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name canHandle
	 * @description This function determines if the given mode name is equal to the SELECT mode.
	 * @param {string} modeName - The mode name to be checked.
	 * @returns {boolean} Returns true if the mode name is SELECT, false otherwise.
	 */
    this.canHandle = function (modeName) {
        return geoflo.statics.constants.modes.SELECT === modeName;
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name selectFeature
	 * @description Selects a feature by its ID, adds it to the selected features list, and optionally adds a popup.
	 * @param {string} id - The ID of the feature to be selected.
	 * @returns {Array} - An array of removed features if wantingToEdit is false, otherwise returns the removed feature.
	 */
    this.selectFeature = function (id, options={}) {
        const popup = geoflo.options.select.popup;
        const multipleSelect = options.multipleSelect || geoflo.options.select.multiple;
        
        if (!multipleSelect) geoflo.currentMode.deselectCurrentFeature();
        
        if (!id) return false;
        if (lastKnownSelectIds.indexOf(id) === -1) lastKnownSelectIds.push(id);
        //if (geoflo.hasSelection()) geoflo.forEachSelectedFeature((feature) => { });

        selectedId = id;
        removedFeatures = geoflo.hideFeatures([id]);
        geoflo.addFeaturesToSelected(removedFeatures, options);
        popup ? this.addPopup(removedFeatures) : false;
        startDashAnimation();
        geoflo.fire('feature.select', { ids: geoflo.getSelectedFeatureIds(), features: geoflo.getSelectedFeatures() });
        if (!geoflo.wantingToEdit) return removedFeatures;
        if (removedFeatures.length == 1 && id === removedFeatures[0].id) editFeature(removedFeatures[0]);
        return removedFeatures;
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name deselectCurrentFeature
	 * @description Deselects the current feature by removing its selection.
	 */
    this.deselectCurrentFeature = function () {
        const ids = geoflo.getSelectedFeatureIds();
        const features = geoflo.getSelectedFeatures();
        this.removePopup();
        stopDashAnimation();
        geoflo.removeSelection();
        geoflo.fire('feature.deselect', { ids: ids, features: features });
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name addPopup
	 * @description This function creates a popup element with the specified features and adds it to the map at the click coordinates.
	 * @param {Object} features - The features to be displayed in the popup.
	 * @param {string} features.title - The title of the popup.
	 * @param {string} features.description - The description of the popup.
	 * @param {number} features.latitude - The latitude coordinate for the popup location.
	 * @param {number} features.longitude - The longitude coordinate for the popup location.
	 */
    this.addPopup = function (features) {
        this.popupElement = buildPopup(features);

        this.popup = new mapboxgl.Popup({ closeOnClick: false })
            .setLngLat(clickCoords)
            .setDOMContent(this.popupElement)
            .addTo(geoflo.map)
            .setOffset(12);

        this.popup._container.style['margin-bottom'] = '10px'
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name removePopup
	 * @description Removes the popup element from the DOM if it exists.
	 * @return {boolean} Returns true if the popup element was successfully removed, otherwise false.
	 */
    this.removePopup = function () {
        return this.popup && this.popup.remove ? this.popup.remove() : false;
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name handleMove
	 * @description Handles the mouse move event.
	 * @param {Event} event - The event object representing the mouse move event.
	 */
    this.handleMove = function (event) {
        //geoflo.setMapClass('pointer');
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name handleClick
	 * @description Handles the click event on the map and selects features based on the event.
	 * @param {Object} event - The event object containing information about the click event.
	 * @returns {boolean} Returns false if geoflo.noSelect is true, otherwise selects features based on the event.
	 */
    this.handleClick = function (event) {
        if (geoflo.noSelect) return false;

        var features = geoflo.getRenderedDrawnFeatures(event.lngLat);

        let multipleSelect = event.originalEvent && event.originalEvent.shiftKey;
        clickCoords = [event.lngLat.lng, event.lngLat.lat];

        if (features.length > 0) {
            if (!geoflo.Layers.getSelection(features, clickCoords)) return;

            let newFeatureSet = JSON.stringify(features);
            if (newFeatureSet !== JSON.stringify(nearFeatures)) {
                nearFeatures = features;
                selectedId = null; // Reset selection tracking
            }
            
            selectFeature.call(this, nearFeatures, multipleSelect);
        } else if (!multipleSelect) {
            lastKnownSelectIds = [];
            nearFeatures = [];
            clickCoords = false;
            selectedId = false;
            this.deselectCurrentFeature();
        }
    };

	/**
	 * @function
     * @memberof module:geoflo.Select
	 * @name handleDrag
	 * @description Handles the drag event triggered by a user interaction. It sets the map class to 'grabbing' to indicate dragging.
	 * @param {Event} event - The event object representing the drag event.
	 */
    this.handleDrag = function (event) {
        //geoflo.setMapClass('grabbing');
    }



    function buildPopup (features) {
        const element = document.createElement('div');
        element.classList.add('popup-table-holder');

        const table = buildTable(features);

        element.appendChild(table);

        if (nearFeatures.length > 1) {
            var button = document.createElement('div');
            button.classList.add('popup-table-button');
            button.innerHTML = `<button> Next </button>`;
            button.addEventListener('click', selectFeature.bind(this));
            element.appendChild(button);
        }

        return element;
        
        /* const button = document.createElement('div');
        button.innerHTML = `<button class="btn btn-success btn-simple text-white" > Assign</button>`;
        element.appendChild(button);
        button.addEventListener('click', (e) => { console.log('Button clicked' + name); }); */
    };

    function buildTable (features) {
        var table = document.createElement('table');
        var properties = ['id', 'type'];

        table.style.width = '100%';
        table.style.height = '100%';
        table.setAttribute('border', '1');
        table.classList.add('popup-table');

        var tableBody = document.createElement('tbody');

        features.forEach(function(feature, index) {
            var type = feature.properties.type;

            properties.forEach(function(prop) {
                tableBody.appendChild(buildRow(prop, feature.properties[prop]));
            })

            tableBody.appendChild(buildRow('geometry', feature.geometry.type));

            if (feature.geometry.type === 'LineString') {
                geoflo.Features.addUnits(feature, 'feet');
                tableBody.appendChild(buildRow('unit', feature.geometry.unit));
                tableBody.appendChild(buildRow('units', feature.geometry.units));
            } else if (type === 'Text') {
                tableBody.appendChild(buildRow('content', feature.properties.text));
            } else if (feature.geometry.type === 'Polygon') {
                geoflo.Features.addUnits(feature, 'acres');
                tableBody.appendChild(buildRow('unit', feature.geometry.unit));
                tableBody.appendChild(buildRow('units', feature.geometry.units));
            }
        })

        table.appendChild(tableBody);
        return table;
    };

    function buildRow (header, data) {
        var tr = document.createElement('tr');
        tr.classList.add('popup-table-row');

        if (header) {
            var th = document.createElement('th');
            th.classList.add('popup-table-header');
            th.appendChild(document.createTextNode(header));
            tr.appendChild(th);
        }
        
        var td = document.createElement('td');
        td.classList.add('popup-table-data');
        td.classList.add(header);
        td.appendChild(document.createTextNode(data));
        tr.appendChild(td);

        return tr;
    }

    function selectFeature(features, multipleSelect) {
        nearFeatures = features;

        if (!nearFeatures.length) return;

        // Find index of currently selected feature
        let currentIndex = nearFeatures.findIndex(feature => feature.id === selectedId);
        let currentId = selectedId;
        let nextIndex = currentIndex;
        let loopCount = 0;  // Prevents infinite loops

        // Find the next feature that is *not* already selected
        do {
            nextIndex = (nextIndex + 1) % nearFeatures.length;
            loopCount++;

            // If we've looped through all options, break (prevents infinite loops)
            if (loopCount > nearFeatures.length) {
                console.warn("Looped through all features, no new selection available.");
                return;
            }

        } while (nearFeatures[nextIndex].properties['_selected']); // Skip selected features

        currentId = nearFeatures[nextIndex].id || nearFeatures[nextIndex].properties['id'];

        console.log("Selecting Feature:", currentId);
        geoflo.currentMode.selectFeature(currentId, { multipleSelect: multipleSelect });
    }
    
    function editFeature (feature) {
        geoflo.wantingToEdit = false;
        geoflo.setMode('edit', feature.properties.type, feature);
    }

    function animateDashArray(timestamp=0) {
        if (!animationRunning || !selectedId) return stopDashAnimation();
        const selectedFeatures = geoflo.getSelectedFeatures();
        const selectedFeature = selectedFeatures.find(feature => feature.id === selectedId);
        if (!selectedFeature || selectedFeature.geometry.type !== 'LineString') return stopDashAnimation();

        const newStep = parseInt((timestamp / 50) % dashArraySequence.length);

        if (newStep !== step) {
            geoflo.map.setPaintProperty(geoflo.id + '-line-select', 'line-dasharray', dashArraySequence[step]);
            step = newStep;
        }

        requestAnimationFrame(animateDashArray);
    }

    // Call this when a feature is selected
    function startDashAnimation(stop) {
        if (!animationRunning) {
            animationRunning = true;
            requestAnimationFrame(animateDashArray);
        }
    }

    // Call this when a feature is deselected
    function stopDashAnimation() {
        animationRunning = false;
        geoflo.map.setPaintProperty(geoflo.id + '-line-select', 'line-dasharray', [0, 0]); // Reset line to solid
    }

};

export default Select;