{% extends "pxy_dashboard/partials/base.html" %}
{% load static %}

{% block title %}Sites · Run viewer{% endblock title %}

{% block extra_css %}
<link
  rel="stylesheet"
  href="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Widgets/widgets.css"
/>
<style>
  #sites-map { height: 70vh; border-radius: 0.5rem; overflow: hidden; }
  .layer-chip { margin-right: .75rem; }
  .leaflet-popup-content { margin: 10px 14px; }
  .scrub-wrap { display:flex; align-items:center; gap:.5rem; }
  .scrub-wrap input[type=range]{ width: 220px; }
  .scrub-metric { min-width: 10ch; text-align:right; }
  /* 55B: mini chart */
  #areaChart { width: 100%; height: 110px; }
  .chart-grid line { stroke: #e9ecef; stroke-width: 1; }
  .chart-axis text { fill: #6c757d; font-size: 10px; }
</style>
{% endblock extra_css %}

{% block pagetitle %}
  {% include "pxy_dashboard/partials/page-title.html" with pagetitle="Sites" title="Run viewer" %}
{% endblock pagetitle %}

{% block content %}
<div class="row">
  <div class="col-12">
    <div class="card">
      <div class="card-body">
        <div class="d-flex flex-wrap align-items-center justify-content-between mb-3">
          <div>
            <h4 class="header-title mb-0">Interactive run viewer</h4>
            <small id="runMeta" class="text-muted">Loading…</small>
          </div>

          <div class="d-flex flex-wrap align-items-center gap-3">
            <!-- 55A — Minutes scrubber -->
            <div id="scrubWrap" class="scrub-wrap d-none">
              <button id="btnPlay"  class="btn btn-sm btn-outline-primary" type="button">Play</button>
              <button id="btnPause" class="btn btn-sm btn-outline-secondary" type="button" disabled>Pause</button>
              <div class="d-flex align-items-center gap-2">
                <label class="mb-0 text-muted small">Minutes</label>
                <input id="minuteSlider" type="range" min="0" max="0" step="1" value="0">
                <span id="minuteText" class="badge bg-primary-subtle text-primary-emphasis">—</span>
                <span class="text-muted small">Area:</span>
                <span id="areaText" class="scrub-metric text-muted small">—</span>
              </div>
            </div>

            <!-- Existing layer toggles -->
            <div class="d-flex align-items-center">
              <label class="me-2">Layers</label>
              <div class="form-check form-check-inline layer-chip">
                <input class="form-check-input" type="checkbox" id="chkIso" checked>
                <label class="form-check-label" for="chkIso">Isochrones</label>
              </div>
              <div class="form-check form-check-inline layer-chip">
                <input class="form-check-input" type="checkbox" id="chkCand" checked>
                <label class="form-check-label" for="chkCand">Candidates</label>
              </div>
              <div class="form-check form-check-inline layer-chip">
                <input class="form-check-input" type="checkbox" id="chkDemand">
                <label class="form-check-label" for="chkDemand">Demand (sample)</label>
              </div>
              <div class="form-check form-check-inline layer-chip">
                <input class="form-check-input" type="checkbox" id="chkComp">
                <label class="form-check-label" for="chkComp">Competition (sample)</label>
              </div>
            </div>
            <div class="d-flex align-items-center">
              <label class="me-2">Basemap</label>
              <select id="basemap" class="form-select form-select-sm" style="min-width: 180px;">
                <option value="osm">OpenStreetMap</option>
                <option value="esri">ESRI World Imagery</option>
                <option value="carto_light">CARTO Light</option>
                <option value="carto_dark">CARTO Dark</option>
              </select>
            </div>
          </div>
        </div>

        <div id="sites-map" class="mb-2"></div>

        <!-- 55B — area vs minutes mini-chart -->
        <div id="areaChartWrap" class="mt-2 d-none">
          <svg id="areaChart" viewBox="0 0 640 120" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Isochrone area by minutes"></svg>
        </div>

        <div class="text-muted small mt-1">
          Tip: click candidates for score & breakdown; toggle layers on the right; use the scrubber to switch minutes.
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock content %}

{% block extra_js %}
<script src="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Cesium.js"></script>
<script>
(function(){
  const q = new URLSearchParams(window.location.search);
  const sid = q.get('search_id');
  const metaEl = document.getElementById('runMeta');
  const scrubWrap  = document.getElementById('scrubWrap');
  const minuteSlider = document.getElementById('minuteSlider');
  const minuteText   = document.getElementById('minuteText');
  const areaText     = document.getElementById('areaText');
  const btnPlay  = document.getElementById('btnPlay');
  const btnPause = document.getElementById('btnPause');

  // 55B: chart elements
  const chartWrap = document.getElementById('areaChartWrap');
  const chartEl   = document.getElementById('areaChart');

  if (!sid) {
    metaEl.textContent = "Missing ?search_id=...";
    console.warn("No search_id param");
    return;
  }

  const viewer = new Cesium.Viewer('sites-map', {
    imageryProvider: false,
    baseLayerPicker: false,
    geocoder: false,
    homeButton: false,
    navigationHelpButton: false,
    sceneModePicker: false,
    animation: false,
    timeline: false,
    infoBox: true,
    selectionIndicator: true,
  });

  const basemapSelect = document.getElementById('basemap');
  const basemapLayers = {};

  function getBasemapProvider(key) {
    if (key === 'osm') {
      return new Cesium.OpenStreetMapImageryProvider({
        url: 'https://tile.openstreetmap.org/',
      });
    }
    if (key === 'esri') {
      return new Cesium.UrlTemplateImageryProvider({
        url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
        credit: 'Esri',
      });
    }
    if (key === 'carto_light') {
      return new Cesium.UrlTemplateImageryProvider({
        url: 'https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png',
        credit: 'CARTO',
      });
    }
    if (key === 'carto_dark') {
      return new Cesium.UrlTemplateImageryProvider({
        url: 'https://cartodb-basemaps-a.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png',
        credit: 'CARTO',
      });
    }
    return null;
  }

  function setBaseLayer(key) {
    let layer = basemapLayers[key];
    if (!layer) {
      const provider = getBasemapProvider(key);
      if (!provider) {
        return;
      }
      layer = viewer.imageryLayers.addImageryProvider(provider);
      basemapLayers[key] = layer;
    }
    Object.keys(basemapLayers).forEach((k) => {
      basemapLayers[k].show = k === key;
    });
    viewer.imageryLayers.raiseToTop(layer);
  }

  basemapSelect.addEventListener('change', () => {
    setBaseLayer(basemapSelect.value);
  });
  setBaseLayer(basemapSelect.value);

  viewer.scene.globe.depthTestAgainstTerrain = false;

  const layers = {
    iso: new Map(),
    cand: null,
    demand: null,
    comp: null,
  };

  let isoVisible = true;
  document.getElementById('chkIso').addEventListener('change', (e)=> {
    isoVisible = e.target.checked;
    renderMinute(currentIndex);
  });
  document.getElementById('chkCand').addEventListener('change', (e)=> {
    if (layers.cand) layers.cand.show = e.target.checked;
  });
  document.getElementById('chkDemand').addEventListener('change', (e)=> {
    if (layers.demand) layers.demand.show = e.target.checked;
  });
  document.getElementById('chkComp').addEventListener('change', (e)=> {
    if (layers.comp) layers.comp.show = e.target.checked;
  });

  const isoURL   = `/api/sites/geojson/isochrones/${encodeURIComponent(sid)}`;
  const candURL  = `/api/sites/geojson/candidates/${encodeURIComponent(sid)}`;
  const demURL   = `/api/sites/geojson/popgrid/${encodeURIComponent(sid)}`;
  const compURL  = `/api/sites/geojson/pois_competition/${encodeURIComponent(sid)}`;
  const artURL   = `/media/sites/run_${encodeURIComponent(sid)}.json`;

  // 55A scrubber state
  const bandColors = ['#2E86AB','#F18F01','#C73E1D','#6C5B7B','#17B890','#7E57C2'];
  let minutes = [];
  const isoGroups = new Map();    // minute -> array of features
  const minuteAreas = new Map();  // minute -> total area_km2
  let currentIndex = 0;
  let timer = null;

  function fmtArea(km2) {
    if (km2 == null) return '—';
    if (km2 < 10) return km2.toFixed(2) + ' km²';
    if (km2 < 100) return km2.toFixed(1) + ' km²';
    return Math.round(km2) + ' km²';
  }

  function setButtons(playing) {
    btnPlay.disabled  = playing;
    btnPause.disabled = !playing;
  }

  function renderMinute(idx) {
    currentIndex = Math.max(0, Math.min(idx, minutes.length - 1));
    const m = minutes[currentIndex];
    minuteText.textContent = m != null ? `${m} min` : '—';
    areaText.textContent = fmtArea(minuteAreas.get(m));

    layers.iso.forEach((ds, minute)=>{
      ds.show = isoVisible && minute === m;
    });

    updateAreaMarker(m);
  }

  minuteSlider.addEventListener('input', (e)=>{
    renderMinute(parseInt(e.target.value, 10) || 0);
  });

  btnPlay.addEventListener('click', ()=>{
    if (timer || minutes.length <= 1) return;
    setButtons(true);
    timer = setInterval(()=>{
      const next = (currentIndex + 1) % minutes.length;
      minuteSlider.value = String(next);
      renderMinute(next);
    }, 1200);
  });

  btnPause.addEventListener('click', ()=>{
    if (timer){ clearInterval(timer); timer = null; }
    setButtons(false);
  });

  // 55B — area chart internals
  let chartState = null; // {mins, areas, x, y, markerX, markerY}
  function drawAreaChart(mins, areas) {
    // Responsive box
    const vbW = 640, vbH = 120;
    const padL = 42, padR = 14, padT = 10, padB = 24;
    const W = vbW - padL - padR;
    const H = vbH - padT - padB;

    const minM = Math.min(...mins);
    const maxM = Math.max(...mins);
    const maxA = Math.max(...areas) * 1.1 || 1;

    const x = (m)=> padL + ( (m - minM) / (maxM - minM || 1) ) * W;
    const y = (a)=> padT + (1 - (a / (maxA || 1))) * H;

    // Build path
    let d = '';
    mins.forEach((m, i)=>{
      const X = x(m), Y = y(areas[i]);
      d += (i ? 'L' : 'M') + X + ' ' + Y + ' ';
    });
    // Close to baseline for fill
    d += `L ${x(maxM)} ${padT + H} L ${x(minM)} ${padT + H} Z`;

    // X ticks at each minute; Y ticks 0, mid, max
    const midA = maxA/2;
    function tickY(val){ return padT + (1 - (val/maxA)) * H; }

    const yTicks = [
      {v:0,label:'0'},
      {v:midA,label: midA>=1 ? (midA<10?midA.toFixed(1):Math.round(midA)) : midA.toFixed(2)},
      {v:maxA,label: maxA>=10 ? Math.round(maxA) : maxA.toFixed(1)}
    ];

    // SVG
    chartEl.innerHTML = `
      <defs>
        <linearGradient id="ag" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#2E86AB" stop-opacity="0.30"/>
          <stop offset="100%" stop-color="#2E86AB" stop-opacity="0.05"/>
        </linearGradient>
      </defs>

      <g class="chart-grid">
        ${yTicks.map(t=>`<line x1="${padL}" y1="${tickY(t.v)}" x2="${padL+W}" y2="${tickY(t.v)}"/>`).join('')}
      </g>

      <path d="${d}" fill="url(#ag)" stroke="#2E86AB" stroke-width="2" />

      <!-- X ticks -->
      <g class="chart-axis">
        ${mins.map(m=>{
          const X = x(m);
          return `
            <line x1="${X}" y1="${padT+H}" x2="${X}" y2="${padT+H+4}" stroke="#adb5bd"/>
            <text x="${X}" y="${padT+H+14}" text-anchor="middle">${m}m</text>
          `;
        }).join('')}
      </g>

      <!-- Y ticks -->
      <g class="chart-axis">
        ${yTicks.map(t=>{
          return `<text x="${padL-6}" y="${tickY(t.v)+3}" text-anchor="end">${t.label} km²</text>`;
        }).join('')}
      </g>

      <!-- Marker group (updated dynamically) -->
      <g id="marker">
        <line id="markerLine" x1="${padL}" y1="${padT}" x2="${padL}" y2="${padT+H}" stroke="#495057" stroke-dasharray="3,3"/>
        <circle id="markerDot" cx="${padL}" cy="${padT+H}" r="4" fill="#2E86AB" stroke="#111" stroke-width="1.5"/>
      </g>
    `;

    chartState = { mins, areas, x, y, padT, H };
    updateAreaMarker(minutes[currentIndex] || mins[mins.length-1]);
    chartWrap.classList.toggle('d-none', mins.length < 2);
  }

  function updateAreaMarker(minute) {
    if (!chartState) return;
    const { mins, areas, x, y } = chartState;
    const i = Math.max(0, mins.indexOf(minute));
    const X = x(mins[i]);
    const Y = y(areas[i]);

    const line = chartEl.querySelector('#markerLine');
    const dot  = chartEl.querySelector('#markerDot');
    if (line) { line.setAttribute('x1', X); line.setAttribute('x2', X); }
    if (dot)  { dot.setAttribute('cx', X); dot.setAttribute('cy', Y); }
  }

  function extendBounds(bounds, lon, lat) {
    if (!bounds) {
      return { minLon: lon, minLat: lat, maxLon: lon, maxLat: lat };
    }
    return {
      minLon: Math.min(bounds.minLon, lon),
      minLat: Math.min(bounds.minLat, lat),
      maxLon: Math.max(bounds.maxLon, lon),
      maxLat: Math.max(bounds.maxLat, lat),
    };
  }

  function boundsFromGeometry(bounds, geom) {
    if (!geom) return bounds;
    if (geom.type === 'Point') {
      return extendBounds(bounds, geom.coordinates[0], geom.coordinates[1]);
    }
    if (geom.type === 'Polygon') {
      geom.coordinates.forEach(ring => {
        ring.forEach(coord => { bounds = extendBounds(bounds, coord[0], coord[1]); });
      });
    }
    if (geom.type === 'MultiPolygon') {
      geom.coordinates.forEach(poly => {
        poly.forEach(ring => {
          ring.forEach(coord => { bounds = extendBounds(bounds, coord[0], coord[1]); });
        });
      });
    }
    return bounds;
  }

  function flyToBounds(bounds) {
    if (!bounds) return;
    const rect = Cesium.Rectangle.fromDegrees(bounds.minLon, bounds.minLat, bounds.maxLon, bounds.maxLat);
    viewer.camera.flyTo({ destination: rect });
  }

  async function loadGeoJson(fc, options) {
    if (!fc || !fc.features || fc.features.length === 0) {
      return null;
    }
    return Cesium.GeoJsonDataSource.load(fc, options);
  }

  // Fetch everything
  Promise.allSettled([
    fetch(isoURL).then(r=>r.ok?r.json():null),
    fetch(candURL).then(r=>r.ok?r.json():null),
    fetch(demURL).then(r=>r.ok?r.json():null),
    fetch(compURL).then(r=>r.ok?r.json():null),
    fetch(artURL).then(r=>r.ok?r.json():null)
  ]).then(async ([iso, cand, dem, comp, art])=>{
    const isoFC  = iso.value || {type:'FeatureCollection',features:[]};
    const candFC = cand.value|| {type:'FeatureCollection',features:[]};
    const demFC  = dem.value || {type:'FeatureCollection',features:[]};
    const compFC = comp.value|| {type:'FeatureCollection',features:[]};
    const artObj = art.value || null;

    let bounds = null;

    // Build minute groups + areas
    (isoFC.features || []).forEach((f)=>{
      const m = (f.properties && Number(f.properties.minutes)) || 0;
      if (!isoGroups.has(m)) isoGroups.set(m, []);
      isoGroups.get(m).push(f);
      const a = Number((f.properties && f.properties.area_km2) || 0);
      minuteAreas.set(m, (minuteAreas.get(m) || 0) + (isFinite(a) ? a : 0));
      bounds = boundsFromGeometry(bounds, f.geometry);
    });

    minutes = Array.from(isoGroups.keys()).sort((a,b)=>a-b);

    for (const minute of minutes) {
      const i = Math.max(0, Math.floor(minute/5) - 1);
      const colorHex = bandColors[i % bandColors.length];
      const color = Cesium.Color.fromCssColorString(colorHex);
      const fc = { type: 'FeatureCollection', features: isoGroups.get(minute) || [] };
      const ds = await loadGeoJson(fc, {
        stroke: color,
        fill: color.withAlpha(0.25),
        strokeWidth: 2,
        clampToGround: true,
      });
      if (ds) {
        ds.show = false;
        viewer.dataSources.add(ds);
        layers.iso.set(minute, ds);
      }
    }

    // 55B: draw chart if ≥2 bands
    if (minutes.length >= 2) {
      const areas = minutes.map(m => minuteAreas.get(m) || 0);
      drawAreaChart(minutes, areas);
    } else {
      chartWrap.classList.add('d-none');
    }

    // Configure slider
    if (minutes.length > 0) {
      minuteSlider.min = 0;
      minuteSlider.max = Math.max(0, minutes.length - 1);
      minuteSlider.step = 1;
      minuteSlider.value = String(minutes.length - 1); // default to largest band
      scrubWrap.classList.toggle('d-none', minutes.length <= 1);
      renderMinute(parseInt(minuteSlider.value, 10));
      setButtons(false);
    } else {
      scrubWrap.classList.add('d-none');
    }

    // Candidates
    layers.cand = await loadGeoJson(candFC, { clampToGround: true });
    if (layers.cand) {
      viewer.dataSources.add(layers.cand);
      layers.cand.entities.values.forEach((entity)=>{
        const score = entity.properties?.score?.getValue(Cesium.JulianDate.now()) || 0;
        const rank = entity.properties?.rank?.getValue(Cesium.JulianDate.now()) || '—';
        const demand = entity.properties?.demand?.getValue(Cesium.JulianDate.now()) || 0;
        const competition = entity.properties?.competition?.getValue(Cesium.JulianDate.now()) || 0;
        const access = entity.properties?.access?.getValue(Cesium.JulianDate.now()) || 0;
        const radius = 6 + Math.round(score * 10);
        entity.point = new Cesium.PointGraphics({
          pixelSize: radius,
          color: Cesium.Color.fromCssColorString('#1B998B'),
          outlineColor: Cesium.Color.BLACK.withAlpha(0.7),
          outlineWidth: 1,
        });
        entity.description = `<b>Rank ${rank}</b> · score ${Number(score).toFixed(2)}<br>` +
          `<small>Demand ${Number(demand).toFixed(2)}, ` +
          `Comp ${Number(competition).toFixed(2)}, ` +
          `Access ${Number(access).toFixed(2)}</small>`;
      });
      if (!bounds) {
        candFC.features.forEach((f)=>{ bounds = boundsFromGeometry(bounds, f.geometry); });
      }
    }

    // Demand & Competition (start hidden)
    layers.demand = await loadGeoJson(demFC, { clampToGround: true });
    if (layers.demand) {
      viewer.dataSources.add(layers.demand);
      layers.demand.show = false;
      layers.demand.entities.values.forEach((entity)=>{
        entity.point = new Cesium.PointGraphics({
          pixelSize: 4,
          color: Cesium.Color.fromCssColorString('#d9534f'),
          outlineColor: Cesium.Color.BLACK.withAlpha(0.4),
          outlineWidth: 1,
        });
      });
    }

    layers.comp = await loadGeoJson(compFC, { clampToGround: true });
    if (layers.comp) {
      viewer.dataSources.add(layers.comp);
      layers.comp.show = false;
      layers.comp.entities.values.forEach((entity)=>{
        const name = entity.properties?.name?.getValue(Cesium.JulianDate.now()) || '(poi)';
        const cat = entity.properties?.category?.getValue(Cesium.JulianDate.now()) || '';
        entity.point = new Cesium.PointGraphics({
          pixelSize: 4,
          color: Cesium.Color.fromCssColorString('#0d6efd'),
          outlineColor: Cesium.Color.BLACK.withAlpha(0.4),
          outlineWidth: 1,
        });
        entity.description = `${name}${cat ? ' — ' + cat : ''}`;
      });
    }

    document.getElementById('chkDemand').checked = false;
    document.getElementById('chkComp').checked = false;

    if (bounds) {
      flyToBounds(bounds);
    } else {
      viewer.camera.flyTo({
        destination: Cesium.Cartesian3.fromDegrees(-99.1332, 19.4326, 14000),
      });
    }

    // Meta (best-effort via artifact)
    if (artObj && artObj.request) {
      const req = artObj.request;
      const bands = (req.time_bands || []).join(', ');
      metaEl.textContent = `${req.business || '—'} @ ${req.city || '—'} · bands: ${bands} · search_id ${sid.slice(0,8)}…`;
    } else {
      metaEl.textContent = `search_id ${sid.slice(0,8)}…`;
    }
  });

  // Redraw chart on resize (debounced)
  let _rt = null;
  window.addEventListener('resize', ()=>{
    if (!_rt) {
      _rt = requestAnimationFrame(()=>{
        if (minutes.length >= 2) {
          const areas = minutes.map(m => minuteAreas.get(m) || 0);
          drawAreaChart(minutes, areas);
        }
        _rt = null;
      });
    }
  });
})();
</script>
{% endblock extra_js %}
