<script lang="ts">
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
import { MAP_URL } from '../../constants/config';
import { api } from '../../api/client';

const TILE_ZOOM = 11;
const TILE_PIXELS = 1024;
const SEASON = 's0';

import type { Map as LeafletMap, GridLayer, CircleMarker, LatLng, Coords } from 'leaflet';
type LeafletModule = typeof import('leaflet');

type PixelEventDetail = { tileX: number; tileY: number; x: number; y: number; latlng: LatLng };

const dispatch = createEventDispatcher<{ pixel: PixelEventDetail }>();

let mapContainer: HTMLDivElement;
let map: LeafletMap | null = null;
let tiles: GridLayer | null = null;
let marker: CircleMarker | null = null;
let L: LeafletModule | null = null;

export let center: [number, number] = [0, 0];

onMount(async () => {
  L = await import('leaflet');
  await import('leaflet/dist/leaflet.css');

  map = L.map(mapContainer, {
    center,
    zoom: TILE_ZOOM,
    minZoom: TILE_ZOOM,
    maxZoom: TILE_ZOOM,
    zoomControl: false,
    maxBounds: [
      [-85, -180],
      [85, 180]
    ],
    maxBoundsViscosity: 1.0
  });

  L.tileLayer(MAP_URL + '/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors',
    maxZoom: TILE_ZOOM,
    minZoom: TILE_ZOOM
  }).addTo(map);

  const gridLayer = L.gridLayer({
    minZoom: TILE_ZOOM,
    maxZoom: TILE_ZOOM,
    tileSize: 256,
    className: 'pixelated'
  }) as GridLayer & { createTile: (coords: Coords) => HTMLImageElement };

  gridLayer.createTile = (coords) => {
    const img = document.createElement('img');
    img.width = 256;
    img.height = 256;
    img.decoding = 'async';
    img.loading = 'lazy';
    img.referrerPolicy = 'no-referrer';
    img.src = api.getTileImageUrl(SEASON, coords.x, coords.y);
    return img;
  };

  tiles = gridLayer;
  tiles.addTo(map);

  map.on('click', (event) => {
    if (!L) return;
    const latlng = event.latlng;
    const info = latLngToTile(latlng.lat, latlng.lng);
    if (!info) return;

    if (!marker) {
      marker = L.circleMarker(latlng, {
        radius: 4,
        color: '#ffffff',
        weight: 2,
        fillColor: '#ed1c24',
        fillOpacity: 1
      }).addTo(map!);
    } else {
      marker.setLatLng(latlng);
    }

    dispatch('pixel', { ...info, latlng });
  });
});

onDestroy(() => {
  map?.remove();
  map = null;
  tiles = null;
  marker = null;
});

export function focusPixel(tileX: number, tileY: number, x: number, y: number) {
  if (!map || !L) return;
  const latlng = tileToLatLng(tileX, tileY, x, y);
  map.panTo(latlng, { animate: true });
  if (!marker) {
    marker = L.circleMarker(latlng, {
      radius: 4,
      color: '#ffffff',
      weight: 2,
      fillColor: '#ed1c24',
      fillOpacity: 1
    }).addTo(map);
  } else {
    marker.setLatLng(latlng);
  }
}

export function refreshTiles() {
  tiles?.redraw();
}

function latLngToTile(lat: number, lng: number) {
  const n = Math.pow(2, TILE_ZOOM);
  const xFloat = ((lng + 180) / 360) * n;
  const latRad = (lat * Math.PI) / 180;
  const yFloat = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n;
  const tileX = Math.floor(xFloat);
  const tileY = Math.floor(yFloat);
  if (tileX < 0 || tileX >= n || tileY < 0 || tileY >= n) {
    return null;
  }
  const localX = Math.floor((xFloat - tileX) * TILE_PIXELS);
  const localY = Math.floor((yFloat - tileY) * TILE_PIXELS);
  return { tileX, tileY, x: localX, y: localY };
}

function tileToLatLng(tileX: number, tileY: number, x: number, y: number) {
  const n = Math.pow(2, TILE_ZOOM);
  const fx = tileX + x / TILE_PIXELS;
  const fy = tileY + y / TILE_PIXELS;
  const lng = (fx / n) * 360 - 180;
  const latRad = Math.atan(Math.sinh(Math.PI * (1 - (2 * fy) / n)));
  const lat = (latRad * 180) / Math.PI;
  return L!.latLng(lat, lng);
}
</script>

<style>
:global(.leaflet-container) {
  width: 100%;
  height: 100%;
}
</style>

<div bind:this={mapContainer} class="w-full h-full rounded-box overflow-hidden shadow-lg border border-base-300"></div>
