<template>
    <div class="relative w-full">
        <!-- Map Container -->
        <div 
            :id="mapId" 
            class="relative h-[600px] w-full bg-gray-50 dark:bg-gray-900 rounded-xl overflow-hidden"
        >
            <div class="relative w-full h-full">
                <ZoomControlComponent ref="zoom" :mapId="mapId" />
                <DrawControlComponent ref="draw" :mapId="mapId" :southWest="southWest" :northEast="northEast" />
            </div>
        </div>
        
        <!-- Validate Button -->
        <div class="absolute left-4 top-44 z-[1000] select-none">
            <button
                class="group flex items-center justify-center relative overflow-visible outline-none font-semibold text-sm transition-all duration-300 ease-in-out
                       w-10 h-10 rounded-xl border-2 shadow-md
                       focus-visible:outline focus-visible:outline-3 focus-visible:outline-offset-2"
                :class="[
                    validBoundingBox 
                        ? 'bg-gradient-to-br from-lime-500 to-lime-700 border-lime-700 text-white shadow-lime-500/40 cursor-default gap-2 w-auto px-4 focus-visible:outline-lime-500/50' 
                        : 'bg-white dark:bg-gray-800 border-red-500 text-red-600 dark:text-red-400 gap-0 hover:bg-red-50 dark:hover:bg-gray-900 hover:border-red-500 hover:text-red-700 dark:hover:text-red-300 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-red-500/30 hover:w-auto hover:px-4 hover:gap-2 focus-visible:outline-red-500/50'
                ]"
                type="button"
                @click.stop.prevent="validateBoundingBox"
                :aria-label="validBoundingBox ? 'Bounding box is valid' : 'Validate bounding box'"
            >
                <!-- Icon -->
                <BaseIcon 
                    v-if="mdiMapCheckOutline" 
                    :path="mdiMapCheckOutline" 
                    :size="20"
                    :class="[
                        'transition-transform duration-300',
                        validBoundingBox && 'animate-[checkPulse_2s_ease-in-out_infinite]'
                    ]"
                />
                
                <!-- Status badge -->
                <span 
                    class="text-xs font-bold uppercase tracking-wider whitespace-nowrap transition-all duration-300 overflow-hidden"
                    :class="[
                        validBoundingBox 
                            ? 'max-w-[100px] opacity-100 text-white drop-shadow' 
                            : 'max-w-0 opacity-0 group-hover:max-w-[100px] group-hover:opacity-100'
                    ]"
                >
                    {{ label }}
                </span>

                <!-- Pulse animation for valid state -->
                <span 
                    v-if="validBoundingBox"
                    class="absolute top-1/2 left-1/2 w-full h-full bg-white/30 rounded-xl -translate-x-1/2 -translate-y-1/2 animate-[pulse_2s_ease-out_infinite] pointer-events-none"
                ></span>

                <!-- Ripple effect on click -->
                <span 
                    class="absolute top-1/2 left-1/2 w-0 h-0 rounded-full bg-white/30 -translate-x-1/2 -translate-y-1/2 transition-all duration-[600ms] active:w-[300px] active:h-[300px]"
                ></span>

                <!-- Tooltip -->
                <span 
                    v-if="!validBoundingBox"
                    class="absolute left-[calc(100%+0.5rem)] top-1/2 -translate-y-1/2 px-3 py-1.5 bg-gray-800 text-white text-xs rounded-md whitespace-nowrap opacity-0 pointer-events-none transition-opacity duration-200 z-[1001] group-hover:opacity-100 group-hover:animate-[fadeInTooltip_0.2s_ease_0.5s_forwards]"
                >
                    Click to validate
                </span>
            </button>
        </div>
    </div>
</template>

<script lang="ts">
import { EventEmitter } from './EventEmitter';
import { Component, Vue, Prop, Ref } from 'vue-facing-decorator';
import { Map } from 'leaflet/src/map/index';
import { Control } from 'leaflet/src/control/Control';
import { LatLngBoundsExpression, LatLngBounds } from 'leaflet/src/geo/LatLngBounds';
import { LatLng } from 'leaflet';
import { tileLayerWMS } from 'leaflet/src/layer/tile/TileLayer.WMS';
import { Attribution } from 'leaflet/src/control/Control.Attribution';
import { mdiMapCheckOutline } from '@mdi/js';
import BaseIcon from '@/Components/BaseIcon.vue';

import { MapOptions } from './MapOptions';
import { LayerOptions, LayerMap } from './LayerOptions';
import { MapService } from '@/Stores/map.service';
import { ZoomControlComponent } from './zoom.component.vue';
import { DrawControlComponent } from './draw.component.vue';
import { Coverage } from '@/Dataset';
import { canvas } from 'leaflet/src/layer/vector/Canvas';
import { svg } from 'leaflet/src/layer/vector/SVG';
import Notification from '@/utils/toast';

Map.include({
    getRenderer: function (layer) {
        var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;

        if (!renderer) {
            renderer = this._renderer = this._createRenderer();
        }

        if (!this.hasLayer(renderer)) {
            this.addLayer(renderer);
        }
        return renderer;
    },

    _getPaneRenderer: function (name) {
        if (name === 'overlayPane' || name === undefined) {
            return false;
        }

        var renderer = this._paneRenderers[name];
        if (renderer === undefined) {
            renderer = this._createRenderer({ pane: name });
            this._paneRenderers[name] = renderer;
        }
        return renderer;
    },

    _createRenderer: function (options) {
        return (this.options.preferCanvas && canvas(options)) || svg(options);
    },
});

const DEFAULT_BASE_LAYER_NAME = 'BaseLayer';
const DEFAULT_BASE_LAYER_ATTRIBUTION = '&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors';

@Component({
    name: 'MapComponent',
    components: {
        ZoomControlComponent,
        DrawControlComponent,
        BaseIcon,
    },
})
export class MapComponent extends Vue {
    @Prop()
    public mapId: string;

    @Prop()
    public mapOptions: MapOptions;

    @Prop()
    public coverage: Coverage;

    @Prop({ default: null })
    public fitBounds: LatLngBoundsExpression;

    @Prop()
    public zoomControlOptions: Control.ZoomOptions;

    @Prop()
    public baseMaps: LayerMap;

    get label(): string {
        return this.validBoundingBox ? 'Valid' : 'Invalid';
    }

    get validBoundingBox(): boolean {
        let isValidNumber =
            (typeof this.coverage.x_min === 'number' || !isNaN(Number(this.coverage.x_min))) &&
            (typeof this.coverage.y_min === 'number' || !isNaN(Number(this.coverage.y_min))) &&
            (typeof this.coverage.x_max === 'number' || !isNaN(Number(this.coverage.x_max))) &&
            (typeof this.coverage.y_max === 'number' || !isNaN(Number(this.coverage.y_max)));

        let isBoundValid = true;
        if (isValidNumber) {
            let _southWest: LatLng = new LatLng(this.coverage.y_min!, this.coverage.x_min!);
            let _northEast: LatLng = new LatLng(this.coverage.y_max!, this.coverage.x_max!);
            const bounds = new LatLngBounds(this.southWest, this.northEast);
            if (!bounds.isValid() || !(_southWest.lat < _northEast.lat && _southWest.lng < _northEast.lng)) {
                isBoundValid = false;
            }
        }
        return isValidNumber && isBoundValid;
    }

    @Ref('zoom') 
    private zoom: ZoomControlComponent;
    
    @Ref('draw') 
    private draw: DrawControlComponent;

    mapService = MapService();

    mdiMapCheckOutline = mdiMapCheckOutline;
    southWest: LatLng;
    northEast: LatLng;

    public onMapInitializedEvent: EventEmitter<string> = new EventEmitter<string>();

    public map!: Map;

    validateBoundingBox() {
        if (this.validBoundingBox == false) {
            this.draw.removeShape();
            Notification.showError('Bounds are not valid.');
            return;
        }
        this.map.control && this.map.control.disable();
        var _this = this;
 
        let _southWest: LatLng = new LatLng(this.coverage.y_min!, this.coverage.x_min!);
        let _northEast: LatLng = new LatLng(this.coverage.y_max!, this.coverage.x_max!);
 
        const bounds = new LatLngBounds(this.southWest, this.northEast);
        if (!bounds.isValid() || !(_southWest.lat < _northEast.lat && _southWest.lng < _northEast.lng)) {
            this.draw.removeShape();
            Notification.showTemporary('Bounds are not valid.');
        } else {
            try {
                this.draw.drawShape(_southWest, _northEast);
                _this.map.fitBounds(bounds);

                Notification.showSuccess('Valid bounding box');
            } catch (err) {
                Notification.showTemporary('An error occurred while drawing bounding box');
                throw err;
            }
        }
    }

    mounted(): void {
        this.initMap();
    }

    unmounted() {
        this.map.off('zoomend zoomlevelschange');
    }

    protected initMap(): void {
        let map: Map = (this.map = new Map(this.mapId, this.mapOptions));
        this.mapService.setMap(this.mapId, map);
        map.scrollWheelZoom.disable();

        this.onMapInitializedEvent.emit(this.mapId);
        this.addBaseMap();

        const attributionControl = new Attribution().addTo(this.map);
        attributionControl.setPrefix(false);

        map.on(
            'Draw.Event.CREATED',
            (event: any) => {
                var layer = event.layer;
                var bounds = layer.getBounds();
                this.coverage.x_min = bounds.getSouthWest().lng;
                this.coverage.y_min = bounds.getSouthWest().lat;
                this.coverage.x_max = bounds.getNorthEast().lng;
                this.coverage.y_max = bounds.getNorthEast().lat;
            },
        );

        this.map.on('zoomend zoomlevelschange', this.zoom.updateDisabled, this.zoom);

        if (this.coverage.x_min && this.coverage.y_min) {
            this.southWest = new LatLng(this.coverage.y_min!, this.coverage.x_min!);
        } else {
            this.southWest = new LatLng(46.5, 9.9);
        }
        if (this.coverage.x_max && this.coverage.y_max) {
            this.northEast = new LatLng(this.coverage.y_max!, this.coverage.x_max!);
        } else {
            this.northEast = new LatLng(48.9, 16.9);
        }
        const bounds = new LatLngBounds(this.southWest, this.northEast);
        map.fitBounds(bounds);

        if (this.coverage.x_min && this.coverage.x_max && this.coverage.y_min && this.coverage.y_max) {
            let _southWest: LatLng;
            let _northEast: LatLng;
            if (this.coverage.x_min && this.coverage.y_min) {
                _southWest = new LatLng(this.coverage.y_min, this.coverage.x_min);
            }
            if (this.coverage.x_max && this.coverage.y_max) {
                _northEast = new LatLng(this.coverage.y_max, this.coverage.x_max);
            }
            this.draw.drawShape(_southWest, _northEast);
        }
    }

    private addBaseMap(layerOptions?: LayerOptions): void {
        if (this.map) {
            if (!this.baseMaps || this.baseMaps.size === 0) {
                let osmGgray = tileLayerWMS('https://ows.terrestris.de/osm-gray/service', {
                    format: 'image/png',
                    attribution: DEFAULT_BASE_LAYER_ATTRIBUTION,
                    layers: 'OSM-WMS',
                });
                layerOptions = {
                    label: DEFAULT_BASE_LAYER_NAME,
                    visible: true,
                    layer: osmGgray,
                };
                layerOptions.layer.addTo(this.map);
            }
        }
    }
}
export default MapComponent;
</script>

<style scoped>
/* Leaflet container - only what can't be done with Tailwind */
:deep(.leaflet-container) {
    height: 600px;
    width: 100%;
    background: transparent;
}

:deep(.leaflet-container .leaflet-pane) {
    z-index: 30 !important;
}

/* Custom animations */
@keyframes checkPulse {
    0%, 100% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.1);
    }
}

@keyframes pulse {
    0% {
        transform: translate(-50%, -50%) scale(0);
        opacity: 1;
    }
    100% {
        transform: translate(-50%, -50%) scale(1.5);
        opacity: 0;
    }
}

@keyframes fadeInTooltip {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

/* Responsive adjustments */
@media (max-width: 768px) {
    :deep(.leaflet-container) {
        height: 100%;
    }
}

@media (max-width: 640px) {
    :deep(.leaflet-container) {
        height: 100%;
    }
}
</style>