import {DOM} from '../../util/dom.ts';

import type {Map} from '../map.ts';
import type {ControlPosition, IControl} from './control.ts';
import type {MapSourceDataEvent, MapStyleDataEvent, MapTerrainEvent} from '../events.ts';
import type {StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
/**
 * The {@link AttributionControl} options object
 */
export type AttributionControlOptions = {
    /**
     * If `true`, the attribution control will always collapse when moving the map. If `false`,
     * force the expanded attribution control. The default is a responsive attribution that collapses when the user moves the map on maps less than 640 pixels wide.
     * **Attribution should not be collapsed if it can comfortably fit on the map. `compact` should only be used to modify default attribution when map size makes it impossible to fit default attribution and when the automatic compact resizing for default settings are not sufficient.**
     */
    compact?: boolean;
    /**
     * Attributions to show in addition to any other attributions.
     */
    customAttribution?: string | string[];
};

export const defaultAttributionControlOptions: AttributionControlOptions = {
    compact: true,
    customAttribution: '<a href="https://maplibre.org/" target="_blank">MapLibre</a>'
};

/**
 * An `AttributionControl` control presents the map's attribution information. By default, the attribution control is expanded (regardless of map width).
 * @group Markers and Controls
 * @example
 * ```ts
 * let map = new Map({attributionControl: false})
 *     .addControl(new AttributionControl({
 *         compact: true
 *     }));
 * ```
 * @see [Change the default position for attribution](https://maplibre.org/maplibre-gl-js/docs/examples/change-the-default-position-for-attribution/)
 */
export class AttributionControl implements IControl {
    options: AttributionControlOptions;
    _map: Map;
    _compact: boolean | undefined;
    _container: HTMLElement;
    _innerContainer: HTMLElement;
    _compactButton: HTMLElement;
    _editLink: HTMLAnchorElement;
    _attribHTML: string;
    styleId: string;
    styleOwner: string;

    /**
     * @param options - the attribution options
     */
    constructor(options: AttributionControlOptions = defaultAttributionControlOptions) {
        this.options = options;
    }

    getDefaultPosition(): ControlPosition {
        return 'bottom-right';
    }

    /** {@inheritDoc IControl.onAdd} */
    onAdd(map: Map): HTMLElement {
        this._map = map;
        this._compact = this.options.compact;
        this._container = DOM.create('details', 'maplibregl-ctrl maplibregl-ctrl-attrib');
        this._compactButton = DOM.create('summary', 'maplibregl-ctrl-attrib-button', this._container);
        this._compactButton.addEventListener('click', this._toggleAttribution);
        this._setElementTitle(this._compactButton, 'ToggleAttribution');
        this._innerContainer = DOM.create('div', 'maplibregl-ctrl-attrib-inner', this._container);

        this._updateAttributions();
        this._updateCompact();

        this._map.on('styledata', this._updateData);
        this._map.on('sourcedata', this._updateData);
        this._map.on('terrain', this._updateData);
        this._map.on('resize', this._updateCompact);
        this._map.on('drag', this._updateCompactMinimize);

        return this._container;
    }

    /** {@inheritDoc IControl.onRemove} */
    onRemove(): void {
        this._container.remove();

        this._map.off('styledata', this._updateData);
        this._map.off('sourcedata', this._updateData);
        this._map.off('terrain', this._updateData);
        this._map.off('resize', this._updateCompact);
        this._map.off('drag', this._updateCompactMinimize);

        this._map = undefined;
        this._compact = undefined;
        this._attribHTML = undefined;
    }

    _setElementTitle(element: HTMLElement, title: 'ToggleAttribution' | 'MapFeedback'): void {
        const str = this._map._getUIString(`AttributionControl.${title}`);
        element.title = str;
        element.setAttribute('aria-label', str);
    }

    _toggleAttribution = (): void => {
        if (this._container.classList.contains('maplibregl-compact')) {
            if (this._container.classList.contains('maplibregl-compact-show')) {
                this._container.setAttribute('open', '');
                this._container.classList.remove('maplibregl-compact-show');
            } else {
                this._container.classList.add('maplibregl-compact-show');
                this._container.removeAttribute('open');
            }
        }
    };

    _updateData = (e: MapSourceDataEvent | MapStyleDataEvent | MapTerrainEvent): void => {
        if (e && (e.type === 'terrain' || e.dataType === 'style' || (e.dataType === 'source' && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility')))) {
            this._updateAttributions();
        }
    };

    _updateAttributions(): void {
        if (!this._map.style) return;
        let attributions: string[] = [];
        if (this.options.customAttribution) {
            if (Array.isArray(this.options.customAttribution)) {
                attributions = attributions.concat(
                    this.options.customAttribution.map(attribution => {
                        if (typeof attribution !== 'string') return '';
                        return attribution;
                    })
                );
            } else if (typeof this.options.customAttribution === 'string') {
                attributions.push(this.options.customAttribution);
            }
        }

        if (this._map.style.stylesheet) {
            const stylesheet = this._map.style.stylesheet as StyleSpecification & { owner: string; id: string };
            this.styleOwner = stylesheet.owner;
            this.styleId = stylesheet.id;
        }

        const tileManagers = this._map.style.tileManagers;
        for (const id in tileManagers) {
            const tileManager = tileManagers[id];
            if (tileManager.used || tileManager.usedForTerrain) {
                const source = tileManager.getSource();
                if (source.attribution && !attributions.includes(source.attribution)) {
                    attributions.push(source.attribution);
                }
            }
        }

        // remove any entries that are whitespace
        attributions = attributions.filter(e => String(e).trim());

        // remove any entries that are substrings of another entry.
        // first sort by length so that substrings come first
        attributions.sort((a, b) => a.length - b.length);
        attributions = attributions.filter((attrib, i) => {
            for (let j = i + 1; j < attributions.length; j++) {
                if (attributions[j].includes(attrib)) { return false; }
            }
            return true;
        });

        // check if attribution string is different to minimize DOM changes
        const attribHTML = attributions.join(' | ');
        if (attribHTML === this._attribHTML) return;

        this._attribHTML = attribHTML;

        if (attributions.length) {
            this._innerContainer.innerHTML = DOM.sanitize(attribHTML);
            this._container.classList.remove('maplibregl-attrib-empty');
        } else {
            this._container.classList.add('maplibregl-attrib-empty');
        }
        this._updateCompact();
        // remove old DOM node from _editLink
        this._editLink = null;
    }

    _updateCompact = (): void => {
        if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) {
            if (this._compact === false) {
                this._container.setAttribute('open', '');
            } else if (!this._container.classList.contains('maplibregl-compact') && !this._container.classList.contains('maplibregl-attrib-empty')) {
                this._container.setAttribute('open', '');
                this._container.classList.add('maplibregl-compact', 'maplibregl-compact-show');
            }
        } else {
            this._container.setAttribute('open', '');
            if (this._container.classList.contains('maplibregl-compact')) {
                this._container.classList.remove('maplibregl-compact', 'maplibregl-compact-show');
            }
        }
    };

    _updateCompactMinimize = (): void => {
        if (this._container.classList.contains('maplibregl-compact')) {
            if (this._container.classList.contains('maplibregl-compact-show')) {
                this._container.classList.remove('maplibregl-compact-show');
            }
        }
    };
}
