/**
 * Petition signature map widget.
 * Uses Leaflet + OpenStreetMap (CartoDB Positron tiles) with Norway fylker GeoJSON.
 *
 * Features:
 * - Animated dot scatter on load (staggered, one per confirmed signer)
 * - Dots randomly placed within each fylke polygon using point-in-polygon
 * - Mouse/touch parallax: dots nudge slightly toward pointer
 * - Click fylke: popup with signer count and names
 * - Polls data endpoint every 60s, animates new dots with sprinkle effect
 * - Anonymous signers shown with mask icon instead of colored dot
 */

(function () {
  'use strict';

  const CFG = window.PETITION_MAP_CONFIG;
  if (!CFG) return;

  // Wait for Leaflet to be available
  function waitForLeaflet(cb) {
    if (window.L) { cb(); return; }
    const t = setInterval(() => { if (window.L) { clearInterval(t); cb(); } }, 50);
  }

  // ------- Color palette (from site CSS vars) -------
  const COLORS = {
    dot:        'oklch(0.618 0.1176 173.93)',  // --color-green
    dotAlt:     'oklch(0.6376 0.0739 242.84)', // --color-blue
    anonDot:    'oklch(0.55 0.04 250)',
    fylkeFill:  'oklch(0.618 0.1176 173.93)',
    fylkeFillH: 'oklch(0.50 0.12 173.93)',
    fylkeBorder:'oklch(0.45 0.08 173.93)',
  };

  // Map from GeoJSON NAME_1 to petition region key
  const NAME_TO_KEY = {
    'Agder':          'agder',
    'Akershus':       'akershus',
    'Buskerud':       'buskerud',
    'Finnmark':       'finnmark',
    'Innlandet':      'innlandet',
    'Møre og Romsdal':'more_og_romsdal',
    'Nordland':       'nordland',
    'Oslo':           'oslo',
    'Rogaland':       'rogaland',
    'Telemark':       'telemark',
    'Troms':          'troms',
    'Trøndelag':      'trondelag',
    'Vestfold':       'vestfold',
    'Vestland':       'vestland',
    'Østfold':        'ostfold',
  };

  // ------- State -------
  let map, geojsonLayer, dotLayer;
  let fylkeFeatures = {};  // key -> { layer, bounds, polygon coords }
  let currentData = null;  // last fetched data
  let knownTotal = 0;

  // ------- Geometry helpers -------

  /** Bounding box of a polygon ring [[lon,lat],...] */
  function ringBounds(ring) {
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (const [x, y] of ring) {
      if (x < minX) minX = x;
      if (x > maxX) maxX = x;
      if (y < minY) minY = y;
      if (y > maxY) maxY = y;
    }
    return { minX, minY, maxX, maxY };
  }

  /** Ray-casting point-in-polygon for a single ring */
  function pointInRing(px, py, ring) {
    let inside = false;
    const n = ring.length;
    for (let i = 0, j = n - 1; i < n; j = i++) {
      const [xi, yi] = ring[i];
      const [xj, yj] = ring[j];
      if (((yi > py) !== (yj > py)) && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) {
        inside = !inside;
      }
    }
    return inside;
  }

  /**
   * Test if point [lon, lat] is inside a GeoJSON geometry (Polygon or MultiPolygon).
   * For MultiPolygon: point must be inside an outer ring and outside all inner rings.
   */
  function pointInGeometry(lon, lat, geometry) {
    const test = (polygon) => {
      const [outer, ...holes] = polygon;
      if (!pointInRing(lon, lat, outer)) return false;
      for (const hole of holes) {
        if (pointInRing(lon, lat, hole)) return false;
      }
      return true;
    };

    if (geometry.type === 'Polygon') {
      return test(geometry.coordinates);
    }
    if (geometry.type === 'MultiPolygon') {
      return geometry.coordinates.some(test);
    }
    return false;
  }

  /**
   * Generate a random point within a GeoJSON feature's geometry.
   * Uses rejection sampling within the bounding box.
   */
  function randomPointInFeature(geometry) {
    // Build overall bounding box
    const allRings = geometry.type === 'Polygon'
      ? [geometry.coordinates[0]]
      : geometry.coordinates.map(p => p[0]);

    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (const ring of allRings) {
      const b = ringBounds(ring);
      if (b.minX < minX) minX = b.minX;
      if (b.minY < minY) minY = b.minY;
      if (b.maxX > maxX) maxX = b.maxX;
      if (b.maxY > maxY) maxY = b.maxY;
    }

    // Apply a small inset to avoid dots on the very edge
    const insetX = (maxX - minX) * 0.04;
    const insetY = (maxY - minY) * 0.04;
    minX += insetX; maxX -= insetX;
    minY += insetY; maxY -= insetY;

    // Rejection sampling (max 200 attempts)
    for (let i = 0; i < 200; i++) {
      const lon = minX + Math.random() * (maxX - minX);
      const lat = minY + Math.random() * (maxY - minY);
      if (pointInGeometry(lon, lat, geometry)) {
        return [lat, lon]; // Leaflet uses [lat, lon]
      }
    }
    // Fallback: centroid approximation
    return [(minY + maxY) / 2, (minX + maxX) / 2];
  }

  // ------- Dot rendering -------

  /** Create a Leaflet marker with a permanent tooltip label */
  function createDotMarker(latLng, signer) {
    // Invisible zero-size marker as anchor
    const icon = L.divIcon({ iconSize: [0, 0], iconAnchor: [0, 0], className: '' });
    const marker = L.marker(latLng, { icon, interactive: false, pane: 'dots' });

    if (signer.a) {
      marker.bindTooltip(
        `<img src="${CFG.anonIconUrl}" alt="" class="anon-icon"> Anonym`,
        { permanent: true, direction: 'center', className: 'signer-tip signer-tip--anon', offset: [0, 0] }
      );
    } else {
      marker.bindTooltip(
        escapeHtml(signer.n || ''),
        { permanent: true, direction: 'center', className: 'signer-tip', offset: [0, 0] }
      );
    }
    return marker;
  }

  /** Place new dots for a region (used by poll updates) */
  function renderRegionDots(signers, geometry) {
    for (const signer of signers) {
      const latLng = randomPointInFeature(geometry);
      createDotMarker(latLng, signer).addTo(dotLayer);
    }
  }

  // ------- Fylke layer -------

  function buildGeoJsonLayer(geojson) {
    geojsonLayer = L.geoJSON(geojson, {
      style: {
        color: COLORS.fylkeBorder,
        weight: 1.5,
        fillColor: COLORS.fylkeFill,
        fillOpacity: 0.08,
        opacity: 0.5,
      },
      onEachFeature(feature, layer) {
        const name = feature.properties.name;
        const key  = NAME_TO_KEY[name] ?? name.toLowerCase();

        fylkeFeatures[key] = {
          layer,
          geometry: feature.geometry,
        };

        layer.on('mouseover', function () {
          if (!this._popupOpen) {
            this.setStyle({ fillOpacity: 0.2, weight: 2.5 });
          }
        });

        layer.on('mouseout', function () {
          if (!this._popupOpen) {
            this.setStyle({ fillOpacity: 0.08, weight: 1.5 });
          }
        });

        layer.on('click', function (e) {
          showFylkePopup(key, name, e.latlng);
        });
      },
    });

    return geojsonLayer;
  }

  function showFylkePopup(key, name, latlng) {
    // Reset all layers
    geojsonLayer.resetStyle();

    const region = currentData?.regions?.[key];
    const count = region?.count ?? 0;

    const countText = count === 1 ? '1 underskrift' : `${count} underskrifter`;

    const html = `
      <div class="fylke-popup">
        <div class="fylke-popup-title">${escapeHtml(name)}</div>
        <div class="fylke-popup-count">${countText}</div>
      </div>`;

    L.popup({ maxWidth: 240, className: 'fylke-info-popup' })
      .setLatLng(latlng)
      .setContent(html)
      .openOn(map);

    // Highlight this fylke
    const fl = fylkeFeatures[key]?.layer;
    if (fl) {
      fl.setStyle({ fillOpacity: 0.25, weight: 2.5, color: COLORS.fylkeFillH });
      fl._popupOpen = true;
    }

    map.on('popupclose', function handler() {
      geojsonLayer.resetStyle();
      if (fl) fl._popupOpen = false;
      map.off('popupclose', handler);
    });
  }

  // ------- Data loading -------

  function escapeHtml(str) {
    return String(str ?? '')
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function updateCountLabel(total) {
    const el = document.getElementById('petition-map-count');
    if (!el) return;

    // Count up animation to match dot animation
    let current = 0;
    const target = total;
    const duration = 30000; // Same as dot animation
    const startTime = performance.now();

    function updateCounter(timestamp) {
      const elapsed = timestamp - startTime;
      const progress = Math.min(elapsed / duration, 1);
      current = Math.floor(progress * target);

      el.textContent = current === 1
        ? '1 person har signert så langt'
        : `${current} personer har signert så langt`;

      if (progress < 1) {
        requestAnimationFrame(updateCounter);
      }
    }

    requestAnimationFrame(updateCounter);
  }

  /** Initial render: place all dots with one smooth staggered animation */
  function initialRender(data) {
    currentData = data;
    knownTotal  = data.total ?? 0;

    updateCountLabel(knownTotal);

    // Collect all dots across all regions, then shuffle for a natural scatter
    const allDots = [];
    for (const [key, region] of Object.entries(data.regions ?? {})) {
      const feature = fylkeFeatures[key];
      if (!feature) continue;
      for (const signer of region.signers) {
        allDots.push({ signer, geometry: feature.geometry });
      }
    }

    // Fisher-Yates shuffle so dots appear from random regions, not one by one
    for (let i = allDots.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [allDots[i], allDots[j]] = [allDots[j], allDots[i]];
    }

    const duration = 30000; // ms total animation window
    const msPerDot = Math.max(1, duration / Math.max(1, allDots.length));

    allDots.forEach((dot, i) => {
      const delay = Math.round(i * msPerDot);
      setTimeout(() => {
        const latLng = randomPointInFeature(dot.geometry);
        const marker = createDotMarker(latLng, dot.signer);
        marker.addTo(dotLayer);
      }, delay);
    });
  }

  /** Poll update: find new signers and add them with sprinkle */
  function pollUpdate(newData) {
    const oldRegions = currentData?.regions ?? {};
    const newRegions = newData.regions ?? {};

    for (const [key, newRegion] of Object.entries(newRegions)) {
      const oldCount = oldRegions[key]?.count ?? 0;
      const newCount = newRegion.count ?? 0;

      if (newCount > oldCount) {
        const added = newRegion.signers.slice(0, newCount - oldCount);
        const feature = fylkeFeatures[key];
        if (feature) {
          renderRegionDots(added, feature.geometry);
        }
      }
    }

    currentData = newData;
    knownTotal  = newData.total ?? 0;
    updateCountLabel(knownTotal);
  }

  async function fetchData() {
    // Cache-bust with timestamp for polling (static file may be cached by browser)
    const sep = CFG.dataUrl.includes('?') ? '&' : '?';
    const res = await fetch(CFG.dataUrl + sep + 't=' + Date.now());
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return res.json();
  }

  // ------- Init -------

  const mapEl = document.getElementById('petition-map');
  if (!mapEl) return;

  function initMap() {
    waitForLeaflet(async function () {

    // Remove loading indicator once Leaflet starts
    const loadingEl = document.getElementById('map-loading');

    map = L.map('petition-map', {
      center: [65.5, 16],
      zoom: 5,
      zoomControl: true,
      attributionControl: true,
      scrollWheelZoom: false,
    });

    // CartoDB Positron — muted, clean, no distracting details
    L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png', {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://carto.com/">CARTO</a>',
      subdomains: 'abcd',
      maxZoom: 10,
      minZoom: 4,
    }).addTo(map);

    // Custom pane so dots render above fylke polygons
    map.createPane('dots');
    map.getPane('dots').style.zIndex = 450; // above overlay (400) but below popups (700)

    // Dot layer
    dotLayer = L.layerGroup({ pane: 'dots' }).addTo(map);

    // Load GeoJSON
    let geojson;
    try {
      const res = await fetch(CFG.geojsonUrl);
      geojson = await res.json();
    } catch (e) {
      if (loadingEl) loadingEl.textContent = 'Kunne ikke laste kart.';
      return;
    }

    buildGeoJsonLayer(geojson).addTo(map);

    if (loadingEl) loadingEl.remove();

    // Load initial data
    try {
      const data = await fetchData();
      initialRender(data);
    } catch (e) {
      updateCountLabel(0);
    }

    // Poll for updates
    setInterval(async function () {
      try {
        const data = await fetchData();
        if ((data.total ?? 0) > knownTotal) {
          pollUpdate(data);
        }
      } catch (_) { /* silent */ }
    }, CFG.pollInterval);
    });
  }

  // Only initialise when the map element enters the viewport
  if ('IntersectionObserver' in window) {
    const observer = new IntersectionObserver((entries, obs) => {
      if (entries[0].isIntersecting) {
        obs.disconnect();
        initMap();
      }
    }, { threshold: 0.1 });
    observer.observe(mapEl);
  } else {
    // Fallback for browsers without IntersectionObserver
    initMap();
  }

})();
