---
import type { MapMarker, PlayerMarker } from '@/types/Leaflet';

import markersData from '@/components/maps/data/aldys/markers.json'
import playersData from '@/components/maps/data/aldys/players.json'
import MarkerCreator from './overlay/MarkerCreator.vue';

const markers = markersData.data as MapMarker[]
const players = playersData as PlayerMarker
---

<div class="world-wrapper">
    <div id="world"></div>

    <MarkerCreator client:load mapKey="aldys" />
</div>

<script lang="ts" define:vars={{ markers, players }} defer>

const mapHeight = 21896; //
const mapWidth = 30000; // Find these in the zoomify / original image data

// Units used for convertions to avoid changing original marker coords
const xRatio = .68447
const yRatio = .68447
const zoomRatio = .75
const xOffset = 0
const yOffset = -27.63442

/**
 * LEAFLET MAP PARAMERTERS
 */
const minZoom = 3.5
const maxZoom = 6.7 // Looks best with zoomify image
const flyToDuration = 1.2 // In seconds
const flyToZoomLevel = 6.5

const mobileZoomLevels = {
    minZoom: 2.5,
    maxZoom: 6.7
}

const inertiaDeceleration = 2000
const maxBoundsPadding = 10
const maxBoundsViscosity = .75

const mapEl = document.getElementById('world')

// Initializes the map
const map = L.map('world', {
    crs: L.CRS.Simple,
    maxZoom,
    minZoom,
    zoom: minZoom,
    zoomSnap: 0,
    zoomControl: false,
    inertiaDeceleration,
    maxBoundsViscosity
})

/**
 * MOBILE JS
 */
// Breakpoints
const desktopMQ = window.matchMedia('(min-width: 900px)')

function handleMapResize(e) {
    if (e.matches) {
        map.setMaxZoom(maxZoom)
        map.setMinZoom(minZoom)
    } else {
        map.setMaxZoom(mobileZoomLevels.maxZoom)
        map.setMinZoom(mobileZoomLevels.minZoom)
    }
}
desktopMQ.addEventListener('change', handleMapResize)

handleMapResize(desktopMQ)

// Immediately set center of map
L.control.zoom({
    position: 'bottomright',
}).addTo(map)

// Layers
const baseTileLayer = L.tileLayer.zoomify('/zoomify/aldys-nord/{g}/{z}-{x}-{y}.jpg', {
    width: mapWidth,
    height: mapHeight,
}).addTo(map)

const layerGroups = {
    players: L.layerGroup(),
    capitals: L.layerGroup(),
    cities: L.layerGroup(),
    towns: L.layerGroup(),
    landmarks: L.layerGroup(),
    quests: L.layerGroup(),
}

/**
 * Build all markers and their related info
 */
for (let i = 0; i < markers.length; i++) {
    const m = markers[i]

    const coords = [convertYToScale(m.markerCoords.y), convertXToScale(m.markerCoords.x)]

    let markerIcon
    let markerIconOverride

    switch (m.group) {
        case "capitals":
            markerIcon = L.icon({
                iconUrl: `/icon/castle.png`,
                shadowUrl: `/icon/castle-shadow.png`,
                iconSize: [25, 25],
                shadowSize: [35, 30],
                iconAnchor: [12.5, 8],
                shadowAnchor: [11, 12]
            })
            break
        
        case "cities":
            markerIcon = L.icon({
                iconUrl: `/icon/circle.png`,
                iconSize: [12, 12],
            })
            break

        case "landmarks":
            markerIcon = L.icon({
                iconUrl: `/icon/monument.png`,
                shadowUrl: `/icon/monument-shadow.png`,
                iconSize: [18, 18],
                shadowSize: [32, 14],
                iconAnchor: [12.5, 8],
                shadowAnchor: [15, 6]
            })
            break

        case "quests":
            markerIcon = L.icon({
                iconUrl: `/icon/flag.png`,
                shadowUrl: `/icon/flag-shadow.png`,
                iconSize: [20, 23],
                shadowSize: [35, 30],
                iconAnchor: [12.5, 8],
                shadowAnchor: [11, 12]
            })
            break

        case "towns":
        default:
            iconKey = "house"
            break
    }

    // Handle icon override if it's specified
    markerIconOverride = markerIcon
    if (m.icon) {
        markerIconOverride = L.icon({
            iconUrl: `/icon/${m.icon}.png`,
            shadowUrl: `/icon/${m.icon}-shadow.png`,
            iconSize: [25, 25],
            shadowSize: [35, 30],
            iconAnchor: [12.5, 8],
            shadowAnchor: [11, 12]
        })
    }

    const marker = L.marker(coords, { icon: markerIconOverride }).addTo(map)

    // Build popup content
    let popupContent = ""

    if (m.cover && m.coverAuthor && m.coverLink) {
        const figureClass = m.coverPortrait ? 'portrait' : 'landscape';
        const figureWidth = m.coverPortrait ? 210 : 420;

        popupContent += `
            <figure class="${figureClass}">
                <img src="/images/cover/${m.cover}" alt="${m.title}" width="${figureWidth}" loading="lazy" />
                <figcaption>
                    <cite>par <a href="${m.coverLink}" target="_blank">${m.coverAuthor}</a></cite>
                </figcaption>
            </figure>
        `
    }

    if (m.link) {
        popupContent += `
            <a href="${m.link}" target="_blank" class="title">
                ${m.title}
            </a>
        `
    } else {
        popupContent += `
            <strong class="title">
                ${m.title}
            </strong>
        `
    }

    if (m.description) {
        popupContent += `<p>${m.description}</p>`
    }

    marker.bindPopup(popupContent).openPopup()

    const displayTooltip = m.group === 'capitals'

    if (displayTooltip) {
        L.tooltip({ permanent: true, direction: 'top', offset: [0, 2], content: m.title, className: "capital-name", opacity: 1 }).setLatLng(coords).addTo(map)
    }

    marker.addTo(layerGroups[m.group])

    /**
     * Fly to specific marker
     */
    document.addEventListener(`fly-to-${m.title}`, (e) => {
        map.flyTo(coords, flyToZoomLevel, { duration: flyToDuration })
        setTimeout(() => {
            marker.openPopup()
        }, flyToDuration * 1000)
    })
}

/**
 * Add player's position marker
 */
if ( players && Object.keys(players).length > 0 ) {
    const playersPosition =[
        convertYToScale(players.markerCoords.y),
        convertXToScale(players.markerCoords.x)
    ]

    const playerIcon = L.icon({
        iconUrl: `/icon/location-pin.png`,
        shadowUrl: `/icon/location-pin-shadow.png`,
        iconSize: [18, 24],
        shadowSize: [20, 16],
        iconAnchor: [11, 8],
        shadowAnchor: [3, 0],
    })

    const playerMarker = L.marker(playersPosition, { icon: playerIcon }).addTo(map)
    playerMarker.addTo(layerGroups['players'])

    const popupContent = `
        <strong class="title">
            ${players.title}
        </strong>
        <p>${players.description}</p>
    `
    playerMarker.bindPopup(popupContent).openPopup()

    document.addEventListener('fly-to-players', () => {
        map.flyTo(playersPosition, flyToZoomLevel, { duration: flyToDuration })
        setTimeout(() => {
            playerMarker.openPopup()
        }, flyToDuration * 1000)
    })
}

map.fitBounds(baseTileLayer.getBounds())

/**
 * RULER
 */
/**
 * Workaround specific to leaflet version 1.8.0
 * See https://github.com/ljagis/leaflet-measure/issues/171 for details
 */
L.Control.Measure.include({
    // set icon on the capture marker
    _setCaptureMarkerIcon: function () {
        // disable autopan
        this._captureMarker.options.autoPanOnFocus = false

        // default function
        this._captureMarker.setIcon(
            L.divIcon({
                iconSize: this._map.getSize().multiplyBy(2)
            })
        )
    },
})

const rulerDistanceRatio = 2

const rulerOptions = {
    position: 'bottomleft',
    primaryLengthUnit: 'kilometers',
    secondaryLengthUnit: 'days',
    primaryAreaUnit: 'hectares',
    units: {
        kilometers: {
            factor: 0.00013 * rulerDistanceRatio,
            display: 'Kilomètres',
        },
        days: {
            factor: 0.00000325 * rulerDistanceRatio,
            display: 'Jours de marche',
            decimals: 1
        }
    }
}

const measureControl = L.control.measure(rulerOptions)
measureControl.addTo(map)

map.setMaxBounds(
    [
        [
            baseTileLayer.getBounds().getSouth() - maxBoundsPadding,
            baseTileLayer.getBounds().getWest() - maxBoundsPadding
        ],
        [
            baseTileLayer.getBounds().getNorth() + maxBoundsPadding,
            baseTileLayer.getBounds().getEast() + maxBoundsPadding
        ]
    ]
)

/**
 * CUSTOM MARKERS
 */

// List to be persisted in localStorage
let customMarkersList = []

/**
 * Function that saves the customMarkersList object into the localStorage
 * It also sends an event to refresh data on other components that use localStorage
 */
function saveCustomMarkers() {
    localStorage.setItem('custom-markers-aldys', JSON.stringify(customMarkersList))

    const refreshCustomMarkersEvent = new CustomEvent('refresh-custom-markers', { bubbles: true })

    mapEl.dispatchEvent(refreshCustomMarkersEvent)
}

// Stores last map coords on context
map.addEventListener('contextmenu', storeLastHeldPositions)
map.addEventListener('click', storeLastHeldPositions)

function storeLastHeldPositions(e) {
    localStorage.setItem('lastHeldXPosition', e.latlng.lng)
    localStorage.setItem('lastHeldYPosition', e.latlng.lat)
}

/**
 * Function that creates custom markers and places them on the map
 * @param {string} markerTitle
 */
function addCustomMarker(markerTitle) {
    const lon = Number(localStorage.getItem('lastHeldXPosition'))
    const lat = Number(localStorage.getItem('lastHeldYPosition'))

    const customMarkerIconOptions = {
        iconUrl: `/icon/push-pin.png`,
        shadowUrl: `/icon/push-pin-shadow.png`,
        iconSize: [20, 20],
        shadowSize: [30, 25],
        iconAnchor: [0, 13],
        shadowAnchor: [0, 15],
        className: "fade-in"
    }
    const customMarkerIcon = L.icon(customMarkerIconOptions)
    const customMarkerCoords = [lat, lon]

    // Sticks the custom marker on the map
    const customMarker = L.marker(customMarkerCoords, { icon: customMarkerIcon }).addTo(map)

    if (markerTitle) {
        const popupContent = `
            ${markerTitle}
        `
        customMarker.bindPopup(popupContent, { offset: [5, -5] }).openPopup()
    }

    /**
     * Register event listener to fly to this custom marker
     * TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
     */
    document.addEventListener(`fly-to-${markerTitle}`, (e) => {
        map.flyTo(customMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
        setTimeout(() => {
            customMarker.openPopup()
        }, flyToDuration * 1000)
    })

    /**
     * Register event listener to remove this custom marker
     * TODO : Refactor these functions with the loadCustomMarkersFromStorage implementation
     */
    document.addEventListener(`delete-${markerTitle}`, () => {
        removeCustomMarker(customMarker, markerTitle)
    })

    customMarkersList.push({
        lat,
        lon,
        title: markerTitle,
        icon: customMarkerIconOptions,
        group: "custom"
    })

    saveCustomMarkers()
}

// Listeners for the custom add marker event
// See MarkerCreator.vue component for more details
document.addEventListener('add-custom-marker', (e) => {
    addCustomMarker(e.detail.title)
})

/**
 * Load all custom markers onto the map
 */
function loadCustomMarkersFromStorage() {
    const customMarkersData = JSON.parse(localStorage.getItem('custom-markers-aldys'))

    if (!customMarkersData) return

    customMarkersList = customMarkersData

    for (let i = 0; i < customMarkersData.length; i++) {
        const m = customMarkersData[i];

        const savedMarkerCoords = [m.lat, m.lon]
        const savedMarker = L.marker(savedMarkerCoords, { icon: L.icon(m.icon) }).addTo(map)

        if (m.title) {
            const popupContent = `
                ${m.title}
            `
            savedMarker.bindPopup(popupContent, { offset: [5, -5] })
        }

        /**
         * Register event listener to fly to this custom marker
         * TODO : Refactor these functions with the addCustomMarker implementation
         */
        document.addEventListener(`fly-to-${m.title}`, (e) => {
            map.flyTo(savedMarkerCoords, flyToZoomLevel, { duration: flyToDuration })
            setTimeout(() => {
                savedMarker.openPopup()
            }, flyToDuration * 1000)
        })

        /**
         * Register event listener to remove this custom marker
         * TODO : Refactor these functions with the addCustomMarker implementation
         */
        document.addEventListener(`delete-${m.title}`, () => {
            removeCustomMarker(savedMarker, m.title)
        })
    }
}
loadCustomMarkersFromStorage()

/**
 * Function that deletes a specific marker from storage and removes it from the map
 * @param {Marker} marker Leaflet marker object
 * @param {string} markerTitle Key for custom marker
 */
function removeCustomMarker(marker, markerTitle) {
    marker.remove()

    customMarkersList = customMarkersList.filter(m => {
        return m.title !== markerTitle
    })

    saveCustomMarkers()
}

/**
 * URL PARAMETERS
 */

/**
 * From the leaflet setup, get a working param config
 *
 * @returns Latitude, Longitude and zoom level of the map
 */
 function getURLFromSetup() {
    const centerOfMap = map.getCenter()

    const lat = centerOfMap.lat
    const lon = centerOfMap.lng
    const zoom = parseFloat(map.getZoom().toFixed(1))

    return { lat, lon, zoom }
}

/**
 * Get leaflet coords from URL params
 * 
 * @returns URLSearchParams
 */
function getCoordsSetupFromURL() {
    const params = new URL(document.location).searchParams

    return params
}

/**
 * Check if the params in the URL can be parsed as leaflet coordinates
 * 
 * @return boolean
 */
function hasInitialSetup() {
    const params = new URL(document.location).searchParams

    return params.has('lat') && params.has('lon')
}

/**
 * Function to refresh the URL params
 * 
 * @returns void
 */
function setupToURL() {
    const setup = getURLFromSetup()

    const newParams = new URLSearchParams(setup)
    const newUrl = new URL(window.location.href)
    newUrl.search = newParams.toString()

    window.history.replaceState(null, '', newUrl.toString())
}

/**
 * Uses the URL params to setup the leaflet map
 */
function setupFromURL() {
    const setup = getCoordsSetupFromURL()

    map.setView({ lat: setup.get('lat'), lng: setup.get('lon') }, setup.get('zoom') | minZoom, { animate: false })
}

// Check if the map has initial params in the URL
// If not, just setup the default URL
if (hasInitialSetup()) {
    setupFromURL()
} else {
    setupToURL()
}

// When the map stops moving, update the URL to keep it up to date
map.on('moveend', () => {
    setupToURL()
})

/**
 * UTILITIES
 */
/**
 * Convert x coords to scale with ratio, zoom and offset
 * @param {number} x
 */
 function convertXToScale(x) {
    return (x * xRatio * zoomRatio) + xOffset
}
/**
 * Convert y coords to scale with ratio, zoom and offset
 * @param {number} y
 */
function convertYToScale(y) {
    return (y * yRatio * zoomRatio) + yOffset
}

/**
 * Convert leaflet scale to x with ratio, zoom and offset
 * 
 * It is basically the mathematically inverse function of convertXToScale()
 * @param {number} y 
 */
function convertScaleToX(x) {
    return (x - xOffset) / (xRatio * zoomRatio)
}

/**
 * Convert leaflet scale to y with ratio, zoom and offset
 * 
 * It is basically the mathematically inverse function of convertYToScale()
 * @param {number} y
 */
function convertScaleToY(y) {
    return (y - yOffset) / (yRatio * zoomRatio)
}

/**
 * DEBUGS
 */
/**
 * Get relevant map info
 */
map.addEventListener('click', (event) => {
    const lat = convertScaleToY(event.latlng.lat)
    const lon = convertScaleToX(event.latlng.lng)
    console.group('Point data')
    console.log("Latitude :", lat)
    console.log("Longitude :", lon)
    console.log("Zoom :", map.getZoom())
    console.groupEnd()
})
</script>
    
<style lang="scss">
html,
body {
    height: 100vh;
    margin: 0;
    position: relative;
}

.world-wrapper {
    position: fixed;
    inset: 0;
    z-index: 0;
    isolation: isolate;

    #world {
        height: 100%;
        width: 100%;
    }
}

.leaflet-container {
    aspect-ratio: 1551 / 1605;
}
</style>