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

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

{% block extra_css %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
  integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
<style>
  #sites-map { height: 70vh; border-radius: 0.5rem; }
  .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>
        </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: hover 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://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
  integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></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;
  }

  // Base map
  const map = L.map('sites-map', { zoomControl: true });
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    { maxZoom: 19, attribution: '&copy; OpenStreetMap' }).addTo(map);

  // Layers
  const layers = {
    iso: L.layerGroup().addTo(map),
    cand: L.layerGroup().addTo(map),
    demand: L.layerGroup(),
    comp: L.layerGroup(),
  };
  document.getElementById('chkIso').addEventListener('change', (e)=> toggle(layers.iso, e.target.checked));
  document.getElementById('chkCand').addEventListener('change', (e)=> toggle(layers.cand, e.target.checked));
  document.getElementById('chkDemand').addEventListener('change', (e)=> toggle(layers.demand, e.target.checked));
  document.getElementById('chkComp').addEventListener('change', (e)=> toggle(layers.comp, e.target.checked));
  function toggle(layer, on){ if (on) layer.addTo(map); else map.removeLayer(layer); }

  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`;

  let fitBounds = null;

  // 55A scrubber state
  const bandColors = ['#2E86AB','#F18F01','#C73E1D','#6C5B7B','#17B890','#7E57C2'];
  let minutes = [];
  const isoGroups = new Map();    // minute -> LayerGroup
  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} min`;
    areaText.textContent = fmtArea(minuteAreas.get(m));

    layers.iso.clearLayers();
    const group = isoGroups.get(m);
    if (group) layers.iso.addLayer(group);

    // 55B: move chart marker
    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, padT, H } = 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); }
  }

  // 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(([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;

    // Fit bounds to all isochrones
    const allIso = L.geoJSON(isoFC);
    const b = allIso.getBounds();
    if (b.isValid()) { map.fitBounds(b.pad(0.05)); fitBounds = b; }

    // Build minute groups + areas
    (isoFC.features || []).forEach((f)=>{
      const m = (f.properties && Number(f.properties.minutes)) || 0;
      if (!isoGroups.has(m)) isoGroups.set(m, L.layerGroup());
      const i = Math.max(0, Math.floor(m/5) - 1);
      const c = bandColors[i % bandColors.length];
      const lyr = L.geoJSON(f, { style: { color:c, weight:2, fillColor:c, fillOpacity:.25 } });
      isoGroups.get(m).addLayer(lyr);
      const a = Number((f.properties && f.properties.area_km2) || 0);
      minuteAreas.set(m, (minuteAreas.get(m) || 0) + (isFinite(a) ? a : 0));
    });

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

    // 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
    L.geoJSON(candFC, {
      pointToLayer: (f, latlng)=>{
        const s = Number(f.properties?.score ?? 0.5);
        const r = 6 + Math.round(s * 10);
        return L.circleMarker(latlng, { radius: r, weight: 2, color: '#111', fillOpacity: 0.9 });
      },
      onEachFeature: (f, layer)=>{
        const p = f.properties || {};
        const score = Number(p.score ?? 0).toFixed(2);
        const br = `Demand ${Number(p.demand||0).toFixed(2)}, `
                 + `Comp ${Number(p.competition||0).toFixed(2)}, `
                 + `Access ${Number(p.access||0).toFixed(2)}`;
        layer.bindPopup(`<b>Rank ${p.rank || '—'}</b> · score ${score}<br><small>${br}</small>`);
      }
    }).addTo(layers.cand);

    if (!fitBounds) {
      const b2 = L.geoJSON(candFC).getBounds();
      if (b2.isValid()) map.fitBounds(b2.pad(0.1));
      else map.setView([19.4326, -99.1332], 11);
    }

    // Demand & Competition (start hidden)
    L.geoJSON(demFC, {
      pointToLayer: (f, latlng)=> L.circleMarker(latlng, { radius: 3, color: '#d9534f', weight: 0, fillOpacity: .7 }),
      onEachFeature: (f, layer)=>{
        const pop = f.properties?.pop;
        if (pop) layer.bindPopup(`Population: ${Math.round(pop)}`);
      }
    }).addTo(layers.demand);
    L.geoJSON(compFC, {
      pointToLayer: (f, latlng)=> L.circleMarker(latlng, { radius: 3, color: '#0d6efd', weight: 0, fillOpacity: .7 }),
      onEachFeature: (f, layer)=>{
        const name = f.properties?.name || '(poi)';
        const cat  = f.properties?.category || '';
        layer.bindPopup(`${name}${cat?' — '+cat:''}`);
      }
    }).addTo(layers.comp);
    map.removeLayer(layers.demand);
    map.removeLayer(layers.comp);
    document.getElementById('chkDemand').checked = false;
    document.getElementById('chkComp').checked = false;

    // 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 %}
