'use strict';

// ── Configuration ────────────────────────────────────────────────────────────
const MAPTILER_KEY_STORAGE = 'droneMapUK_maptilerKey';

const STYLES = {
  outdoor:   key => `https://api.maptiler.com/maps/outdoor-v2/style.json?key=${key}`,
  satellite: key => `https://api.maptiler.com/maps/satellite/style.json?key=${key}`,
  streets:   key => `https://api.maptiler.com/maps/streets-v2/style.json?key=${key}`,
};

// ── UK licensed aerodromes (FRZ = 5 km radius from reference point, approx.) ─
const AERODROMES = [
  { name: 'Heathrow',              icao: 'EGLL', lat: 51.4775,  lon: -0.4614  },
  { name: 'Gatwick',               icao: 'EGKK', lat: 51.1481,  lon: -0.1903  },
  { name: 'Stansted',              icao: 'EGSS', lat: 51.8850,  lon:  0.2350  },
  { name: 'Luton',                 icao: 'EGGW', lat: 51.8747,  lon: -0.3683  },
  { name: 'London City',           icao: 'EGLC', lat: 51.5048,  lon:  0.0505  },
  { name: 'Manchester',            icao: 'EGCC', lat: 53.3537,  lon: -2.2750  },
  { name: 'Birmingham',            icao: 'EGBB', lat: 52.4538,  lon: -1.7480  },
  { name: 'Edinburgh',             icao: 'EGPH', lat: 55.9500,  lon: -3.3625  },
  { name: 'Glasgow',               icao: 'EGPF', lat: 55.8719,  lon: -4.4331  },
  { name: 'Bristol',               icao: 'EGGD', lat: 51.3827,  lon: -2.7190  },
  { name: 'Leeds Bradford',        icao: 'EGNM', lat: 53.8659,  lon: -1.6606  },
  { name: 'Newcastle',             icao: 'EGNT', lat: 55.0375,  lon: -1.6917  },
  { name: 'Liverpool',             icao: 'EGGP', lat: 53.3336,  lon: -2.8497  },
  { name: 'East Midlands',         icao: 'EGNX', lat: 52.8311,  lon: -1.3281  },
  { name: 'Southampton',           icao: 'EGHI', lat: 50.9503,  lon: -1.3568  },
  { name: 'Aberdeen',              icao: 'EGPD', lat: 57.2019,  lon: -2.1978  },
  { name: 'Cardiff',               icao: 'EGFF', lat: 51.3967,  lon: -3.3433  },
  { name: 'Belfast International', icao: 'EGAA', lat: 54.6575,  lon: -6.2158  },
  { name: 'Belfast City',          icao: 'EGAC', lat: 54.6181,  lon: -5.8725  },
  { name: 'Exeter',                icao: 'EGTE', lat: 50.7344,  lon: -3.4139  },
  { name: 'Norwich',               icao: 'EGSH', lat: 52.6758,  lon:  1.2828  },
  { name: 'Bournemouth',           icao: 'EGHH', lat: 50.7800,  lon: -1.8425  },
  { name: 'Durham Tees Valley',    icao: 'EGNV', lat: 54.5092,  lon: -1.4294  },
  { name: 'Humberside',            icao: 'EGNJ', lat: 53.5744,  lon: -0.3508  },
  { name: 'Doncaster Sheffield',   icao: 'EGCN', lat: 53.4805,  lon: -1.0106  },
  { name: 'Inverness',             icao: 'EGPE', lat: 57.5425,  lon: -4.0475  },
  { name: 'Prestwick',             icao: 'EGPK', lat: 55.5094,  lon: -4.5867  },
  { name: 'Dundee',                icao: 'EGPN', lat: 56.4525,  lon: -3.0258  },
  { name: 'Cambridge',             icao: 'EGSC', lat: 52.2050,  lon:  0.1750  },
  { name: 'Oxford',                icao: 'EGTK', lat: 51.8369,  lon: -1.3200  },
  { name: 'Farnborough',           icao: 'EGLF', lat: 51.2778,  lon: -0.7764  },
  { name: 'Biggin Hill',           icao: 'EGKB', lat: 51.3308,  lon:  0.0325  },
  { name: 'Southend',              icao: 'EGMC', lat: 51.5714,  lon:  0.6956  },
  { name: 'Lydd',                  icao: 'EGMD', lat: 50.9561,  lon:  0.9392  },
  { name: 'Shoreham',              icao: 'EGKA', lat: 50.8356,  lon: -0.2972  },
  { name: 'Gloucester',            icao: 'EGBJ', lat: 51.8942,  lon: -2.1672  },
  { name: 'Newquay',               icao: 'EGHQ', lat: 50.4406,  lon: -4.9954  },
  { name: 'Ronaldsway (IoM)',      icao: 'EGNS', lat: 54.0833,  lon: -4.6239  },
  { name: 'Jersey',                icao: 'EGJJ', lat: 49.2078,  lon: -2.1950  },
  { name: 'Guernsey',              icao: 'EGJB', lat: 49.4350,  lon: -2.6019  },
];

// ── Royal Parks (London) ─────────────────────────────────────────────────────
const ROYAL_PARKS = [
  { name: 'Hyde Park',           lat: 51.5073, lon: -0.1657, radiusKm: 0.72 },
  { name: 'Kensington Gardens',  lat: 51.5057, lon: -0.1882, radiusKm: 0.62 },
  { name: "St James's Park",     lat: 51.5024, lon: -0.1347, radiusKm: 0.42 },
  { name: 'Green Park',          lat: 51.5027, lon: -0.1432, radiusKm: 0.32 },
  { name: "Regent's Park",       lat: 51.5313, lon: -0.1564, radiusKm: 0.85 },
  { name: 'Richmond Park',       lat: 51.4422, lon: -0.2781, radiusKm: 1.80 },
  { name: 'Bushy Park',          lat: 51.4153, lon: -0.3382, radiusKm: 1.20 },
  { name: 'Greenwich Park',      lat: 51.4769, lon:  0.0003, radiusKm: 0.55 },
];

// ── Geometry helpers ─────────────────────────────────────────────────────────
function makeCircle(lon, lat, radiusKm, steps = 64) {
  const R = 6371;
  const d = radiusKm / R;
  const latR = lat * Math.PI / 180;
  const lonR = lon * Math.PI / 180;
  const coords = [];
  for (let i = 0; i <= steps; i++) {
    const bearing = (i / steps) * 2 * Math.PI;
    const pLat = Math.asin(
      Math.sin(latR) * Math.cos(d) + Math.cos(latR) * Math.sin(d) * Math.cos(bearing)
    );
    const pLon = lonR + Math.atan2(
      Math.sin(bearing) * Math.sin(d) * Math.cos(latR),
      Math.cos(d) - Math.sin(latR) * Math.sin(pLat)
    );
    coords.push([pLon * 180 / Math.PI, pLat * 180 / Math.PI]);
  }
  return coords;
}

const emptyFC = () => ({ type: 'FeatureCollection', features: [] });

function buildFRZGeoJSON() {
  return {
    type: 'FeatureCollection',
    features: AERODROMES.map(a => ({
      type: 'Feature',
      properties: { name: `${a.name} (${a.icao})`, icao: a.icao, type: 'FRZ', radiusKm: 5 },
      geometry: { type: 'Polygon', coordinates: [makeCircle(a.lon, a.lat, 5)] },
    })),
  };
}

function buildRoyalParksGeoJSON() {
  return {
    type: 'FeatureCollection',
    features: ROYAL_PARKS.map(p => ({
      type: 'Feature',
      properties: { name: p.name, type: 'Royal Park' },
      geometry: { type: 'Polygon', coordinates: [makeCircle(p.lon, p.lat, p.radiusKm)] },
    })),
  };
}

// ── National Parks — OS Open Geography Portal ─────────────────────────────────
async function fetchNationalParks() {
  const url =
    'https://services1.arcgis.com/ESMARspQHYMw9BZ9/arcgis/rest/services/' +
    'National_Parks_December_2023_GB_BFE/FeatureServer/0/query' +
    '?where=1%3D1&outFields=NPARK23NM&outSR=4326&f=geojson&geometryPrecision=5&resultRecordCount=20';
  const res = await fetch(url);
  if (!res.ok) throw new Error(res.status);
  return res.json();
}

// ── SSSIs — Natural England ArcGIS (England, viewport-lazy) ──────────────────
// Loads up to 200 features intersecting the current map extent; debounced on moveend.
const SSSI_MIN_ZOOM = 8;
let sssiTimer = null;
let sssiFileData = null; // user-loaded Scotland/Wales data merged with England API data

async function loadSSSIViewport() {
  if (!map || !map.getSource('sssi') || !layerVisibility['sssi']) return;

  if (map.getZoom() < SSSI_MIN_ZOOM) {
    setSSSIStatus('Zoom in to zoom ≥ 8 to see England SSSIs');
    map.getSource('sssi').setData(sssiFileData || emptyFC());
    return;
  }

  setSSSIStatus('Loading…');
  const b = map.getBounds();
  const bbox = `${b.getWest()},${b.getSouth()},${b.getEast()},${b.getNorth()}`;
  const url =
    'https://services.arcgis.com/JJzESW51TqeY9uat/arcgis/rest/services/SSSI_England/FeatureServer/0/query' +
    `?geometry=${bbox}&geometryType=esriGeometryEnvelope&inSR=4326&spatialRel=esriSpatialRelIntersects` +
    `&outFields=SSSI_NAME,STATUS,HA_AREA&outSR=4326&f=geojson&resultRecordCount=200&geometryPrecision=4`;

  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(res.status);
    const data = await res.json();
    if (!map.getSource('sssi')) return;
    // Merge England API data with any user-loaded file data
    const merged = {
      type: 'FeatureCollection',
      features: [
        ...data.features,
        ...(sssiFileData ? sssiFileData.features : []),
      ],
    };
    map.getSource('sssi').setData(merged);
    const count = data.features.length;
    setSSSIStatus(`${count} England SSSIs visible${count === 200 ? ' (limit)' : ''}${sssiFileData ? ' + file data' : ''}`);
  } catch (err) {
    console.warn('SSSI England load failed:', err);
    setSSSIStatus('England load failed — load file manually');
    if (sssiFileData && map.getSource('sssi')) map.getSource('sssi').setData(sssiFileData);
  }
}

function scheduleSSSIRefresh() {
  clearTimeout(sssiTimer);
  sssiTimer = setTimeout(loadSSSIViewport, 450);
}

function setSSSIStatus(msg) {
  const el = document.getElementById('sssi-status');
  if (el) el.textContent = msg;
}

function setNATSStatus(msg) {
  const el = document.getElementById('nats-status');
  if (el) el.textContent = msg;
}

// ── NATS data path (served from same local directory) ─────────────────────────
const NATS_XML_PATH = 'EG_AIP_DS_20260514_XML/EG_AIP_DS_FULL_20260514.xml';

// ── App state ─────────────────────────────────────────────────────────────────
let map = null;
let apiKey = null;
let currentStyle = 'outdoor';
let layerVisibility = {
  nats: true,
  frz: true, danger: true, military: true,
  'national-parks': true, 'royal-parks': true,
  sssi: true,
  forestry: false,
};
// User-loaded GeoJSON persisted across style switches
const fileData = {
  danger:    null,
  military:  null,
  forestry:  null,
};
let natsGeoJSON = null; // persists across style switches
let natsLoaded = false; // prevents re-fetching on style switches

// Per-type visibility for NATS layer (all on by default)
const natsTypeFilter = {
  R: true, P: true, CTR: true,
  CTA: true, TMA: true, RAS: true, 'OTHER:TMZ': true,
};

let is3DEnabled = false;
let hillshadeEnabled = false;

// Location tracking
let locationWatcher = null;
let locationMarker = null;
let isFirstFix = true;

// ── Hillshade ─────────────────────────────────────────────────────────────────
function addHillshade() {
  if (map.getLayer('hillshade')) return;
  // Insert below the first airspace fill layer so restrictions stay on top
  const beforeId = map.getLayer('frz-fill') ? 'frz-fill' : undefined;
  map.addLayer({
    id: 'hillshade',
    type: 'hillshade',
    source: 'terrain-dem',
    paint: {
      'hillshade-exaggeration': 0.5,
      'hillshade-shadow-color': '#4a3728',
      'hillshade-highlight-color': '#ffffff',
      'hillshade-accent-color': '#3d2b1f',
      'hillshade-illumination-direction': 335,
      'hillshade-illumination-anchor': 'viewport',
    },
  }, beforeId);
}

function removeHillshade() {
  if (map.getLayer('hillshade')) map.removeLayer('hillshade');
}

// ── 3D terrain ────────────────────────────────────────────────────────────────
function addTerrainSource() {
  if (!map.getSource('terrain-dem')) {
    map.addSource('terrain-dem', {
      type: 'raster-dem',
      url: `https://api.maptiler.com/tiles/terrain-rgb-v2/tiles.json?key=${apiKey}`,
      tileSize: 256,
    });
  }
}

function enable3D() {
  addTerrainSource();
  map.setTerrain({ source: 'terrain-dem', exaggeration: 1.4 });
  if (!map.getLayer('sky')) {
    try {
      map.addLayer({
        id: 'sky', type: 'sky',
        paint: {
          'sky-type': 'atmosphere',
          'sky-atmosphere-sun': [0.0, 90.0],
          'sky-atmosphere-sun-intensity': 15,
        },
      });
    } catch { /* style may already include sky */ }
  }
  map.easeTo({ pitch: 50, duration: 600 });
}

function disable3D() {
  map.setTerrain(null);
  try { if (map.getLayer('sky')) map.removeLayer('sky'); } catch { /* ignore */ }
  map.easeTo({ pitch: 0, duration: 400 });
}

// ── NATS type filter ──────────────────────────────────────────────────────────
function applyNATSFilter() {
  if (!map) return;
  const enabled = Object.keys(natsTypeFilter).filter(k => natsTypeFilter[k]);
  const f = enabled.length === 0
    ? ['==', 1, 0]
    : ['match', ['get', 'type'], enabled, true, false];
  ['nats-fill', 'nats-line', 'nats-label'].forEach(id => {
    if (map.getLayer(id)) map.setFilter(id, f);
  });
}

// ── Layer definitions ─────────────────────────────────────────────────────────
function addAllLayers() {
  // Defensive cleanup — prevents "source already exists" crash on rapid style switches
  // or if this fires twice (MapLibre can fire both 'load' and 'style.load' in some versions).
  [
    'nats-label','nats-line','nats-fill',
    'frz-label','frz-line','frz-fill',
    'danger-line','danger-fill',
    'military-line','military-fill',
    'national-parks-line','national-parks-fill',
    'royal-parks-line','royal-parks-fill',
    'sssi-line','sssi-fill',
    'forestry-line','forestry-fill',
    'location-accuracy-line','location-accuracy-fill',
    'hillshade',
  ].forEach(id => { try { if (map.getLayer(id)) map.removeLayer(id); } catch (_) {} });
  [
    'nats','frz','danger','military',
    'national-parks','royal-parks','sssi','forestry','location-accuracy',
  ].forEach(id => { try { if (map.getSource(id)) map.removeSource(id); } catch (_) {} });

  // 1 — Terrain DEM source (shared by hillshade and 3D)
  addTerrainSource();

  // 2 — Hillshade sits below everything else
  if (hillshadeEnabled) addHillshade();

  // 3 — NATS AIP airspace — filter applied dynamically via applyNATSFilter()
  map.addSource('nats', { type: 'geojson', data: natsGeoJSON || emptyFC() });
  map.addLayer({ id: 'nats-fill', type: 'fill', source: 'nats',
    paint: {
      'fill-color': [
        'match', ['get', 'type'],
        ['R', 'P'],     '#ef4444',
        ['CTR'],        '#facc15',
        ['CTA', 'TMA'], '#60a5fa',
        ['RAS'],        '#818cf8',
        ['OTHER:TMZ'],  '#c084fc',
        '#94a3b8',
      ],
      'fill-opacity': 0.15,
    },
  });
  map.addLayer({ id: 'nats-line', type: 'line', source: 'nats',
    paint: {
      'line-color': [
        'match', ['get', 'type'],
        ['R', 'P'],     '#ef4444',
        ['CTR'],        '#facc15',
        ['CTA', 'TMA'], '#60a5fa',
        ['RAS'],        '#818cf8',
        ['OTHER:TMZ'],  '#c084fc',
        '#94a3b8',
      ],
      'line-width': 1.2, 'line-opacity': 0.75,
    },
  });
  map.addLayer({ id: 'nats-label', type: 'symbol', source: 'nats', minzoom: 8,
    layout: {
      'text-field': ['coalesce', ['get', 'designator'], ['get', 'name']],
      'text-size': 9, 'text-anchor': 'center',
      'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
    },
    paint: { 'text-color': '#fff', 'text-halo-color': '#000', 'text-halo-width': 1.2 },
  });
  applyNATSFilter(); // apply per-type checkbox state

  // 4 — Hardcoded FRZ circles
  map.addSource('frz', { type: 'geojson', data: buildFRZGeoJSON() });
  map.addLayer({ id: 'frz-fill', type: 'fill', source: 'frz',
    paint: { 'fill-color': '#ef4444', 'fill-opacity': 0.15 } });
  map.addLayer({ id: 'frz-line', type: 'line', source: 'frz',
    paint: { 'line-color': '#ef4444', 'line-width': 1.5, 'line-opacity': 0.8 } });
  map.addLayer({ id: 'frz-label', type: 'symbol', source: 'frz', minzoom: 9,
    layout: {
      'text-field': ['get', 'icao'], 'text-size': 11,
      'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'], 'text-anchor': 'center',
    },
    paint: { 'text-color': '#ef4444', 'text-halo-color': '#000', 'text-halo-width': 1.5 },
  });

  map.addSource('danger', { type: 'geojson', data: fileData.danger || emptyFC() });
  map.addLayer({ id: 'danger-fill', type: 'fill', source: 'danger',
    paint: { 'fill-color': '#f97316', 'fill-opacity': 0.18 } });
  map.addLayer({ id: 'danger-line', type: 'line', source: 'danger',
    paint: { 'line-color': '#f97316', 'line-width': 1.5, 'line-opacity': 0.9 } });

  map.addSource('military', { type: 'geojson', data: fileData.military || emptyFC() });
  map.addLayer({ id: 'military-fill', type: 'fill', source: 'military',
    paint: { 'fill-color': '#64748b', 'fill-opacity': 0.2 } });
  map.addLayer({ id: 'military-line', type: 'line', source: 'military',
    paint: { 'line-color': '#64748b', 'line-width': 1.5, 'line-opacity': 0.8 } });

  // 5 — Protected areas
  map.addSource('national-parks', { type: 'geojson', data: emptyFC() });
  map.addLayer({ id: 'national-parks-fill', type: 'fill', source: 'national-parks',
    paint: { 'fill-color': '#22c55e', 'fill-opacity': 0.12 } });
  map.addLayer({ id: 'national-parks-line', type: 'line', source: 'national-parks',
    paint: { 'line-color': '#22c55e', 'line-width': 1.2, 'line-opacity': 0.7 } });

  map.addSource('royal-parks', { type: 'geojson', data: buildRoyalParksGeoJSON() });
  map.addLayer({ id: 'royal-parks-fill', type: 'fill', source: 'royal-parks',
    paint: { 'fill-color': '#a855f7', 'fill-opacity': 0.18 } });
  map.addLayer({ id: 'royal-parks-line', type: 'line', source: 'royal-parks',
    paint: { 'line-color': '#a855f7', 'line-width': 1.2, 'line-opacity': 0.8 } });

  map.addSource('sssi', { type: 'geojson', data: emptyFC() });
  map.addLayer({ id: 'sssi-fill', type: 'fill', source: 'sssi',
    paint: { 'fill-color': '#06b6d4', 'fill-opacity': 0.14 } });
  map.addLayer({ id: 'sssi-line', type: 'line', source: 'sssi',
    paint: { 'line-color': '#06b6d4', 'line-width': 1.2, 'line-opacity': 0.75 } });

  // 6 — Managed land
  map.addSource('forestry', { type: 'geojson', data: fileData.forestry || emptyFC() });
  map.addLayer({ id: 'forestry-fill', type: 'fill', source: 'forestry',
    paint: { 'fill-color': '#4d7c0f', 'fill-opacity': 0.2 } });
  map.addLayer({ id: 'forestry-line', type: 'line', source: 'forestry',
    paint: { 'line-color': '#4d7c0f', 'line-width': 1.2, 'line-opacity': 0.8 } });

  // 7 — Location accuracy circle
  map.addSource('location-accuracy', { type: 'geojson', data: emptyFC() });
  map.addLayer({ id: 'location-accuracy-fill', type: 'fill', source: 'location-accuracy',
    paint: { 'fill-color': '#2563eb', 'fill-opacity': 0.08 } });
  map.addLayer({ id: 'location-accuracy-line', type: 'line', source: 'location-accuracy',
    paint: { 'line-color': '#2563eb', 'line-width': 1, 'line-dasharray': [3, 3], 'line-opacity': 0.5 } });

  // Re-apply 3D if enabled before the style switch
  if (is3DEnabled) enable3D();

  // Restore layer visibility from checkbox state
  Object.entries(layerVisibility).forEach(([id, vis]) => setLayerVisibility(id, vis));

  // Click handlers
  ['nats-fill','frz-fill','danger-fill','military-fill',
   'national-parks-fill','royal-parks-fill','sssi-fill','forestry-fill',
  ].forEach(layer => {
    map.on('click', layer, onFeatureClick);
    map.on('mouseenter', layer, () => { map.getCanvas().style.cursor = 'pointer'; });
    map.on('mouseleave', layer, () => { map.getCanvas().style.cursor = ''; });
  });

  // Async data fetches
  fetchNationalParks()
    .then(data => { if (map.getSource('national-parks')) map.getSource('national-parks').setData(data); })
    .catch(err => console.warn('National Parks load failed:', err));

  if (layerVisibility['sssi']) scheduleSSSIRefresh();
}

// ── Visibility helper ─────────────────────────────────────────────────────────
function setLayerVisibility(layerId, visible) {
  const vis = visible ? 'visible' : 'none';
  const hasLabel = layerId === 'frz' || layerId === 'nats';
  const suffixes = hasLabel ? ['fill', 'line', 'label'] : ['fill', 'line'];
  suffixes.forEach(s => {
    const id = `${layerId}-${s}`;
    if (map && map.getLayer(id)) map.setLayoutProperty(id, 'visibility', vis);
  });
}

// ── Click popup ───────────────────────────────────────────────────────────────
function onFeatureClick(e) {
  const f = e.features[0];
  const p = f.properties;

  let name, extra = '', tagClass, tagLabel;

  switch (f.source) {
    case 'nats': {
      name = p.name || p.designator || 'Airspace';
      const typeLabel = {
        R: 'Restricted Area', P: 'Prohibited Area',
        D: 'Danger Area', D_OTHER: 'Danger Area',
        CTR: 'Control Zone (CTR)', CTA: 'Control Area (CTA)',
        TMA: 'Terminal Area (TMA)', RAS: 'Restricted Airspace',
        'OTHER:TMZ': 'Transponder Mandatory Zone', FIR: 'Flight Information Region',
      }[p.type] || p.type;
      const tagMap = {
        R: 'tag-nats-r', P: 'tag-nats-r', D: 'tag-nats-d', D_OTHER: 'tag-nats-d',
        CTR: 'tag-nats-ctr', CTA: 'tag-nats-cta', TMA: 'tag-nats-cta',
        RAS: 'tag-nats-ras', 'OTHER:TMZ': 'tag-nats-tmz', FIR: 'tag-nats-fir',
      };
      tagClass = tagMap[p.type] || 'tag-nats-fir';
      tagLabel = typeLabel;
      if (p.designator) extra += `<p>Designator: <strong>${p.designator}</strong></p>`;
      if (p.lowerLimit) extra += `<p>Lower: <strong>${p.lowerLimit} ${p.lowerUom || ''} ${p.lowerRef || ''}</strong></p>`;
      if (p.upperLimit) extra += `<p>Upper: <strong>${p.upperLimit} ${p.upperUom || ''} ${p.upperRef || ''}</strong></p>`;
      break;
    }
    case 'frz':
      name = p.name || 'FRZ';
      extra = `${p.icao ? `<p>ICAO: <strong>${p.icao}</strong></p>` : ''}
               ${p.radiusKm ? `<p>Radius: <strong>${p.radiusKm} km</strong> (approx.)</p>` : ''}`;
      tagClass = 'tag-frz'; tagLabel = 'Flight Restriction Zone';
      break;
    case 'danger':
      name = p.name || p.Name || p.NAME || 'Restricted Area';
      tagClass = 'tag-danger'; tagLabel = 'Danger / Restricted Area';
      break;
    case 'military':
      name = p.name || p.Name || p.NAME || 'Military Area';
      tagClass = 'tag-military'; tagLabel = 'MoD / Military';
      break;
    case 'national-parks':
      name = p.NPARK23NM || p.name || 'National Park';
      tagClass = 'tag-parks'; tagLabel = 'National Park';
      break;
    case 'royal-parks':
      name = p.name || 'Royal Park';
      tagClass = 'tag-royal'; tagLabel = 'Royal Park';
      break;
    case 'sssi':
      name = p.SSSI_NAME || p.sssiName || p.name || 'SSSI';
      if (p.HA_AREA) extra = `<p>Area: <strong>${Math.round(p.HA_AREA)} ha</strong></p>`;
      tagClass = 'tag-sssi'; tagLabel = 'Site of Special Scientific Interest';
      break;
    case 'forestry':
      name = p.name || p.Name || p.NAME || 'Forestry managed land';
      tagClass = 'tag-forestry'; tagLabel = 'Managed Forest';
      break;
    default:
      name = p.name || 'Feature';
      tagClass = 'tag-danger'; tagLabel = 'Restricted Area';
  }

  document.getElementById('popup-body').innerHTML = `
    <h3>${name}</h3>
    ${extra}
    <p style="font-size:.72rem;color:#555;margin-top:6px">Always verify with official CAA / NATS sources.</p>
    <span class="tag ${tagClass}">${tagLabel}</span>
  `;
  document.getElementById('popup').classList.remove('hidden');
}

// ── Location tracking ─────────────────────────────────────────────────────────
function createLocationElement() {
  const el = document.createElement('div');
  el.className = 'location-dot';
  el.innerHTML = '<div class="location-pulse"></div><div class="location-center"></div>';
  return el;
}

function setLocateLabel(text) {
  const el = document.getElementById('locate-label');
  if (el) el.textContent = text;
}

function onLocationUpdate(pos) {
  const { longitude: lon, latitude: lat, accuracy } = pos.coords;
  const btn = document.getElementById('locate-btn');
  btn.classList.remove('locating');
  btn.classList.add('tracking');
  setLocateLabel('Stop Tracking');

  if (locationMarker) {
    locationMarker.setLngLat([lon, lat]);
  } else {
    locationMarker = new maplibregl.Marker({ element: createLocationElement(), anchor: 'center' })
      .setLngLat([lon, lat]).addTo(map);
  }

  if (map.getSource('location-accuracy')) {
    map.getSource('location-accuracy').setData({
      type: 'FeatureCollection',
      features: [{ type: 'Feature', properties: {},
        geometry: { type: 'Polygon', coordinates: [makeCircle(lon, lat, Math.max(accuracy / 1000, 0.01))] },
      }],
    });
  }

  if (isFirstFix) {
    isFirstFix = false;
    map.flyTo({ center: [lon, lat], zoom: 13, duration: 1400 });
  }
}

function onLocationError(err) {
  stopTracking();
  // Permission denied (code 1) — user chose not to share, no need to alert
  if (err.code !== 1) setLocateLabel('Location error — click to retry');
}

function startTracking() {
  if (!navigator.geolocation) { setLocateLabel('Geolocation not supported'); return; }
  document.getElementById('locate-btn').classList.add('locating');
  setLocateLabel('Getting location…');
  isFirstFix = true;
  locationWatcher = navigator.geolocation.watchPosition(
    onLocationUpdate, onLocationError,
    { enableHighAccuracy: true, timeout: 12000, maximumAge: 5000 }
  );
}

function stopTracking() {
  if (locationWatcher !== null) { navigator.geolocation.clearWatch(locationWatcher); locationWatcher = null; }
  if (locationMarker) { locationMarker.remove(); locationMarker = null; }
  if (map && map.getSource('location-accuracy')) map.getSource('location-accuracy').setData(emptyFC());
  document.getElementById('locate-btn').classList.remove('locating', 'tracking');
  setLocateLabel('Track location');
}

// ── Auto-load NATS XML from local path ───────────────────────────────────────
function loadNATSLocal() {
  if (natsLoaded) return; // already parsed; source data restored by addAllLayers

  const progressBar  = document.getElementById('nats-progress');
  const progressFill = document.getElementById('nats-progress-fill');

  setNATSStatus('Fetching NATS data…');
  progressBar.classList.remove('hidden');
  progressFill.style.width = '0%';

  fetch(NATS_XML_PATH)
    .then(res => {
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      setNATSStatus('Downloading… (72 MB)');
      return res.arrayBuffer();
    })
    .then(buffer => {
      setNATSStatus('Parsing airspace…');
      const worker = new Worker('nats-worker.js');

      worker.onmessage = msg => {
        const { type, percent, geojson, message } = msg.data;
        if (type === 'progress') {
          progressFill.style.width = `${percent}%`;
          setNATSStatus(`Parsing… ${percent}%`);
        } else if (type === 'done') {
          progressBar.classList.add('hidden');
          natsLoaded = true;
          natsGeoJSON = geojson;
          if (map && map.getSource('nats')) map.getSource('nats').setData(natsGeoJSON);
          setNATSStatus(`${geojson.features.length.toLocaleString()} features loaded`);
          worker.terminate();
        } else if (type === 'error') {
          progressBar.classList.add('hidden');
          setNATSStatus(`Parse error: ${message}`);
          worker.terminate();
        }
      };

      worker.onerror = err => {
        progressBar.classList.add('hidden');
        setNATSStatus(`Worker error: ${err.message}`);
      };

      worker.postMessage({ buffer }, [buffer]);
    })
    .catch(err => {
      progressBar.classList.add('hidden');
      setNATSStatus(`Load failed: ${err.message}`);
    });
}

// ── Map initialisation ────────────────────────────────────────────────────────
function initMap(key) {
  apiKey = key;
  map = new maplibregl.Map({
    container: 'map',
    style: STYLES[currentStyle](apiKey),
    center: [-2.5, 54.0],
    zoom: 5.5,
    minZoom: 4,
    maxPitch: 85,
    attributionControl: false,
    // Constrain panning to the UK + Channel Islands + a small buffer
    maxBounds: [[-11, 47.5], [4, 62.5]],
  });

  map.addControl(new maplibregl.AttributionControl({
    customAttribution: '© <a href="https://bournemouthtechnology.co.uk" target="_blank" rel="noopener">Bournemouth Technology</a>',
  }), 'bottom-right');
  map.addControl(new maplibregl.NavigationControl({ showCompass: true }), 'bottom-right');

  // style.load fires when the initial style is ready — add layers here.
  // Style *switches* use idle (see switchStyle) because setStyle fires style.load
  // prematurely on some MapTiler styles before the map is ready to accept sources.
  map.once('style.load', addAllLayers);

  // load fires once after the first full render.
  map.once('load', () => {
    document.getElementById('key-modal').classList.add('hidden');
    loadNATSLocal();
    startTracking();
  });

  map.on('moveend', () => {
    if (layerVisibility['sssi']) scheduleSSSIRefresh();
  });

  map.on('error', e => {
    if (e.error?.status === 401 || e.error?.status === 403) {
      localStorage.removeItem(MAPTILER_KEY_STORAGE);
      showKeyModal('Invalid API key — please try again.');
    }
  });

  map.on('click', e => {
    const clickable = [
      'nats-fill', 'frz-fill', 'danger-fill', 'military-fill',
      'national-parks-fill', 'royal-parks-fill', 'sssi-fill', 'forestry-fill',
    ];
    if (!map.queryRenderedFeatures(e.point, { layers: clickable }).length) {
      document.getElementById('popup').classList.add('hidden');
    }
  });
}

// ── Style switch ──────────────────────────────────────────────────────────────
function switchStyle(styleKey) {
  if (styleKey === currentStyle) return;
  currentStyle = styleKey;
  // Cancel any previous pending idle handler (rapid clicks), then wait for
  // the map to be truly idle after the style swap before re-adding layers.
  // Using 'idle' rather than 'style.load' because MapTiler styles fire
  // style.load before they're fully ready to accept addSource/addLayer calls.
  map.off('idle', addAllLayers);
  map.once('idle', addAllLayers);
  map.setStyle(STYLES[styleKey](apiKey));
}

// ── API key modal ─────────────────────────────────────────────────────────────
function showKeyModal(msg) {
  if (msg) document.querySelector('.modal-box p').innerHTML = `<span style="color:#f87171">${msg}</span>`;
  document.getElementById('key-modal').classList.remove('hidden');
}

// ── Controls wiring ───────────────────────────────────────────────────────────
function wireControls() {
  document.getElementById('panel-toggle').addEventListener('click', () => {
    document.getElementById('panel-body').classList.toggle('collapsed');
  });

  document.getElementById('warn-btn').addEventListener('click', () => {
    document.getElementById('warn-popup').classList.toggle('hidden');
  });

  document.querySelectorAll('[data-layer]').forEach(cb => {
    cb.addEventListener('change', () => {
      const id = cb.dataset.layer;
      layerVisibility[id] = cb.checked;
      if (map) setLayerVisibility(id, cb.checked);
      if (id === 'sssi' && cb.checked) scheduleSSSIRefresh();
    });
  });

  document.querySelectorAll('[data-nats-type]').forEach(cb => {
    cb.addEventListener('change', () => {
      natsTypeFilter[cb.dataset.natsType] = cb.checked;
      applyNATSFilter();
    });
  });

  document.getElementById('layers-select-all').addEventListener('click', () => {
    document.querySelectorAll('[data-layer]').forEach(cb => {
      cb.checked = true;
      const id = cb.dataset.layer;
      layerVisibility[id] = true;
      if (map) setLayerVisibility(id, true);
      if (id === 'sssi') scheduleSSSIRefresh();
    });
  });

  document.getElementById('layers-deselect-all').addEventListener('click', () => {
    document.querySelectorAll('[data-layer]').forEach(cb => {
      cb.checked = false;
      const id = cb.dataset.layer;
      layerVisibility[id] = false;
      if (map) setLayerVisibility(id, false);
    });
  });

  // Info buttons — click to show/hide description; each button specifies its box via data-infobox
  let activeInfoBtn = null;
  document.querySelectorAll('.info-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      const box = document.getElementById(btn.dataset.infobox || 'nats-type-info');
      if (activeInfoBtn === btn) {
        btn.classList.remove('active');
        box.classList.add('hidden');
        activeInfoBtn = null;
      } else {
        if (activeInfoBtn) {
          const prevBox = document.getElementById(activeInfoBtn.dataset.infobox || 'nats-type-info');
          prevBox.classList.add('hidden');
          activeInfoBtn.classList.remove('active');
        }
        btn.classList.add('active');
        box.textContent = btn.dataset.info;
        box.classList.remove('hidden');
        activeInfoBtn = btn;
      }
    });
  });

  document.querySelectorAll('.style-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      document.querySelectorAll('.style-btn').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      switchStyle(btn.dataset.style);
    });
  });

  document.getElementById('toggle-3d').addEventListener('change', e => {
    if (!map) return;
    is3DEnabled = e.target.checked;
    if (is3DEnabled) enable3D(); else disable3D();
  });

  document.getElementById('toggle-hillshade').addEventListener('change', e => {
    if (!map) return;
    hillshadeEnabled = e.target.checked;
    if (hillshadeEnabled) addHillshade(); else removeHillshade();
  });

  // ── GeoJSON file loader — targets the layer selected in the dropdown ────────
  const fileInput = document.getElementById('geojson-file');
  document.getElementById('geojson-btn').addEventListener('click', () => fileInput.click());
  fileInput.addEventListener('change', e => {
    const file = e.target.files[0];
    if (!file) return;
    const targetId = document.getElementById('layer-select').value;
    const reader = new FileReader();
    reader.onload = ev => {
      try {
        const data = JSON.parse(ev.target.result);

        if (targetId === 'sssi') {
          // Merge into the SSSI layer alongside England API data
          sssiFileData = data;
          if (map && map.getSource('sssi')) scheduleSSSIRefresh();
        } else {
          // Replace the appropriate source data
          fileData[targetId] = data;
          if (map && map.getSource(targetId)) {
            map.getSource(targetId).setData(data);
            // Auto-enable the layer checkbox if it was off
            const cb = document.querySelector(`[data-layer="${targetId}"]`);
            if (cb && !cb.checked) {
              cb.checked = true;
              layerVisibility[targetId] = true;
              setLayerVisibility(targetId, true);
            }
          }
        }
      } catch {
        alert('Could not parse GeoJSON — check the file format.');
      }
    };
    reader.readAsText(file);
    fileInput.value = '';
  });

  document.getElementById('locate-btn').addEventListener('click', () => {
    if (locationWatcher !== null) stopTracking(); else startTracking();
  });

  document.getElementById('popup-close').addEventListener('click', () => {
    document.getElementById('popup').classList.add('hidden');
  });
}

// ── Bootstrap ─────────────────────────────────────────────────────────────────
(function bootstrap() {
  wireControls();

  const stored = localStorage.getItem(MAPTILER_KEY_STORAGE);
  if (stored) {
    initMap(stored);
  } else {
    document.getElementById('key-modal').classList.remove('hidden');
  }

  document.getElementById('key-submit').addEventListener('click', () => {
    const key = document.getElementById('key-input').value.trim();
    if (!key) return;
    localStorage.setItem(MAPTILER_KEY_STORAGE, key);
    initMap(key);
  });

  document.getElementById('key-input').addEventListener('keydown', e => {
    if (e.key === 'Enter') document.getElementById('key-submit').click();
  });
})();
