/**
 * SimCanvas — the fullscreen map renderer.
 *
 * Draws the campus map (day + night blend by clock), agents as colored
 * dots with labels and glow, camera follow on focus, and drag-to-pan,
 * with smooth exponential interpolation between tick positions.
 *
 * Architecture: the visual heart of the dashboard, fed entirely by the
 * snapshot; no direct backend calls beyond fetching map image assets.
 *
 * Design: interpolation hides tick granularity; night overlay is a time-
 * based alpha blend, not a separate simulation state.
 */

import { useEffect, useRef } from "react";
import { lerp } from "../utils/lerp";

const LERP_FACTOR = 0.12;
const CAM_LERP = 0.10;

function nightAlphaForHour(h) {
  // 0.85 at 00–04 and 22–24, ~0 between 09 and 17, smooth ramps between.
  if (h >= 22 || h < 5) return 0.82;
  if (h >= 9 && h <= 16) return 0.0;
  if (h >= 5 && h < 9) return 0.82 * (1 - (h - 5) / 4);   // dawn ramp down
  return 0.82 * ((h - 16) / 6);                            // dusk ramp up (17–22)
}

export default function SimCanvas({ snapshot, focusedId, onFocus }) {
  const canvasRef = useRef(null);
  const mapImgRef = useRef(null);
  const nightImgRef = useRef(null);
  const agentsRef = useRef({});
  const rafRef = useRef(null);
  const nightAlphaRef = useRef(0);
  const camRef = useRef({ x: 0, y: 0, tx: 0, ty: 0, userPan: false });
  const viewRef = useRef({ ox: 0, oy: 0, scale: 1 });
  const focusRef = useRef(null);

  useEffect(() => { focusRef.current = focusedId; }, [focusedId]);

  // Read URLs from the backend so Hugging Face Space variables work at runtime.
  useEffect(() => {
    let disposed = false;
    const tryLoadImage = (urls, filename, onLoaded) => {
      const baseUrl = import.meta.env?.BASE_URL || "/";
      const cleanBase = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
      const candidates = [...urls, filename, `./${filename}`, `/${filename}`, `${cleanBase}${filename}`];
      let idx = 0;
      const loadNext = () => {
        if (idx >= candidates.length) return;
        const img = new Image();
        const src = candidates[idx++];
        img.onload = () => { if (!disposed) onLoaded(img); };
        img.onerror = () => loadNext();
        img.src = src;
      };
      loadNext();
    };

    async function loadMaps() {
      let assets = {};
      try {
        const response = await fetch(`${import.meta.env.BASE_URL}api/assets`);
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        assets = await response.json();
      } catch (error) {
        console.warn("Could not read remote map configuration; using local assets.", error);
      }
      if (disposed) return;
      tryLoadImage(assets.map_image_url ? [assets.map_image_url] : [], "map.png", (img) => {
        mapImgRef.current = img;
      });
      tryLoadImage(assets.night_image_url ? [assets.night_image_url] : [], "map_night.png", (img) => {
        nightImgRef.current = img;
      });
      startLoop();
    }

    loadMaps();
    return () => { disposed = true; };
  }, []);

  // Update targets from snapshot
  useEffect(() => {
    if (!snapshot?.agents) return;
    if (snapshot.time) {
      const h = parseInt(String(snapshot.time).split(":")[0], 10) || 0;
      nightAlphaRef.current = nightAlphaForHour(h);
    }
    for (const [id, data] of Object.entries(snapshot.agents)) {
      if (!agentsRef.current[id]) {
        agentsRef.current[id] = { x: data.position.x, y: data.position.y };
      }
      const a = agentsRef.current[id];
      a.tx = data.position.x;
      a.ty = data.position.y;
      a.color = data.color;
      a.name = data.name;
      a.activity = data.activity || "";
      a.inConversation = data.in_conversation;
    }
    if (!rafRef.current) startLoop();
  }, [snapshot]);

  function startLoop() {
    if (rafRef.current) return;
    function tick() {
      const canvas = canvasRef.current;
      if (!canvas) { rafRef.current = null; return; }
      const ctx = canvas.getContext("2d");
      const W = (canvas.width = window.innerWidth);
      const H = (canvas.height = window.innerHeight);
      const img = mapImgRef.current;

      ctx.clearRect(0, 0, W, H);
      ctx.fillStyle = "#000000";
      ctx.fillRect(0, 0, W, H);

      const iw = img ? img.width : 1276;
      const ih = img ? img.height : 1233;
      const scale = Math.min(W / iw, H / ih) * 0.92;
      const baseOx = (W - iw * scale) / 2;
      const baseOy = (H - ih * scale) / 2;

      // Camera: follow focused agent unless the user is panning.
      const cam = camRef.current;
      const fid = focusRef.current;
      if (fid && agentsRef.current[fid] && !cam.userPan) {
        const a = agentsRef.current[fid];
        cam.tx = W / 2 - (baseOx + a.x * scale);
        cam.ty = H / 2 - (baseOy + a.y * scale);
      }
      cam.x = lerp(cam.x, cam.tx, CAM_LERP);
      cam.y = lerp(cam.y, cam.ty, CAM_LERP);
      const ox = baseOx + cam.x, oy = baseOy + cam.y;
      viewRef.current = { ox, oy, scale };

      // advance dot positions
      for (const id of Object.keys(agentsRef.current)) {
        const a = agentsRef.current[id];
        if (a.tx == null) continue;
        a.x = lerp(a.x, a.tx, LERP_FACTOR);
        a.y = lerp(a.y, a.ty, LERP_FACTOR);
      }

      ctx.save();
      ctx.translate(ox, oy);
      ctx.scale(scale, scale);

      if (img) {
        ctx.drawImage(img, 0, 0);
        // Day/night blend: overlay the night map at a time-based opacity.
        const na = nightAlphaRef.current;
        if (nightImgRef.current && na > 0.01) {
          ctx.globalAlpha = na;
          ctx.drawImage(nightImgRef.current, 0, 0);
          ctx.globalAlpha = 1;
        }
      } else {
        // Procedural campus background fallback if image is loading or missing
        ctx.fillStyle = "#000000";
        ctx.fillRect(0, 0, iw, ih);

        // Draw campus grid lines
        ctx.strokeStyle = "rgba(255, 255, 255, 0.06)";
        ctx.lineWidth = 1;
        const step = 60;
        for (let x = 0; x <= iw; x += step) {
          ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, ih); ctx.stroke();
        }
        for (let y = 0; y <= ih; y += step) {
          ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(iw, y); ctx.stroke();
        }

        // Campus boundary border
        ctx.strokeStyle = "rgba(212, 160, 74, 0.4)";
        ctx.lineWidth = 3;
        ctx.strokeRect(0, 0, iw, ih);

        // Campus title label
        ctx.fillStyle = "rgba(255, 255, 255, 0.35)";
        ctx.font = "14px 'Space Mono', monospace";
        ctx.textAlign = "center";
        ctx.fillText("VALHALLA CAMPUS MAP", iw / 2, 40);
      }

      // draw agents
      for (const id of Object.keys(agentsRef.current)) {
        const a = agentsRef.current[id];
        if (a.x == null) continue;
        const focused = id === fid;
        const r = focused ? 7 : 4;

        // glow / focus ring
        const glowR = focused ? 22 : 11;
        const glow = ctx.createRadialGradient(a.x, a.y, 0, a.x, a.y, glowR);
        glow.addColorStop(0, (a.color || "#fff") + (focused ? "90" : "50"));
        glow.addColorStop(1, (a.color || "#fff") + "00");
        ctx.beginPath();
        ctx.arc(a.x, a.y, glowR, 0, Math.PI * 2);
        ctx.fillStyle = glow;
        ctx.fill();

        if (a.inConversation) {
          ctx.beginPath();
          ctx.arc(a.x, a.y, r + 5, 0, Math.PI * 2);
          ctx.strokeStyle = "#d4a04a";
          ctx.lineWidth = 1.5;
          ctx.stroke();
        }

        ctx.beginPath();
        ctx.arc(a.x, a.y, r, 0, Math.PI * 2);
        ctx.fillStyle = a.color || "#fff";
        ctx.fill();
        ctx.strokeStyle = focused ? "#fff" : "rgba(255,255,255,0.7)";
        ctx.lineWidth = focused ? 2 : 1.2;
        ctx.stroke();

        // labels (name + short activity)
        const fontPx = Math.max(9, 11 / scale);
        ctx.font = `${fontPx}px 'Outfit', sans-serif`;
        ctx.textAlign = "center";
        ctx.fillStyle = "#fff";
        ctx.strokeStyle = "rgba(0,0,0,0.7)";
        ctx.lineWidth = 3 / scale;
        const ly = a.y - r - 6 / scale;
        ctx.strokeText(a.name || id, a.x, ly);
        ctx.fillText(a.name || id, a.x, ly);
        if (focused && a.activity) {
          const af = Math.max(8, 9 / scale);
          ctx.font = `${af}px 'Space Mono', monospace`;
          ctx.fillStyle = "#d4a04a";
          const act = a.activity.length > 40 ? a.activity.slice(0, 40) + "…" : a.activity;
          ctx.strokeText(act, a.x, a.y + r + 12 / scale);
          ctx.fillText(act, a.x, a.y + r + 12 / scale);
        }
      }
      ctx.restore();

      // vignette
      const vig = ctx.createRadialGradient(W / 2, H / 2, H * 0.15, W / 2, H / 2, H * 0.78);
      vig.addColorStop(0, "rgba(0,0,0,0)");
      vig.addColorStop(1, "rgba(0,0,0,0.5)");
      ctx.fillStyle = vig;
      ctx.fillRect(0, 0, W, H);

      rafRef.current = requestAnimationFrame(tick);
    }
    rafRef.current = requestAnimationFrame(tick);
  }

  // Click-to-focus + drag-to-pan
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    let dragging = false, moved = false, sx = 0, sy = 0, startCam = null;

    function onDown(e) {
      dragging = true; moved = false;
      sx = e.clientX; sy = e.clientY;
      startCam = { ...camRef.current };
    }
    function onMove(e) {
      if (!dragging) return;
      const dx = e.clientX - sx, dy = e.clientY - sy;
      if (Math.abs(dx) + Math.abs(dy) > 4) moved = true;
      camRef.current.userPan = true;
      camRef.current.tx = startCam.tx + dx;
      camRef.current.ty = startCam.ty + dy;
    }
    function onUp(e) {
      dragging = false;
      if (moved) return;
      // treat as a click: hit-test agents
      const { ox, oy, scale } = viewRef.current;
      const mx = (e.clientX - ox) / scale;
      const my = (e.clientY - oy) / scale;
      let best = null, bestD = 18 / scale;
      for (const id of Object.keys(agentsRef.current)) {
        const a = agentsRef.current[id];
        const d = Math.hypot(a.x - mx, a.y - my);
        // Names are rendered above the marker, so make the visible name area
        // clickable too. This keeps the map as the sole, uncluttered roster.
        const nameHalfWidth = Math.max(22, (a.name || id).length * 3.6) / scale;
        const inName = Math.abs(a.x - mx) <= nameHalfWidth
          && my >= a.y - 24 / scale
          && my <= a.y - 2 / scale;
        if (inName || d < bestD) { bestD = d; best = id; }
      }
      if (onFocus) {
        if (best) { camRef.current.userPan = false; onFocus(best); }
        else onFocus(null);
      }
    }
    canvas.addEventListener("mousedown", onDown);
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    const onResize = () => { if (!rafRef.current) startLoop(); };
    window.addEventListener("resize", onResize);
    return () => {
      canvas.removeEventListener("mousedown", onDown);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
      window.removeEventListener("resize", onResize);
      cancelAnimationFrame(rafRef.current);
      rafRef.current = null;
    };
  }, [onFocus]);

  return (
    <canvas
      ref={canvasRef}
      style={{ position: "fixed", top: 0, left: 0, cursor: "grab", display: "block" }}
    />
  );
}
