/**
 * @mixin
 * @memberof module:geoflo
 * @name Styles
 * @description This module provides the styling functionality for the Geoflo application. It allows users to change the map style by selecting from a list of predefined styles.
 * @param {Object} options - The options object containing the styles and selected style.
 * @returns {Object} Returns the Styles object.
 */
const Styles = function (options={}) {
    const geoflo = this.geoflo;
    this.options = options;
    
    /**
	 * @function
     * @memberof module:geoflo.Styles
     * @name init
     * @description Initializes the Styles object by extending the options object with the provided options, setting the styles, defaultStyle, and event listeners.
     * @param {Object} options - The options object containing the styles and selected style.
     * @returns {Object} Returns the Styles object.
     */
    this.init = function (options={}) {
        this.options = Object.assign(this.options, options);
        this.selected = this.options.selected || "Dark";
        this.onDocumentClick = this.onDocumentClick.bind(this);
        this.events = this.options.eventListeners;
        return this;
    }

    /**
	 * @function
     * @memberof module:geoflo.Styles
     * @name select
     * @description Selects the style with the provided name by setting the map style to the style's uri.
     * @param {String} name - The name of the style to select.
     * @returns {void}
     */
    this.select = function (name) {
        if (!this.mapStyleContainer || !this.mapStyleContainer.checkVisibility()) {
            var style = this.options.styles.find(style => style.title === name);
            if (style) this.map.setStyle(style.uri), this.selected = name;
            if (this.events && this.events.onChange && this.events.onChange({ style: style }, style))
            return;
        }

        const elms = this.mapStyleContainer.getElementsByClassName(name);
        if (elms.length > 0) elms[0].click();
    }

    /**
	 * @function
     * @memberof module:geoflo.Styles
     * @name hide
     * @description Hides the style control container.
     * @returns {void}
     */
    this.hide = function () {
        if (this.controlContainer) this.controlContainer.style.display = "none";
    }

    /**
     * @function
     * @memberof module:geoflo.Styles
     * @name show
     * @description Shows the style control container.
     * @returns {void}
     */
    this.show = function () {
        if (this.controlContainer) this.controlContainer.style.display = "block";
    }

    /**
	 * @function
     * @memberof module:geoflo.Styles
     * @name getDefaultPosition
     * @description Returns the default position for the style control container.
     * @returns {String} The default position for the style control container.
     */
    this.getDefaultPosition = function () {
        const defaultPosition = "top-right";
        return defaultPosition;
    }

    /**
	 * @function
     * @memberof module:geoflo.Styles
     * @name onAdd
     * @description Adds the style control container to the map.
     * @param {Object} map - The map object to add the style control container to.
     * @returns {Object} The style control container.
     * @throws {Error} Throws an error if the map object is not provided.
     */
    this.onAdd = function (map) {
        this.map = map;
        this.controlContainer = document.createElement("div");
        this.controlContainer.classList.add("mapboxgl-ctrl");
        this.controlContainer.classList.add("mapboxgl-ctrl-group");
        this.mapStyleContainer = document.createElement("div");
        this.styleButton = document.createElement("button");
        this.styleButton.type = "button";
        this.mapStyleContainer.classList.add("mapboxgl-style-list");

        for (const style of this.options.styles) {
            const styleElement = document.createElement("button");

            styleElement.type = "button";
            styleElement.classList.add(style.title.replace(/[^a-z0-9-]/gi, '_'));
            styleElement.dataset.uri = JSON.stringify(style.uri);

            styleElement.addEventListener("click", event => {
                const srcElement = event.target || event.srcElement;
                this.closeModal();
                if (srcElement.classList.contains("active")) return;
                if (this.events && this.events.onOpen && this.events.onOpen(event)) return;
                const style = JSON.parse(srcElement.dataset.uri);
                this.map.setStyle(style);
                const elms = this.mapStyleContainer.getElementsByClassName("active");
                while (elms[0]) elms[0].classList.remove("active");
                srcElement.classList.add("active");
                if (this.events && this.events.onChange && this.events.onChange(event, style)) return;
            });

            if (style.title === this.selected) {
                styleElement.classList.add("active");
            }

            this.mapStyleContainer.appendChild(styleElement);
        }

        this.styleButton.classList.add("mapboxgl-ctrl-icon");
        this.styleButton.classList.add("mapboxgl-style-switcher");

        this.styleButton.addEventListener("click", event => {
            if (this.events && this.events.onSelect && this.events.onSelect(event)) return;
            this.openModal();
        });

        document.addEventListener("click", this.onDocumentClick);

        this.controlContainer.appendChild(this.styleButton);
        this.controlContainer.appendChild(this.mapStyleContainer);
        
        this.closeModal();
        return this.controlContainer;
    }

    /**
	 * @function
     * @memberof module:geoflo.Styles
     * @name onRemove
     * @description Removes the style control container from the map.
     * @returns {void}
     * @throws {Error} Throws an error if the control container, control container parent node, map, or style button is not provided.
     * @throws {Error} Throws an error if the style button event listener is not removed.
     * @throws {Error} Throws an error if the document event listener is not removed.
     */
    this.onRemove = function () {
        if (!this.controlContainer || !this.controlContainer.parentNode || !this.map || !this.styleButton) {
            return;
        }
        this.styleButton.removeEventListener("click", this.onDocumentClick);
        this.controlContainer.parentNode.removeChild(this.controlContainer);
        document.removeEventListener("click", this.onDocumentClick);
        this.map = undefined;
    }

    /**
	 * @function
     * @memberof module:geoflo.Styles
     * @name closeModal
     * @description Closes the style control container.
     * @returns {void}
     */
    this.closeModal = function () {
        if (this.mapStyleContainer && this.styleButton) {
            this.mapStyleContainer.style.display = "none";
            this.styleButton.style.display = "block";
        }
    }

    /**
     * @function
     * @memberof module:geoflo.Styles
     * @name openModal
     * @description Opens the style control container.
     * @returns {void}
     */
    this.openModal = function () {
        if (this.mapStyleContainer && this.styleButton) {
            this.mapStyleContainer.style.display = "block";
            this.styleButton.style.display = "none";
        }
    }

    this.onDocumentClick = function (event) {
        if (this.controlContainer && !this.controlContainer.contains(event.target)) this.closeModal();
    }

    this.init();
};

export default Styles;