import React, { useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import { num } from '../api/useXplane.js';

// Synthetic Vision background: real-world 3D terrain (elevation tiles) rendered
// in WebGL, with the camera placed at the aircraft and oriented by heading and
// pitch. Bank (roll) is applied as a CSS rotation of the whole canvas. This is
// the SVT *concept* using real-world DEM data — not X-Plane's own scenery.
//
// Free public elevation tiles: AWS "terrarium" (no API key needed).
const STYLE = {
  version: 8,
  glyphs: 'https://fonts.openmaptiles.org/{fontstack}/{range}.pbf', // for runway-number labels
  sources: {
    dem: {
      type: 'raster-dem',
      tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
      encoding: 'terrarium',
      tileSize: 256,
      maxzoom: 11, // coarser cap = far fewer tiles to fetch
      attribution: 'Elevation: Mapzen/AWS',
    },
  },
  layers: [
    // background shows above the horizon = the sky
    { id: 'bg', type: 'background', paint: { 'background-color': '#4a93da' } },
    {
      id: 'relief',
      type: 'color-relief',
      source: 'dem',
      paint: {
        'color-relief-color': [
          'interpolate', ['linear'], ['elevation'],
          -50, '#3d6ea5', 0, '#2e6b3a', 300, '#5a8f3c', 800, '#9aa84a',
          1500, '#b08f4e', 2500, '#8d6b4a', 3500, '#b9b0a6', 4500, '#ffffff',
        ],
      },
    },
    { id: 'hill', type: 'hillshade', source: 'dem', paint: { 'hillshade-exaggeration': 0.55 } },
  ],
  terrain: { source: 'dem', exaggeration: 1.3 },
};

// Build runway surfaces (+ threshold number labels) from the bridge's runway
// list. Each runway becomes a ground-draped quad plus two rotated number tags.
function runwayGeoJSON(list) {
  const feats = [];
  for (const r of list) {
    const midLat = (r.la1 + r.la2) / 2;
    const mLat = 111320, mLon = 111320 * Math.cos((midLat * Math.PI) / 180);
    const dx = (r.lo2 - r.lo1) * mLon, dy = (r.la2 - r.la1) * mLat;
    const len = Math.hypot(dx, dy) || 1;
    const hw = (r.w || 30) / 2;
    const dLon = ((-dy / len) * hw) / mLon, dLat = ((dx / len) * hw) / mLat;
    const c1 = [r.lo1 + dLon, r.la1 + dLat], c2 = [r.lo2 + dLon, r.la2 + dLat];
    const c3 = [r.lo2 - dLon, r.la2 - dLat], c4 = [r.lo1 - dLon, r.la1 - dLat];
    feats.push({ type: 'Feature', geometry: { type: 'Polygon', coordinates: [[c1, c2, c3, c4, c1]] }, properties: {} });
    const brg = (Math.atan2(dx, dy) * 180 / Math.PI + 360) % 360;
    feats.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [r.lo1, r.la1] }, properties: { num: r.n1, rot: brg } });
    feats.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [r.lo2, r.la2] }, properties: { num: r.n2, rot: (brg + 180) % 360 } });
  }
  return { type: 'FeatureCollection', features: feats };
}

// Place the synthetic horizon exactly where the PFD attitude horizon sits, so
// the 3D terrain and the 2D attitude agree. The attitude horizon is at 28% of
// the SVT box at level flight and moves PITCH_PX (9 px) per degree within the
// 706-px-tall box; the canvas is scaled 1.5× about that 28% line (to cover the
// corners when banked). We invert MapLibre's perspective to find the camera
// pitch that lands the flat horizon there. With the default vertical FOV of
// 36.87°, the focal-length/height ratio is 0.5/tan(fov/2) = 1.5, independent of
// resolution. Screen offset of the horizon above centre = f·tan(90°−pitch).
const CANVAS_SCALE = 1.5;          // matches .svt-canvas CSS transform scale
const FOV_FH = 1.5;                // 0.5 / tan(fov/2), fov = 36.87° (MapLibre default)
const HORIZON0 = (270 - 74) / 706; // attitude horizon as a fraction of the SVT box (≈0.2776)
const PX_PER_DEG = 9 / 706;        // PITCH_PX / box height — displayed horizon travel per °
function cameraPitchForAircraft(aircraftPitchDeg) {
  const dispFrac = HORIZON0 + PX_PER_DEG * aircraftPitchDeg;        // where the horizon must appear
  const rawFrac = HORIZON0 + (dispFrac - HORIZON0) / CANVAS_SCALE;  // undo the 1.5× canvas scale
  const t = (0.5 - rawFrac) / FOV_FH;                              // = tan(90°−pitch)
  const pitch = 90 - (Math.atan(t) * 180) / Math.PI;
  return Math.max(60, Math.min(85, pitch));
}

export default function SVT({ values }) {
  const elRef = useRef(null);
  const mapRef = useRef(null);
  const dataRef = useRef(values);
  dataRef.current = values;

  useEffect(() => {
    let map;
    try {
      map = new maplibregl.Map({
        container: elRef.current,
        style: STYLE,
        center: [num(values.lon, -122.31), num(values.lat, 47.45)],
        zoom: 11.5,
        pitch: cameraPitchForAircraft(num(values.pitch)),
        bearing: num(values.heading),
        maxPitch: 85,           // horizon placement needs ~82° at level, more when pitched up
        pixelRatio: 1,          // don't render at 2× on retina — big perf/bandwidth win
        renderWorldCopies: false,
        maxTileCacheSize: 40,
        attributionControl: false,
        interactive: false,
        preserveDrawingBuffer: true,
        fadeDuration: 0,
      });
      mapRef.current = map;
    } catch (e) {
      // WebGL unavailable → the CSS gradient fallback stays visible.
      console.warn('SVT: WebGL init failed', e?.message);
      return;
    }

    // Runways from X-Plane's nav data, draped on the terrain with their numbers.
    let rwyTimer;
    const addRunways = () => {
      if (map.getSource('runways')) return;
      map.addSource('runways', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
      map.addLayer({ id: 'rwy-fill', type: 'fill', source: 'runways', filter: ['==', ['geometry-type'], 'Polygon'], paint: { 'fill-color': '#33373b', 'fill-opacity': 0.9 } });
      map.addLayer({ id: 'rwy-line', type: 'line', source: 'runways', filter: ['==', ['geometry-type'], 'Polygon'], paint: { 'line-color': '#e8edf2', 'line-width': 1.6 } });
      map.addLayer({
        id: 'rwy-num', type: 'symbol', source: 'runways', filter: ['==', ['geometry-type'], 'Point'],
        layout: {
          'text-field': ['get', 'num'], 'text-font': ['Open Sans Bold'], 'text-size': 15,
          'text-rotate': ['get', 'rot'], 'text-rotation-alignment': 'map', 'text-keep-upright': false,
          'text-allow-overlap': true, 'text-ignore-placement': true,
        },
        paint: { 'text-color': '#fff', 'text-halo-color': '#000', 'text-halo-width': 1.4 },
      });
      let last = null;
      const refresh = async () => {
        const v = dataRef.current, lat = num(v.lat), lon = num(v.lon);
        if (!isFinite(lat) || !isFinite(lon)) return;
        if (last && Math.abs(last[0] - lat) < 0.02 && Math.abs(last[1] - lon) < 0.02) return;
        last = [lat, lon];
        try {
          const res = await fetch(`/api/nav/runways?lat=${lat}&lon=${lon}&radius=15`);
          if (!res.ok) return;
          map.getSource('runways')?.setData(runwayGeoJSON(await res.json()));
        } catch { /* offline */ }
      };
      refresh();
      rwyTimer = setInterval(refresh, 4000);
    };

    // Terrain awareness (TAWS): recolour the relief relative to aircraft
    // altitude — terrain within 1000 ft below = yellow, within 100 ft below or
    // above = red, otherwise normal. Stops are in metres (terrarium elevation).
    let lastBandM = null;
    const updateTerrainAwareness = (altFt) => {
      const altM = altFt * 0.3048;
      if (lastBandM != null && Math.abs(altM - lastBandM) < 12) return;
      lastBandM = altM;
      const yellowLo = altM - 305, redLo = altM - 30; // 1000 ft / 100 ft below
      const s = []; // [elevation, color] pairs, strictly increasing inputs
      const push = (e, c) => { if (!s.length || e > s[s.length - 2]) s.push(e, c); };
      push(-150, '#2f6a3c'); push(150, '#4f8a3e'); push(900, '#9a8a4a');
      push(yellowLo - 1, '#7d6a3a');
      push(yellowLo, '#e6c200'); push(redLo - 1, '#e6c200');
      push(redLo, '#e03030'); push(redLo + 4000, '#ff2a2a');
      try { map.setPaintProperty('relief', 'color-relief-color', ['interpolate', ['linear'], ['elevation'], ...s]); } catch { /* not ready */ }
    };

    let raf;
    const tick = () => {
      const v = dataRef.current;
      // Keep the view close: higher zoom floor + capped pitch bounds the area.
      const zoom = Math.max(10.5, Math.min(12.5, 12.5 - num(v.altitude) / 3500));
      try {
        map.jumpTo({
          center: [num(v.lon, -122.31), num(v.lat, 47.45)],
          bearing: num(v.heading),
          pitch: cameraPitchForAircraft(num(v.pitch)),
          zoom,
        });
        updateTerrainAwareness(num(v.altitude, 5500));
      } catch { /* style not ready yet */ }
      raf = requestAnimationFrame(tick);
    };
    map.on('load', () => { addRunways(); raf = requestAnimationFrame(tick); });

    return () => { cancelAnimationFrame(raf); clearInterval(rwyTimer); map.remove(); mapRef.current = null; };
  }, []); // eslint-disable-line

  // Bank: rotate the whole terrain canvas opposite to aircraft roll; scale up so
  // the corners stay covered while rotated.
  const roll = num(values.roll);
  return (
    <div className="svt-fallback">
      <div ref={elRef} className="svt-canvas" style={{ transform: `rotate(${-roll}deg) scale(1.5)` }} />
    </div>
  );
}
