import { useRef, useState, useEffect, useCallback } from "react";
import { getRadarPosition } from "../utilities/utilities";
import Player from "./player";
import Bomb from "./bomb";
import Grenade from "./grenade";
import { SmokeOverlay, InfernoOverlay } from "./Smokeandinferno";
import PlayerCard from "./playercard";
import Scoreboard from "./scoreboard";
import KillFeed from "./killfeed";
import { Latency } from "./latency";
import { useAutozoom } from "../hooks/useAutozoom";
import { usePovLock } from "../hooks/usePovLock";
import Advisories from "./advisories";
import PovPlayerList from "./PovPlayerList";
import PlayerContextMenu from "./PlayerContextMenu";

const MIN_ZOOM = 1.0;
const MAX_ZOOM = 6.0;
const ZOOM_FACTOR = 1.18;

// ─── Dropped Weapon Icon ────────────────────────────────────────────────────────
const UTILITY_IDS = new Set([
  "flashbang", "hegrenade", "smokegrenade",
  "molotov", "incgrenade", "decoy",
]);

const isUtilityType = (type) => UTILITY_IDS.has(type);

const DroppedWeaponIcon = ({ weaponType, screenX, screenY, weaponKey, settings }) => {
  const isUtil = isUtilityType(weaponType);
  const sizeMult = isUtil
    ? (settings?.droppedUtilitySize ?? 1.0)
    : (settings?.droppedWeaponSize ?? 1.0);
  const iconSize = 18 * sizeMult;
  const itemOpacity = isUtil
    ? (settings?.droppedUtilityOpacity ?? 0.92)
    : (settings?.droppedWeaponOpacity ?? 0.92);
  const userColor = isUtil
    ? (settings?.droppedUtilityColor ?? "#94a3b8")
    : (settings?.droppedWeaponColor ?? "#94a3b8");
  const useCustomColor = userColor && userColor !== '#94a3b8';
  const iconUrl = `/assets/icons/${weaponType}.svg`;

  // ── Base container style (shared by both modes) ──
  const containerStyle = {
    width: iconSize,
    height: iconSize,
    left: screenX - iconSize / 2,
    top: screenY - iconSize / 2,
    position: 'absolute',
    zIndex: 120,
    opacity: itemOpacity,
  };

  // ── Custom color mode: SVG as CSS mask on colored background ──
  // This ensures the color change is visible immediately (no onError dependency)
  if (useCustomColor) {
    return (
      <div key={weaponKey} className="absolute pointer-events-none" style={containerStyle}>
        <div style={{
          width: '100%',
          height: '100%',
          backgroundColor: userColor,
          boxShadow: `0 0 5px ${userColor}`,
          WebkitMaskImage: `url(${iconUrl})`,
          maskImage: `url(${iconUrl})`,
          WebkitMaskSize: 'contain',
          maskSize: 'contain',
          WebkitMaskRepeat: 'no-repeat',
          maskRepeat: 'no-repeat',
          WebkitMaskPosition: 'center',
          maskPosition: 'center',
        }} />
      </div>
    );
  }

  // ── Default mode: show SVG, fallback to diamond on error ──
  const [svgError, setSvgError] = useState(false);

  if (svgError) {
    return (
      <div
        key={weaponKey}
        className="absolute pointer-events-none"
        style={{
          ...containerStyle,
          borderRadius: 2,
          transform: 'rotate(45deg)',
          backgroundColor: '#94a3b8',
          boxShadow: '0 0 6px #94a3b8',
          filter: 'drop-shadow(0 1px 3px rgba(0,0,0,0.8))',
        }}
      />
    );
  }

  return (
    <div key={weaponKey} className="absolute pointer-events-none" style={{
      ...containerStyle,
      filter: 'drop-shadow(0 1px 3px rgba(0,0,0,0.8))',
    }}>
      <img
        src={iconUrl}
        alt={weaponType}
        style={{ width: '100%', height: '100%', objectFit: 'contain' }}
        onError={() => setSvgError(true)}
      />
    </div>
  );
};

const Radar = ({
  playerArray,
  radarImage,
  mapData,
  localTeam,
  averageLatency,
  bombData,
  settings,
  grenadeArray = [],
  droppedWeaponArray = [],
  roundInfo = null,
  publicIP    = null,
  copied      = false,
  copyAddress = null,
  setSettings = null,
  lockedPlayerIdx = null,
  setLockedPlayerIdx = () => {},
}) => {
  const containerRef  = useRef();
  const radarImageRef = useRef();
  const [isFullscreen, setIsFullscreen] = useState(false);

  // Natural letterboxed rect of the image (pixels, relative to container)
  const [imgStyle, setImgStyle] = useState({ w: 0, h: 0, ml: 0, mt: 0 });

  // Zoom & pan. Pan is in screen pixels relative to container centre.
  const [zoom, setZoom] = useState(1);
  const [pan,  setPan]  = useState({ x: 0, y: 0 });
  const dragRef  = useRef(null);
  const touchRef = useRef(null);
  
  // Manual zoom tracking for autozoom
  const manualZoomRef = useRef(false);
  const manualZoomTimeoutRef = useRef(null);
  // Ref to resetQueues so map-change effect can call it without hoisting issues
  const resetQueuesRef = useRef(null);
  
  // POV Lock state — held in App.jsx so PipWindow shares it
  // lockedPlayerIdx + setLockedPlayerIdx come from props
  const [showPovList, setShowPovList] = useState(false);
  const [showContextMenu, setShowContextMenu] = useState(null);

  const onUpdateSettings = useCallback((patch) => {
    setSettings?.(prev => ({ ...prev, ...patch }));
  }, [setSettings]);

  const HIT_RADIUS = 20;
  const findPlayerAtScreenPosition = useCallback((cursorX, cursorY) => {
    if (!containerRef.current || !imgStyle.w || !playerArray?.length) return null;
    const rect = containerRef.current.getBoundingClientRect();
    const cx = cursorX - rect.left;
    const cy = cursorY - rect.top;
    const cw = rect.width;
    const ch = rect.height;
    let closest = null;
    let closestDist = HIT_RADIUS;
    for (const player of playerArray) {
      if (!mapData) continue;
      const pos = getRadarPosition(mapData, player.m_position ?? player);
      if (!pos) continue;
      const px = imgStyle.ml + imgStyle.w * pos.x;
      const py = imgStyle.mt + imgStyle.h * pos.y;
      const mapCX = cw / 2 + pan.x;
      const mapCY = ch / 2 + pan.y;
      const screenX = mapCX + (px - cw / 2) * zoom;
      const screenY = mapCY + (py - ch / 2) * zoom;
      const dist = Math.sqrt((cx - screenX) ** 2 + (cy - screenY) ** 2);
      if (dist < closestDist) { closestDist = dist; closest = player; }
    }
    return closest;
  }, [playerArray, mapData, imgStyle, pan, zoom]);

  // POV Lock hook — returns rotation/cumulativeRef for the component to own
  const { rotation: cumulativeRot, targetRot, rawTarget, isLocked, cumulativeRotRef } = usePovLock({
    lockedPlayerIdx,
    playerArray,
    mapData,
    imgStyle,
    setZoom,
    setPan,
    zoom: settings?.followZoom ?? 3.5,
  });

  const onFollowRotate = useCallback((idx) => {
    const player = playerArray?.find(p => p.m_idx === idx);
    if (!player || player.m_is_dead) return;
    cumulativeRotRef.current = 0;
    onUpdateSettings({ followYourselfRotation: true });
    setLockedPlayerIdx(idx);
    setShowPovList(false);
    setShowContextMenu(null);
  }, [playerArray, cumulativeRotRef, onUpdateSettings]);

  // Smooth rotation via rAF for POV lock (avoid re-render every frame for map)
  // cumulativeRotRef is owned here; Player gets cumulativeRot for counter-rotate
  const mapWrapperRef = useRef(null);
  const rotationRef  = useRef(0);
  const rafIdRef     = useRef(null);
  const playerArrayRef = useRef(playerArray);
  const mapDataRef     = useRef(mapData);
  playerArrayRef.current = playerArray;
  mapDataRef.current     = mapData;
  const [playerRotation, setPlayerRotation] = useState(0);

  useEffect(() => {
    if (!isLocked) {
      if (rafIdRef.current) { cancelAnimationFrame(rafIdRef.current); rafIdRef.current = null; }
      rotationRef.current = 0;
      setPlayerRotation(0);
      // Restore map transform without rotation
      if (mapWrapperRef.current)
        mapWrapperRef.current.style.transform = `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`;
      return;
    }

    let frameCount = 0;
    const animate = () => {
      if (mapWrapperRef.current) {
        const doRotate = settings.followYourselfRotation && lockedPlayerIdx !== null;
        const target = doRotate ? targetRot + cumulativeRotRef.current : 0;
        let delta = target - rotationRef.current;
        while (delta >  180) delta -= 360;
        while (delta < -180) delta += 360;
        rotationRef.current += delta * 0.2;

        if (doRotate) {
          // When rotating, recalculate pan every frame so player stays centred.
          // The static pan values from usePovLock are for the unrotated map; after
          // rotation the player would drift.  We project the player position through
          // the rotation matrix and translate to keep the dot at centre.
          const pa = playerArrayRef.current;
          const md = mapDataRef.current;
          const lockedPlayer = pa?.find(p => p.m_idx === lockedPlayerIdx && !p.m_is_dead);
          if (lockedPlayer && md) {
            const pos = getRadarPosition(md, lockedPlayer.m_position ?? lockedPlayer);
            if (pos) {
              const cw = containerRef.current?.clientWidth || 1024;
              const ch = containerRef.current?.clientHeight || 1024;
              const ml = imgStyle.ml ?? 0;
              const mt = imgStyle.mt ?? 0;
              const iw = imgStyle.w || 1024;
              const ih = imgStyle.h || 1024;

              // Player pixel position on unrotated wrapper
              const px = ml + iw * pos.x;
              const py = mt + ih * pos.y;

              // Offset from container centre, scaled
              const rx = (px - cw / 2) * zoom;
              const ry = (py - ch / 2) * zoom;

              // Rotate the offset so the player dot stays fixed
              const angleRad = rotationRef.current * Math.PI / 180;
              const c = Math.cos(angleRad);
              const s = Math.sin(angleRad);
              const rotX = rx * c - ry * s;
              const rotY = rx * s + ry * c;

              // Pan = -rotated offset  → brings player to centre
              mapWrapperRef.current.style.transform =
                `translate(${-rotX}px, ${-rotY}px) scale(${zoom}) rotate(${rotationRef.current}deg)`;
            }
          }
        } else {
          mapWrapperRef.current.style.transform =
            `translate(${pan.x}px, ${pan.y}px) scale(${zoom}) rotate(${rotationRef.current}deg)`;
        }

        frameCount++;
        if (frameCount % 3 === 0) setPlayerRotation(rotationRef.current);
      }
      rafIdRef.current = requestAnimationFrame(animate);
    };

    rafIdRef.current = requestAnimationFrame(animate);
    return () => { if (rafIdRef.current) { cancelAnimationFrame(rafIdRef.current); rafIdRef.current = null; } };
  }, [isLocked, targetRot, pan.x, pan.y, zoom, cumulativeRotRef, settings?.followYourselfRotation, settings?.followYourself, lockedPlayerIdx]);

  // ── measure letterbox rect ──────────────────────────────────────────────────
  const measure = useCallback(() => {
    const img = radarImageRef.current;
    const con = containerRef.current;
    if (!img || !con) return;
    const nw = img.naturalWidth  || 1024;
    const nh = img.naturalHeight || 1024;
    const cw = con.clientWidth;
    const ch = con.clientHeight;
    const ratio = nw / nh;
    let w, h;
    if (cw / ch > ratio) { w = ch * ratio; h = ch; }
    else                  { w = cw;         h = cw / ratio; }
    setImgStyle({ w, h, ml: (cw - w) / 2, mt: (ch - h) / 2 });
  }, []);

  useEffect(() => {
    const img = radarImageRef.current;
    const con = containerRef.current;
    if (!img || !con) return;
    img.addEventListener("load", measure);
    const ro = new ResizeObserver(measure);
    ro.observe(con);
    measure();
    return () => { img.removeEventListener("load", measure); ro.disconnect(); };
  }, [radarImage, isFullscreen, measure]);

  // Reset on map change
  useEffect(() => { 
    setZoom(1); 
    setPan({ x: 0, y: 0 }); 
    manualZoomRef.current = false;
    if (manualZoomTimeoutRef.current) clearTimeout(manualZoomTimeoutRef.current);
    // resetQueues is defined after useAutozoom call — use a ref to avoid hoisting issue
    resetQueuesRef.current?.();
    // Auto-unlock POV when map changes
    setLockedPlayerIdx(null);
  }, [radarImage]);

  // Clamp pan so zoomed map can't scroll outside container.
  // Wrapper fills container (inset:0), scale around centre.
  // Max pan = half of (scaled image size - container size).
  const clamp = useCallback((px, py, z) => {
    const con = containerRef.current;
    if (!con || !imgStyle.w) return { x: px, y: py };
    const maxX = Math.max(0, (imgStyle.w * z - con.clientWidth)  / 2);
    const maxY = Math.max(0, (imgStyle.h * z - con.clientHeight) / 2);
    return {
      x: Math.min(maxX, Math.max(-maxX, px)),
      y: Math.min(maxY, Math.max(-maxY, py)),
    };
  }, [imgStyle]);

  // Integrate useAutozoom hook
  const { resetQueues } = useAutozoom({
    enabled: settings?.autoZoom === true && !isLocked,
    playerArray,
    mapData,
    imgStyle,
    manualZoomRef,
    setZoom,
    setPan,
    clamp,
    settings,
  });
  // Keep resetQueuesRef in sync so map-change effect can call it
  resetQueuesRef.current = resetQueues;

  // POV Lock: disable autozoom and manual zoom/pan when locked
  useEffect(() => {
    if (isLocked) {
      manualZoomRef.current = true;
    } else {
      manualZoomRef.current = false;
    }
  }, [isLocked]);

  // When locked player dies, release lock temporarily.
  // Settings (followYourself / followYourselfRotation) stay ON so that the
  // follow-yourself effect below re-locks automatically on respawn.
  // While dead, auto-zoom takes over naturally (no conflict).
  useEffect(() => {
    if (lockedPlayerIdx !== null) {
      const lockedPlayer = playerArray.find(p => p.m_idx === lockedPlayerIdx);
      if (!lockedPlayer || lockedPlayer.m_is_dead) {
        setLockedPlayerIdx(null);
      }
    }
  }, [playerArray, lockedPlayerIdx]);

  useEffect(() => {
    if (!showContextMenu) return;
    const close = () => setShowContextMenu(null);
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, [showContextMenu]);

  // ── Follow Yourself Mode ───────────────────────────────────────────────────
  // Rules:
  //   • Only locks to self when NOT already locked to someone else (respects manual selection).
  //   • When disabled, does NOT clear lockedPlayerIdx (only unlock/death effects do that).
  //   • On death of followed player, death effect clears lockedPlayerIdx → followYourself
  //     re-locks to self on next update (respawn or alive self found).
  //   • Manual "Unlock" button disables followYourself so no re-lock happens.
  //   • Manual selection of another player sets lockedPlayerIdx → followYourself doesn't override.
  useEffect(() => {
    const selfId = settings?.whichPlayerAreYou;
    const selfSteamId = settings?.yourSteamId;
    const enabled = settings?.followYourself === true;
    if (!enabled || !selfId || selfId === "0") {
      return; // Don't clear lockedPlayerIdx — that would override manual selection/unlock
    }

    // Find self player: try steam ID first (stable across deaths/respawns),
    // then fall back to m_idx (legacy whichPlayerAreYou may become stale on respawn).
    let selfPlayer = null;

    // 1) Find by steam ID — stable, survives death/respawn
    if (selfSteamId) {
      selfPlayer = playerArray.find(p => String(p.m_steam_id) === String(selfSteamId) && !p.m_is_dead);
    }

    // 2) Fall back to m_idx (legacy / steam ID not yet set)
    if (!selfPlayer) {
      selfPlayer = playerArray.find(p => p.m_idx === Number(selfId) && !p.m_is_dead);
    }

    if (selfPlayer) {
      // Only auto-lock if not currently locked to a DIFFERENT player (manual selection).
      // If locked to self already → update idx on respawn.
      // If locked to someone else → don't override (user's manual choice).
      if (lockedPlayerIdx === null || lockedPlayerIdx === selfPlayer.m_idx) {
        setLockedPlayerIdx(selfPlayer.m_idx);
        // If m_idx changed (e.g. after respawn), update whichPlayerAreYou so
        // other m_idx-based lookups (PovPlayerList, settings display) stay correct.
        if (String(selfPlayer.m_idx) !== String(selfId)) {
          onUpdateSettings({ whichPlayerAreYou: String(selfPlayer.m_idx) });
        }
      }
    }
  }, [settings?.followYourself, settings?.whichPlayerAreYou, settings?.yourSteamId, playerArray, lockedPlayerIdx]);

  // POV Lock: use followYourselfRotation (not povLockRotation) for map rotation
  useEffect(() => {
    // Skip clamp when POV lock is active — player must stay centered
    if (lockedPlayerIdx !== null) return;
    setPan(prev => clamp(prev.x, prev.y, zoom));
  }, [zoom, imgStyle, clamp, lockedPlayerIdx]);

  // Reset manual zoom when fullscreen toggles so autozoom can resume
  useEffect(() => {
    manualZoomRef.current = false;
    if (manualZoomTimeoutRef.current) clearTimeout(manualZoomTimeoutRef.current);
    resetQueuesRef.current?.();
    setZoom(1);
    setPan({ x: 0, y: 0 });
  }, [isFullscreen]); // eslint-disable-line react-hooks/exhaustive-deps

  // ── fullscreen ──────────────────────────────────────────────────────────────
  useEffect(() => {
    const onChange = () => setIsFullscreen(!!document.fullscreenElement);
    document.addEventListener("fullscreenchange", onChange);
    return () => document.removeEventListener("fullscreenchange", onChange);
  }, []);

  const toggleFullscreen = () => {
    if (!document.fullscreenElement)
      containerRef.current.requestFullscreen().catch(() => {});
    else document.exitFullscreen();
  };

  // ── scroll-wheel zoom ───────────────────────────────────────────────────────
  const onWheel = useCallback((e) => {
    e.preventDefault();
    const con = containerRef.current;
    if (!con) return;
    const rect = con.getBoundingClientRect();
    // Cursor offset from container centre (matches our pan coordinate space)
    const cx = e.clientX - (rect.left + rect.width  / 2);
    const cy = e.clientY - (rect.top  + rect.height / 2);

    // Set manual zoom flag
    manualZoomRef.current = true;
    if (manualZoomTimeoutRef.current) clearTimeout(manualZoomTimeoutRef.current);
    manualZoomTimeoutRef.current = setTimeout(() => {
      manualZoomRef.current = false;
    }, 3000);

    setZoom(prev => {
      const next = e.deltaY < 0
        ? Math.min(MAX_ZOOM, prev * ZOOM_FACTOR)
        : Math.max(MIN_ZOOM, prev / ZOOM_FACTOR);
      const ratio = next / prev;
      // With transformOrigin "50% 50%", the map scales around its own centre.
      // The cursor is at (cx, cy) from container centre.
      // Current map centre in screen space = pan offset from container centre.
      // After scale by ratio, the point under cursor must stay fixed:
      //   newPan = pan + (cursor - pan) * (1 - ratio)
      //          = pan * ratio + cursor * (1 - ratio)
      setPan(prevPan => clamp(
        prevPan.x + (cx - prevPan.x) * (1 - ratio),
        prevPan.y + (cy - prevPan.y) * (1 - ratio),
        next
      ));
      return next;
    });
  }, [clamp]);

  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    el.addEventListener("wheel", onWheel, { passive: false });
    return () => el.removeEventListener("wheel", onWheel);
  }, [onWheel]);

  // ── drag-to-pan ─────────────────────────────────────────────────────────────
  const onMouseDown = useCallback((e) => {
    if (e.button !== 0) return;
    const hitPlayer = findPlayerAtScreenPosition(e.clientX, e.clientY);
    if (hitPlayer) {
      setShowContextMenu({ playerIdx: hitPlayer.m_idx, x: e.clientX, y: e.clientY });
      setShowPovList(false);
      return;
    }
    if (zoom <= 1) return;
    e.preventDefault();
    dragRef.current = { mx: e.clientX, my: e.clientY, px: pan.x, py: pan.y };
  }, [zoom, pan, findPlayerAtScreenPosition]);

  const onMouseMove = useCallback((e) => {
    if (!dragRef.current) return;
    setPan(clamp(
      dragRef.current.px + (e.clientX - dragRef.current.mx),
      dragRef.current.py + (e.clientY - dragRef.current.my),
      zoom
    ));
  }, [zoom, clamp]);

  const onMouseUp = useCallback(() => { dragRef.current = null; }, []);

  // Touch
  const onTouchStart = useCallback((e) => {
    if (zoom <= 1) return;
    touchRef.current = { tx: e.touches[0].clientX, ty: e.touches[0].clientY, px: pan.x, py: pan.y };
  }, [zoom, pan]);

  const onTouchMove = useCallback((e) => {
    if (!touchRef.current) return;
    e.preventDefault();
    setPan(clamp(
      touchRef.current.px + (e.touches[0].clientX - touchRef.current.tx),
      touchRef.current.py + (e.touches[0].clientY - touchRef.current.ty),
      zoom
    ));
  }, [zoom, clamp]);

  const onTouchEnd = useCallback(() => { touchRef.current = null; }, []);

  // ── visibility flags ────────────────────────────────────────────────────────
  const showTCards       = settings?.showTCards     !== false;
  const showCTCards      = settings?.showCTCards    !== false;
  const showScoreboard   = settings?.showScoreboard !== false;
  const showIndicators   = settings?.showIndicators !== false;

  // Wrapper fills the container (inset:0), transformOrigin "50% 50%" = container centre.
  // Transform: translate(pan) then scale(zoom) around container centre.
  // CSS applies transforms right-to-left, so scale happens first, then translate.
  // For POV lock, rotation is applied via rAF directly to DOM.
  // RAF loop owns the transform directly (rotation applied only when povLockRotation)
  // mapTransform is not used here — RAF sets style.transform each frame

  return (
    <div
      ref={containerRef}
      id="radar"
      className={`relative overflow-hidden flex items-center justify-center ${
        isFullscreen ? "w-screen h-screen bg-[#07141e]" : "w-full h-[700px] rounded-xl"
      }`}
      style={{ cursor: zoom > 1 ? (dragRef.current ? "grabbing" : "grab") : "default" }}
      onMouseDown={onMouseDown}
      onMouseMove={onMouseMove}
      onMouseUp={onMouseUp}
      onMouseLeave={onMouseUp}
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
    >
      {/* ── FULLSCREEN TOP BAR ──────────────────────────────────────────────── */}
      {isFullscreen && (
        <div
          onMouseDown={e => e.stopPropagation()}
          style={{
            position: "absolute", top: 0, left: 0, right: 0, height: 44, zIndex: 300,
            display: "flex", alignItems: "center", justifyContent: "flex-end",
            gap: 8, padding: "0 16px",
            background: "rgba(13,17,23,0.92)",
            borderBottom: "1px solid rgba(30,39,54,0.8)",
            backdropFilter: "blur(6px)",
          }}
        >
          {publicIP && copyAddress && (
            <button onClick={copyAddress} disabled={copied} style={{
              display: "flex", alignItems: "center", gap: 6,
              padding: "5px 11px", borderRadius: 6, background: "transparent",
              border: `1px solid ${copied ? "var(--hp-high)" : "var(--bg-border-dim)"}`,
              color: copied ? "var(--hp-high)" : "var(--text-secondary)",
              fontSize: 12, fontWeight: 700, cursor: "pointer",
              fontFamily: "inherit", letterSpacing: "0.04em", transition: "all 0.15s",
            }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                {copied
                  ? <path d="M20 6L9 17l-5-5" />
                  : <><rect x="9" y="9" width="13" height="13" rx="2" /><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" /></>}
              </svg>
              {copied ? "Copied!" : "Share Radar"}
            </button>
          )}
          {setSettings && <Latency settings={settings} setSettings={setSettings} />}
          {/* POV Controls in fullscreen */}
          {(() => {
            const fsSelfIdx = settings?.whichPlayerAreYou;
            const fsSelfSteamId = settings?.yourSteamId;
            const fsSelfChosen = (fsSelfIdx && fsSelfIdx !== "0") || !!fsSelfSteamId;
            // Find self player: prefer steam ID (stable across respawns), fall back to m_idx
            const fsSelfPlayer = fsSelfChosen
              ? (fsSelfSteamId
                  ? (playerArray.find(p => String(p.m_steam_id) === String(fsSelfSteamId) && !p.m_is_dead)
                     || (fsSelfIdx ? playerArray.find(p => p.m_idx === Number(fsSelfIdx)) : null))
                  : playerArray.find(p => p.m_idx === Number(fsSelfIdx)))
              : null;
            const fsSelfMIdx = fsSelfPlayer?.m_idx ?? null;
            const fsLockedPlayer = lockedPlayerIdx !== null
              ? playerArray.find(p => p.m_idx === lockedPlayerIdx)
              : null;
            return (
              <div style={{ position: "relative" }}>
                <button
                  onClick={() => setShowPovList(prev => !prev)}
                  style={{
                    width: 32, height: 32,
                    background: lockedPlayerIdx !== null
                      ? (settings?.followYourselfRotation ? "rgba(74,158,255,0.35)" : "rgba(74,158,255,0.2)")
                      : "transparent",
                    color: lockedPlayerIdx !== null ? "#7dd3fc" : "var(--text-secondary)",
                    border: `1px solid ${lockedPlayerIdx !== null ? "rgba(74,158,255,0.6)" : "var(--bg-border-dim)"}`,
                    borderRadius: 6, cursor: "pointer",
                    display: "flex", alignItems: "center", justifyContent: "center",
                    transition: "all 0.15s",
                  }}
                  title={fsLockedPlayer ? `Following: ${fsLockedPlayer.m_name}${settings?.followYourselfRotation ? " (rotating)" : ""}` : "Select player to follow"}
                >
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none">
                    <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
                    <circle cx="12" cy="12" r="3" fill="rgba(7,20,30,0.8)"/>
                  </svg>
                </button>
                {showPovList && (
                  <div
                    style={{ position: "absolute", top: "calc(100% + 6px)", right: 0, zIndex: 400 }}
                    onMouseDown={e => e.stopPropagation()}
                  >
                    <PovPlayerList
                      playerArray={playerArray}
                      lockedPlayerIdx={lockedPlayerIdx}
                      onSelect={(idx) => {
                        cumulativeRotRef.current = 0;
                        setLockedPlayerIdx(idx);
                        setShowPovList(false);
                        if (!settings?.whichPlayerAreYou || settings?.whichPlayerAreYou === "0") {
                          onUpdateSettings({ whichPlayerAreYou: String(idx) });
                        }
                      }}
                      onUnlock={() => {
                        setLockedPlayerIdx(null);
                        setShowPovList(false);
                        onUpdateSettings({ followYourself: false, followYourselfRotation: false });
                      }}
                      selfSteamId={settings?.whichPlayerAreYou}
                      selfMIdx={fsSelfMIdx}
                      followYourselfRotation={settings?.followYourselfRotation}
                      onToggleRotation={() => onUpdateSettings({ followYourselfRotation: !settings?.followYourselfRotation })}
                      isSelfChosen={fsSelfChosen}
                      cumulativeRotRef={cumulativeRotRef}
                      rawTarget={rawTarget}
                      onUpdateSettings={onUpdateSettings}
                      isSelfAlive={fsSelfPlayer && !fsSelfPlayer.m_is_dead}
                    />
                  </div>
                )}
              </div>
            );
          })()}
          <button onClick={toggleFullscreen} style={{
            display: "flex", alignItems: "center", gap: 5,
            padding: "5px 10px", borderRadius: 6, background: "transparent",
            border: "1px solid var(--bg-border-dim)", color: "var(--text-secondary)",
            fontSize: 12, fontWeight: 700, cursor: "pointer",
            fontFamily: "inherit", letterSpacing: "0.04em",
          }}>
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
            </svg>
            Exit
          </button>
        </div>
      )}

      {/* ── FULLSCREEN SCOREBOARD ───────────────────────────────────────────── */}
      {isFullscreen && showScoreboard && (
        <div style={{
          position: "absolute", top: 54, left: "50%",
          transform: "translateX(-50%)", zIndex: 250, pointerEvents: "none",
        }}>
          <Scoreboard roundInfo={roundInfo} localTeam={localTeam} bombData={bombData} />
        </div>
      )}

      {/* ── ADVISORIES ──────────────────────────────────────────────────────── */}
      {settings?.showAdvisories !== false && (
        <Advisories
          playerArray={playerArray}
          bombData={bombData}
          roundInfo={roundInfo}
          settings={settings}
        />
      )}

      {/* ── FULLSCREEN KILL FEED ─────────────────────────────────────────────── */}
      {isFullscreen && settings?.showKillFeed !== false && (
        <div style={{
          position: "absolute", bottom: 20,
          right: showCTCards ? 310 : 20, zIndex: 200, pointerEvents: "none",
        }}>
          <KillFeed playerArray={playerArray} />
        </div>
      )}

      {/* ── FULLSCREEN PLAYER PANELS ─────────────────────────────────────────── */}
      {isFullscreen && (
        <>
          <div className="absolute left-0 top-0 bottom-0 w-[350px] bg-gradient-to-r from-black/60 to-transparent pointer-events-none z-[170]" />
          {showTCards && (
            <div
              onMouseDown={e => e.stopPropagation()}
              className="absolute left-6 bottom-10 w-[280px] z-[180] flex flex-col gap-3 overflow-y-auto pr-2 no-scrollbar scroll-smooth"
              style={{ top: 54 }}
            >
              <h2 className="text-orange-500 font-black text-[11px] uppercase border-b border-orange-500/40 pb-2 mb-3 tracking-[0.2em] drop-shadow-md">
                Terrorists
              </h2>
              {playerArray.filter(p => p.m_team === 2).map(p => (
                <div key={p.m_idx} className="transition-transform duration-300 hover:scale-[1.02]">
                  <PlayerCard playerData={p} isOnRightSide={false} settings={settings} />
                </div>
              ))}
            </div>
          )}
          <div className="absolute right-0 top-0 bottom-0 w-[350px] bg-gradient-to-l from-black/60 to-transparent pointer-events-none z-[170]" />
          {showCTCards && (
            <div
              onMouseDown={e => e.stopPropagation()}
              className="absolute right-6 bottom-10 w-[280px] z-[180] flex flex-col gap-3 overflow-y-auto pl-2 no-scrollbar scroll-smooth"
              style={{ top: 54 }}
            >
              <h2 className="text-blue-500 font-black text-[11px] uppercase border-b border-blue-500/40 pb-2 mb-3 tracking-[0.2em] text-right drop-shadow-md">
                Counter-Terrorists
              </h2>
              {playerArray.filter(p => p.m_team === 3).map(p => (
                <div key={p.m_idx} className="transition-transform duration-300 hover:scale-[1.02]">
                  <PlayerCard playerData={p} isOnRightSide={true} settings={settings} />
                </div>
              ))}
            </div>
          )}
        </>
      )}

      {/* ── MAP IMAGE + ENTITY LAYER ──────────────────────────────────────────
           The wrapper fills the entire container (inset: 0).
           transformOrigin "50% 50%" = container centre.
           Transform order (CSS applies right-to-left):
             1. translate(pan.x, pan.y)  — shift map by pan offset
             2. scale(zoom)              — scale around container centre
           This means: scale first around centre, then shift.
           Player positions use imgStyle.w/h (unscaled) so they stay in sync. */}
      <div
        ref={mapWrapperRef}
        className="absolute pointer-events-none"
        style={{
          position:        "absolute",
          inset:           0,
          transformOrigin: "50% 50%",
          willChange:      "transform",
          // transform is set entirely by rAF loop above — do NOT set it here
        }}
      >
        {/* Letterboxed map image — centred inside the full-container wrapper */}
        <img
          ref={radarImageRef}
          src={radarImage}
          alt="Radar Map"
          draggable={false}
          style={{
            position:   "absolute",
            left:       imgStyle.ml,
            top:        imgStyle.mt,
            width:      imgStyle.w || "100%",
            height:     imgStyle.h || "100%",
            display:    "block",
            userSelect: "none",
            // ── Map image adjustments (Group 1) ──
            opacity: settings?.mapOpacity ?? 1,
            filter: `brightness(${settings?.mapBrightness ?? 1}) contrast(${settings?.mapContrast ?? 1}) blur(${settings?.mapBlur ?? 0}px)`,
          }}
        />

        {/* Players */}
        {playerArray.map(player => (
          <Player
            key={player.m_idx}
            playerData={player}
            mapData={mapData}
            localTeam={localTeam}
            averageLatency={averageLatency}
            settings={settings}
            mapRotation={isLocked ? playerRotation : 0}
            imgStyle={imgStyle}
            isFollowRotate={isLocked && settings?.followYourselfRotation && settings?.followYourself}
            lockedPlayerIdx={lockedPlayerIdx}
          />
        ))}

        {/* Grenades */}
        {grenadeArray.map((grenade, i) => (
          <Grenade
            key={`gren-${i}`}
            grenadeData={grenade}
            mapData={mapData}
            imgSize={imgStyle}
            settings={settings}
          />
        ))}

        {/* ── Deployed smoke cloud overlay (enhanced multi-blob visual) ── */}
        {settings?.showSmoke !== false && grenadeArray
          .filter(g => g.type === "smoke_cloud")
          .map((smoke, i) => (
            <SmokeOverlay
              key={`smoke-ol-${i}`}
              smokeData={smoke}
              mapData={mapData}
              imgSize={imgStyle}
            />
          ))}

        {/* ── Deployed fire overlay (multi-zone fire positions) ── */}
        {settings?.showFire !== false && grenadeArray
          .filter(g => g.type === "inferno")
          .map((inferno, i) => (
            <InfernoOverlay
              key={`fire-ol-${i}`}
              infernoData={inferno}
              mapData={mapData}
              imgSize={imgStyle}
            />
          ))}

        {/* ── Dropped weapons (weapon ESP) ── */}
        {(settings?.showDroppedWeapons !== false) && droppedWeaponArray.filter(weapon => {
          // Filter utilities vs weapons based on settings
          if (UTILITY_IDS.has(weapon.type)) {
            return settings?.showDroppedUtilities !== false;
          }
          // Regular weapons: apply filter mode
          if (settings?.droppedWeaponFilterMode === "custom") {
            const filterList = settings?.droppedWeaponFilterList ?? [];
            return filterList.includes(weapon.type);
          }
          return true; // "all" mode
        }).map((weapon, i) => {
          const rp = getRadarPosition(mapData, { x: weapon.x, y: weapon.y });
          if (!rp) return null;
          return (
            <DroppedWeaponIcon
              key={`dw-${i}`}
              weaponType={weapon.type}
              screenX={(imgStyle.ml ?? 0) + (imgStyle.w || 0) * rp.x}
              screenY={(imgStyle.mt ?? 0) + (imgStyle.h || 0) * rp.y}
              weaponKey={`dw-${weapon.className}-${i}`}
              settings={settings}
            />
          );
        })}

        {/* Bomb — show only when dropped or planted (carried C4 already has badge on player dot) */}
        {bombData && bombData.state !== "carried" && (
          <Bomb
            bombData={bombData}
            mapData={mapData}
            imgSize={imgStyle}
            localTeam={localTeam}
            averageLatency={averageLatency}
            settings={settings}
          />
        )}
      </div>

      {/* ── ZOOM CONTROLS (always on top, never panned) ─────────────────────── */}
      <div
        className="absolute bottom-3 right-3 z-50 flex items-center gap-1"
        onMouseDown={e => e.stopPropagation()}
      >
        {zoom !== 1 && (
          <button
            onClick={() => { 
              // Clear manual zoom so autozoom can resume
              manualZoomRef.current = false;
              if (manualZoomTimeoutRef.current) clearTimeout(manualZoomTimeoutRef.current);
              setZoom(1); 
              setPan({ x: 0, y: 0 }); 
              resetQueues();
            }}
            style={{
              padding: "3px 8px", fontSize: 10, fontWeight: 700,
              background: "rgba(0,0,0,0.65)", color: "#fff",
              border: "1px solid rgba(255,255,255,0.15)", borderRadius: 4,
              cursor: "pointer", fontFamily: "inherit", letterSpacing: "0.06em",
            }}
          >
            {Math.round(zoom * 100)}% ✕
          </button>
        )}
        <button
          onClick={() => {
            manualZoomRef.current = true;
            if (manualZoomTimeoutRef.current) clearTimeout(manualZoomTimeoutRef.current);
            manualZoomTimeoutRef.current = setTimeout(() => {
              manualZoomRef.current = false;
            }, 3000);
            setZoom(p => Math.max(MIN_ZOOM, +(p / ZOOM_FACTOR).toFixed(4)));
          }}
          style={{
            width: 26, height: 26, fontSize: 16, lineHeight: 1,
            background: "rgba(0,0,0,0.65)", color: "#fff",
            border: "1px solid rgba(255,255,255,0.15)", borderRadius: 4,
            cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center",
          }}
        >−</button>
        <button
          onClick={() => {
            manualZoomRef.current = true;
            if (manualZoomTimeoutRef.current) clearTimeout(manualZoomTimeoutRef.current);
            manualZoomTimeoutRef.current = setTimeout(() => {
              manualZoomRef.current = false;
            }, 3000);
            setZoom(p => Math.min(MAX_ZOOM, +(p * ZOOM_FACTOR).toFixed(4)));
          }}
          style={{
            width: 26, height: 26, fontSize: 16, lineHeight: 1,
            background: "rgba(0,0,0,0.65)", color: "#fff",
            border: "1px solid rgba(255,255,255,0.15)", borderRadius: 4,
            cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center",
          }}
        >+</button>
        {/* ── Choose Self + Lock dropdown ─────────────────────────────── */}
        {!isFullscreen && (() => {
          const selfIdx   = settings?.whichPlayerAreYou;
          const selfSteamId = settings?.yourSteamId;
          const isSelfChosen = (selfIdx && selfIdx !== "0") || !!selfSteamId;
          const selfPlayer = isSelfChosen
            ? (selfSteamId
                ? (playerArray.find(p => String(p.m_steam_id) === String(selfSteamId) && !p.m_is_dead)
                   || playerArray.find(p => p.m_idx === Number(selfIdx)))
                : playerArray.find(p => p.m_idx === Number(selfIdx)))
            : null;
          const selfMIdx = selfPlayer?.m_idx ?? null;
          const isSelfAlive  = selfPlayer && !selfPlayer.m_is_dead;
          const isSelfLocked = selfMIdx !== null && lockedPlayerIdx !== null && lockedPlayerIdx === selfMIdx;

          return (
            <div style={{ position: "relative" }}>
              <button
                onClick={() => setShowPovList(prev => !prev)}
                style={{
                  width: 26, height: 26, fontSize: 11, lineHeight: 1,
                  background: isSelfLocked
                    ? "rgba(74,158,255,0.25)"
                    : isSelfChosen ? "rgba(74,158,255,0.12)" : "rgba(0,0,0,0.65)",
                  color: isSelfLocked ? "#7dd3fc" : "#fff",
                  border: `1px solid ${isSelfLocked ? "rgba(74,158,255,0.6)" : isSelfChosen ? "rgba(74,158,255,0.35)" : "rgba(255,255,255,0.15)"}`,
                  borderRadius: 4,
                  cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center",
                  fontWeight: 700,
                  gap: 2,
                }}
                title={isSelfChosen ? `Self: ${selfPlayer?.m_name || selfIdx}${isSelfLocked ? " (locked)" : ""}` : "Select yourself"}
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill={isSelfLocked ? "#7dd3fc" : "currentColor"} stroke="none">
                  <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
                  <circle cx="12" cy="12" r="3" fill={isSelfLocked ? "#7dd3fc" : "rgba(7,20,30,0.8)"}/>
                </svg>
              </button>
              {showPovList && (
                <PovPlayerList
                  playerArray={playerArray}
                  lockedPlayerIdx={lockedPlayerIdx}
                  onSelect={(idx) => {
                    cumulativeRotRef.current = 0;
                    setLockedPlayerIdx(idx);
                    setShowPovList(false);
                    if (!settings?.whichPlayerAreYou || settings?.whichPlayerAreYou === "0") {
                      onUpdateSettings({ whichPlayerAreYou: String(idx) });
                    }
                  }}
                  onUnlock={() => {
                    setLockedPlayerIdx(null);
                    setShowPovList(false);
                    onUpdateSettings({ followYourself: false, followYourselfRotation: false });
                  }}
                  selfSteamId={settings?.whichPlayerAreYou}
                  selfMIdx={selfMIdx}
                  followYourselfRotation={settings?.followYourselfRotation}
                  onToggleRotation={() => onUpdateSettings({ followYourselfRotation: !settings?.followYourselfRotation })}
                  isSelfChosen={isSelfChosen}
                  cumulativeRotRef={cumulativeRotRef}
                  rawTarget={rawTarget}
                  onUpdateSettings={onUpdateSettings}
                  isSelfAlive={isSelfAlive}
                />
              )}
            </div>
          );
        })()}
        {!isFullscreen && (
          <button
            onClick={toggleFullscreen}
            className="px-3 py-1 text-xs bg-black/60 text-white rounded ml-1"
          >
            fullscreen
          </button>
        )}
      </div>

      {showContextMenu && (() => {
        const menuPlayer = playerArray?.find(p => p.m_idx === showContextMenu.playerIdx);
        if (!menuPlayer) return null;
        return (
          <PlayerContextMenu
            player={menuPlayer}
            x={showContextMenu.x}
            y={showContextMenu.y}
            isLocked={lockedPlayerIdx === menuPlayer.m_idx}
            onFollowPlayer={(idx) => { setLockedPlayerIdx(idx); setShowPovList(false); setShowContextMenu(null); }}
            onFollowRotate={onFollowRotate}
            onUnlock={() => {
              setLockedPlayerIdx(null);
              setShowContextMenu(null);
              onUpdateSettings({ followYourself: false, followYourselfRotation: false });
            }}
            onClose={() => setShowContextMenu(null)}
          />
        );
      })()}
    </div>
  );
};

export default Radar;