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

{% block title %}Simulation MVP (Cesium){% endblock %}

{% block extra_css %}
<link
  rel="stylesheet"
  href="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Widgets/widgets.css"
/>
<style>
  .sim-cesium {
    width: 100%;
    height: 520px;
    border: 1px solid #2b3348;
    border-radius: 8px;
    overflow: hidden;
  }
  .sim-metric {
    font-weight: 600;
  }
  .sim-status {
    font-size: 0.9rem;
    color: #8a94a6;
  }
</style>
{% endblock %}

{% block content %}
<div class="container-fluid">
  <div class="row mb-4">
    <div class="col-12">
      <h4 class="mb-1">Simulation MVP (Cesium)</h4>
      <div class="text-muted">Residents move toward their nearest service node, now rendered in Cesium.</div>
    </div>
  </div>

  <div class="row">
    <div class="col-lg-8">
      <div class="card">
        <div class="card-header">
          <h5 class="mb-0">Live State</h5>
        </div>
        <div class="card-body">
          <div id="cesiumContainer" class="sim-cesium"></div>
        </div>
      </div>
    </div>

    <div class="col-lg-4">
      <div class="card mb-3">
        <div class="card-header">
          <h5 class="mb-0">Controls</h5>
        </div>
        <div class="card-body">
          <div class="mb-2">
            <label class="form-label">Residents</label>
            <input id="simResidents" type="number" class="form-control" value="60" min="5" max="500" />
          </div>
          <div class="mb-2">
            <label class="form-label">Services</label>
            <input id="simServices" type="number" class="form-control" value="4" min="1" max="50" />
          </div>
          <div class="mb-2">
            <label class="form-label">Speed</label>
            <input id="simSpeed" type="number" class="form-control" value="1.25" step="0.1" min="0.1" max="8" />
          </div>
          <div class="mb-3">
            <label class="form-label">Service Radius</label>
            <input id="simRadius" type="number" class="form-control" value="3.5" step="0.1" min="0.5" max="15" />
          </div>
          <div class="mb-2">
            <label class="form-label">Basemap</label>
            <select id="basemap" class="form-select">
              <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 class="form-check form-switch mb-2">
            <input class="form-check-input" type="checkbox" id="simRoads" checked />
            <label class="form-check-label" for="simRoads">Use Real Roads (OSM)</label>
          </div>
          <div class="row g-2 mb-2">
            <div class="col-6">
              <label class="form-label">Lat</label>
              <input id="simLat" type="number" class="form-control" value="19.4326" step="0.0001" />
            </div>
            <div class="col-6">
              <label class="form-label">Lon</label>
              <input id="simLon" type="number" class="form-control" value="-99.1332" step="0.0001" />
            </div>
          </div>
          <div class="mb-3">
            <label class="form-label">OSM Radius (m)</label>
            <input id="simDist" type="number" class="form-control" value="700" step="50" min="200" max="2500" />
          </div>
          <div class="d-flex gap-2 flex-wrap">
            <button id="simNew" class="btn btn-primary">New Run</button>
            <button id="simStep" class="btn btn-outline-primary">Step</button>
            <button id="simAuto" class="btn btn-outline-secondary">Auto Run</button>
          </div>
          <div id="simStatus" class="sim-status mt-3">Ready.</div>
        </div>
      </div>

      <div class="card">
        <div class="card-header">
          <h5 class="mb-0">Metrics</h5>
        </div>
        <div class="card-body">
          <div class="d-flex justify-content-between mb-2"><span>Step</span><span id="metricStep" class="sim-metric">-</span></div>
          <div class="d-flex justify-content-between mb-2"><span>Residents</span><span id="metricResidents" class="sim-metric">-</span></div>
          <div class="d-flex justify-content-between mb-2"><span>Services</span><span id="metricServices" class="sim-metric">-</span></div>
          <div class="d-flex justify-content-between mb-2"><span>Served</span><span id="metricServed" class="sim-metric">-</span></div>
          <div class="d-flex justify-content-between mb-2"><span>Avg Distance</span><span id="metricAvg" class="sim-metric">-</span></div>
          <div class="d-flex justify-content-between"><span>Max Distance</span><span id="metricMax" class="sim-metric">-</span></div>
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock %}

{% block extra_js %}
<script src="https://cesium.com/downloads/cesiumjs/releases/1.114/Build/Cesium/Cesium.js"></script>
<script>
  Cesium.Ion.defaultAccessToken = "{{ ion_token }}";

  const viewer = new Cesium.Viewer("cesiumContainer", {
    animation: false,
    timeline: false,
    baseLayerPicker: false,
    geocoder: false,
    homeButton: false,
    navigationHelpButton: false,
    sceneModePicker: false,
    infoBox: false,
    selectionIndicator: false,
  });

  let osmBuildingsTileset = null;

  async function loadOsmBuildings() {
    try {
      osmBuildingsTileset = await Cesium.createOsmBuildingsAsync();
      viewer.scene.primitives.add(osmBuildingsTileset);
    } catch (err) {
      console.warn("OSM Buildings failed to load", err);
    }
  }

  loadOsmBuildings();

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

  const metricStep = document.getElementById("metricStep");
  const metricResidents = document.getElementById("metricResidents");
  const metricServices = document.getElementById("metricServices");
  const metricServed = document.getElementById("metricServed");
  const metricAvg = document.getElementById("metricAvg");
  const metricMax = document.getElementById("metricMax");
  const statusEl = document.getElementById("simStatus");

  const btnNew = document.getElementById("simNew");
  const btnStep = document.getElementById("simStep");
  const btnAuto = document.getElementById("simAuto");

  const inputResidents = document.getElementById("simResidents");
  const inputServices = document.getElementById("simServices");
  const inputSpeed = document.getElementById("simSpeed");
  const inputRadius = document.getElementById("simRadius");
  const inputRoads = document.getElementById("simRoads");
  const inputLat = document.getElementById("simLat");
  const inputLon = document.getElementById("simLon");
  const inputDist = document.getElementById("simDist");

  const residentEntities = new Map();
  const serviceEntities = new Map();

  let runId = null;
  let autoTimer = null;
  let busy = false;
  let lastState = null;
  let gridCenter = { lat: 19.4326, lon: -99.1332 };
  let stewardAura = null;
  const auraStart = Cesium.JulianDate.now();
  const auraBaseMeters = 260.0;
  const auraPulseMeters = 120.0;

  const R = 6378137;
  const GRID_METERS_PER_UNIT = 10.0;

  function setStatus(text) {
    statusEl.textContent = text;
  }

  function updateStewardAura() {
    const lat = Number(inputLat.value);
    const lon = Number(inputLon.value);
    if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
      return;
    }
    if (stewardAura) {
      viewer.entities.remove(stewardAura);
    }
    const radiusProp = new Cesium.CallbackProperty((time) => {
      const t = Cesium.JulianDate.secondsDifference(time, auraStart);
      const pulse = (Math.sin((t * 2 * Math.PI) / 4) + 1) / 2;
      return auraBaseMeters + auraPulseMeters * pulse;
    }, false);
    const colorProp = new Cesium.CallbackProperty((time) => {
      const t = Cesium.JulianDate.secondsDifference(time, auraStart);
      const pulse = (Math.sin((t * 2 * Math.PI) / 4) + 1) / 2;
      return Cesium.Color.fromCssColorString("#36f1ff").withAlpha(0.18 + 0.18 * pulse);
    }, false);
    stewardAura = viewer.entities.add({
      name: "Steward Aura",
      position: Cesium.Cartesian3.fromDegrees(lon, lat, 0),
      ellipse: {
        semiMajorAxis: radiusProp,
        semiMinorAxis: radiusProp,
        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
        material: new Cesium.ColorMaterialProperty(colorProp),
        outline: true,
        outlineColor: Cesium.Color.fromCssColorString("#8affff").withAlpha(0.7),
        outlineWidth: 2,
      },
    });
  }

  function applyQueryParams() {
    const params = new URLSearchParams(window.location.search);
    const lat = params.get("lat");
    const lon = params.get("lon");
    const dist = params.get("dist");
    const residents = params.get("residents");
    const services = params.get("services");
    const speed = params.get("speed");
    const radius = params.get("radius");
    const auto = params.get("auto");

    if (lat) inputLat.value = lat;
    if (lon) inputLon.value = lon;
    if (dist) inputDist.value = dist;
    if (residents) inputResidents.value = residents;
    if (services) inputServices.value = services;
    if (speed) inputSpeed.value = speed;
    if (radius) inputRadius.value = radius;

    updateStewardAura();

    if (auto === "1") {
      setTimeout(() => {
        btnNew.click();
      }, 400);
    }
  }

  applyQueryParams();

  function mercatorToLon(x) {
    return (x / R) * (180 / Math.PI);
  }

  function mercatorToLat(y) {
    return (2 * Math.atan(Math.exp(y / R)) - Math.PI / 2) * (180 / Math.PI);
  }

  function lonLatOffset(lon, lat, dxMeters, dyMeters) {
    const x = R * (lon * Math.PI / 180);
    const y = R * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI / 180) / 2));
    const nx = x + dxMeters;
    const ny = y + dyMeters;
    return {
      lon: mercatorToLon(nx),
      lat: mercatorToLat(ny),
    };
  }

  function worldToLonLat(x, y, state) {
    const bbox = state?.world?.bbox;
    if (bbox) {
      const mercX = bbox.min_x + x;
      const mercY = bbox.min_y + y;
      return { lon: mercatorToLon(mercX), lat: mercatorToLat(mercY) };
    }
    const world = state?.world || { width: 1, height: 1 };
    const dx = (x - world.width / 2) * GRID_METERS_PER_UNIT;
    const dy = (y - world.height / 2) * GRID_METERS_PER_UNIT;
    return lonLatOffset(gridCenter.lon, gridCenter.lat, dx, dy);
  }

  function syncEntities(map, items, color, pixelSize, state, prefix) {
    const seen = new Set();
    items.forEach((item) => {
      const id = `${prefix}-${item.id}`;
      seen.add(id);
      const pos = worldToLonLat(item.x, item.y, state);
      let entity = map.get(id);
      if (!entity) {
        entity = viewer.entities.add({
          id,
          position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, 2),
          point: {
            pixelSize,
            color,
            outlineColor: Cesium.Color.BLACK.withAlpha(0.6),
            outlineWidth: 1,
          },
        });
        map.set(id, entity);
      } else {
        entity.position = Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, 2);
      }
    });
    map.forEach((entity, id) => {
      if (!seen.has(id)) {
        viewer.entities.remove(entity);
        map.delete(id);
      }
    });
  }

  function updateMetrics(state) {
    if (!state) return;
    metricStep.textContent = state.step ?? "-";
    metricResidents.textContent = state.counts?.residents ?? "-";
    metricServices.textContent = state.counts?.services ?? "-";
    metricServed.textContent = state.metrics?.served ?? "-";
    metricAvg.textContent = state.metrics?.avg_distance?.toFixed(2) ?? "-";
    metricMax.textContent = state.metrics?.max_distance?.toFixed(2) ?? "-";
  }

  function updateEntities(state) {
    if (!state) return;
    syncEntities(
      serviceEntities,
      state.services || [],
      Cesium.Color.fromCssColorString("#ffd166"),
      10,
      state,
      "service"
    );
    syncEntities(
      residentEntities,
      state.residents || [],
      Cesium.Color.fromCssColorString("#64f3ff"),
      6,
      state,
      "resident"
    );
  }

  function flyToState(state) {
    if (!state) return;
    const bbox = state?.world?.bbox;
    if (bbox) {
      const west = mercatorToLon(bbox.min_x);
      const east = mercatorToLon(bbox.max_x);
      const south = mercatorToLat(bbox.min_y);
      const north = mercatorToLat(bbox.max_y);
      const rect = Cesium.Rectangle.fromDegrees(west, south, east, north);
      viewer.camera.flyTo({ destination: rect });
      return;
    }
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(gridCenter.lon, gridCenter.lat, 1200),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-45),
        roll: 0,
      },
    });
  }

  async function apiPost(url, payload) {
    const response = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload || {}),
    });
    const data = await response.json();
    if (!response.ok || data.ok === false) {
      const message = data.message || `Request failed: ${response.status}`;
      throw new Error(message);
    }
    return data;
  }

  async function createRun() {
    if (busy) return;
    busy = true;
    setStatus("Creating run...");
    try {
      gridCenter = {
        lat: Number(inputLat.value) || 19.4326,
        lon: Number(inputLon.value) || -99.1332,
      };
      updateStewardAura();
      const payload = {
        residents: inputResidents.value,
        services: inputServices.value,
        speed: inputSpeed.value,
        service_radius: inputRadius.value,
      };
      if (inputRoads.checked) {
        payload.use_roads = true;
        payload.lat = inputLat.value;
        payload.lon = inputLon.value;
        payload.dist = inputDist.value;
      }
      const data = await apiPost("/api/simulations/new", payload);
      runId = data.run_id;
      lastState = data.state;
      updateMetrics(data.state);
      updateEntities(data.state);
      flyToState(data.state);

      const mode = data.state?.world?.mode || "grid";
      const roadError = data.state?.world?.road_error;
      if (roadError) {
        setStatus(`Roads fallback: ${roadError}`);
      } else {
        setStatus(`Run ${runId.slice(0, 8)} ready (${mode}).`);
      }
    } catch (err) {
      setStatus(err.message || "Failed to create run.");
    } finally {
      busy = false;
    }
  }

  async function stepRun() {
    if (busy || !runId) return;
    busy = true;
    try {
      const data = await apiPost("/api/simulations/step", { run_id: runId, steps: 1 });
      lastState = data.state;
      updateMetrics(data.state);
      updateEntities(data.state);
      setStatus(`Stepped to ${data.state.step}.`);
    } catch (err) {
      setStatus(err.message || "Failed to step.");
    } finally {
      busy = false;
    }
  }

  function toggleAuto() {
    if (autoTimer) {
      clearInterval(autoTimer);
      autoTimer = null;
      btnAuto.textContent = "Auto Run";
      setStatus("Auto run stopped.");
      return;
    }
    if (!runId) {
      setStatus("Create a run first.");
      return;
    }
    btnAuto.textContent = "Stop Auto";
    autoTimer = setInterval(() => {
      stepRun();
    }, 300);
    setStatus("Auto run started.");
  }

  btnNew.addEventListener("click", () => {
    if (autoTimer) {
      clearInterval(autoTimer);
      autoTimer = null;
      btnAuto.textContent = "Auto Run";
    }
    createRun();
  });

  btnStep.addEventListener("click", () => {
    stepRun();
  });

  btnAuto.addEventListener("click", () => {
    toggleAuto();
  });

  createRun();
</script>
{% endblock %}
