'use client'

import { useEffect, useMemo, useRef, useState } from 'react'
import MetaSocket from './MetaSocket'
import PositionsSocket from './PositionsSocket'
import TeamSidebar from './TeamSidebar'

/* ───────── UI config ───────── */
const UI = {
  player: {
    minRadiusPx: 4,
    radiusRel: 0.008,
    dirLenRel: 0.70,
    dirMinLenPx: 6,
    lineWidthRel: 0.25,
    stroke: '#ffffff',
    bombStroke: '#ef4444',
    fillCT: '#3b82f6',
    fillT:  '#f59e0b',
    dirColor: 'auto' as 'auto' | string,
  },
  nade: {
    stroke: '#111111',
    smokeFill: 'rgba(160,160,160,0.35)',
    fireFill:  'rgba(255,128,0,0.35)',
    heFill:    'rgba(90,160,90,0.9)',
    flashFill: 'rgba(255,255,255,0.95)',
    decoyFill: 'rgba(140,140,255,0.25)',
    teamStrokeCT: '#3b82f6',
    teamStrokeT:  '#f59e0b',
    minRadiusPx:  6,
  },
  death: {
    stroke: '#9ca3af',
    lineWidthPx: 2,
    sizePx: 10,
  },
  trail: {
    maxPoints: 60,
    fadeMs: 1500,
    stroke: 'rgba(60,60,60,0.7)',
    widthPx: 2,
  }
}

/* ───────── helpers ───────── */
function contrastStroke(hex: string) {
  const h = hex.replace('#','')
  const r = parseInt(h.slice(0,2),16)/255
  const g = parseInt(h.slice(2,4),16)/255
  const b = parseInt(h.slice(4,6),16)/255
  const toL = (c:number) => (c<=0.03928 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4))
  const L = 0.2126*toL(r) + 0.7152*toL(g) + 0.0722*toL(b)
  return L > 0.6 ? '#111111' : '#ffffff'
}

function mapTeam(t: any): 'T' | 'CT' | string {
  if (t === 2 || t === 'T' || t === 't') return 'T'
  if (t === 3 || t === 'CT' || t === 'ct') return 'CT'
  return String(t ?? '')
}

function detectHasBomb(src: any): boolean {
  const flags = [
    'hasBomb','has_bomb','bomb','c4','hasC4','carryingBomb','bombCarrier','isBombCarrier'
  ]
  for (const k of flags) {
    if (typeof src?.[k] === 'boolean') return !!src[k]
    if (typeof src?.[k] === 'string') {
      const s = String(src[k]).toLowerCase()
      if (s === 'true' || s === '1' || s === 'c4' || s.includes('bomb')) return true
    }
  }
  const arrays = [src?.weapons, src?.inventory, src?.items]
  for (const arr of arrays) {
    if (!arr) continue
    if (Array.isArray(arr)) {
      if (arr.some((w:any)=>
        typeof w === 'string'
          ? w.toLowerCase().includes('c4') || w.toLowerCase().includes('bomb')
          : (w?.name||w?.type||w?.weapon||'').toLowerCase().includes('c4') ||
            (w?.name||w?.type||w?.weapon||'').toLowerCase().includes('bomb')
      )) return true
    } else if (typeof arr === 'object') {
      const vals = Object.values(arr)
      if (vals.some((w:any)=>
        typeof w === 'string'
          ? w.toLowerCase().includes('c4') || w.toLowerCase().includes('bomb')
          : (w?.name||w?.type||w?.weapon||'').toLowerCase().includes('c4') ||
            (w?.name||w?.type||w?.weapon||'').toLowerCase().includes('bomb')
      )) return true
    }
  }
  return false
}

function makeWsUrl(host?: string, port?: string, path?: string, scheme?: string) {
  const h = (host ?? '').trim() || '127.0.0.1'
  const p = (port ?? '').trim() || '8081'
  const pa = (path ?? '').trim() || '/telemetry'
  const sch = (scheme ?? '').toLowerCase()
  const pageHttps = typeof window !== 'undefined' && window.location.protocol === 'https:'
  const useWss = sch === 'wss' || (sch !== 'ws' && (p === '443' || pageHttps))
  const proto = useWss ? 'wss' : 'ws'
  const portPart = (p === '80' || p === '443') ? '' : `:${p}`
  return `${proto}://${h}${portPart}${pa}`
}

const metaUrl = makeWsUrl(
  process.env.NEXT_PUBLIC_CS2_META_WS_HOST,
  process.env.NEXT_PUBLIC_CS2_META_WS_PORT,
  process.env.NEXT_PUBLIC_CS2_META_WS_PATH,
  process.env.NEXT_PUBLIC_CS2_META_WS_SCHEME
)

const posUrl = makeWsUrl(
  process.env.NEXT_PUBLIC_CS2_POS_WS_HOST,
  process.env.NEXT_PUBLIC_CS2_POS_WS_PORT,
  process.env.NEXT_PUBLIC_CS2_POS_WS_PATH,
  process.env.NEXT_PUBLIC_CS2_POS_WS_SCHEME
)

const RAD2DEG = 180 / Math.PI
const normalizeDeg = (d: number) => (d % 360 + 360) % 360
const parseVec3String = (str?: string) => {
  if (!str || typeof str !== 'string') return { x: 0, y: 0, z: 0 }
  const [x, y, z] = str.split(',').map(s => Number(s.trim()))
  return { x: Number.isFinite(x) ? x : 0, y: Number.isFinite(y) ? y : 0, z: Number.isFinite(z) ? z : 0 }
}
const asNum = (n: any, def=0) => { const v = Number(n); return Number.isFinite(v) ? v : def }

/* ───────── types ───────── */
type PlayerState = {
  id: string
  name?: string | null
  team?: 'T' | 'CT' | string
  x: number
  y: number
  z: number
  yaw?: number | null
  alive?: boolean
  hasBomb?: boolean
  hp?: number | null
  armor?: number | null
  helmet?: boolean | null
  defuse?: boolean | null
}

type Grenade = {
  id: string
  kind: 'smoke' | 'molotov' | 'he' | 'flash' | 'decoy' | 'unknown'
  x: number
  y: number
  z: number
  radius?: number | null
  expiresAt?: number | null
  team?: 'T' | 'CT' | string | null
}

type DeathMarker = { id: string; x: number; y: number; t: number }
type Trail = { id: string; kind: Grenade['kind']; pts: {x:number,y:number}[]; lastSeen: number }
type Overview = { posX: number; posY: number; scale: number; rotate?: number }
type Mapper = (xw: number, yw: number) => { x: number; y: number }
type WsStatus = 'idle'|'connecting'|'open'|'closed'|'error'

/* ───────── Komponente ───────── */
export default function LiveRadar() {
  // WS-Status
  const [metaWsStatus, setMetaWsStatus] = useState<WsStatus>('idle')
  const [posWsStatus, setPosWsStatus]   = useState<WsStatus>('idle')

  // Map
  const [activeMapKey, setActiveMapKey] = useState<string | null>(null)

  // Spieler
  const playersRef = useRef<Map<string, PlayerState>>(new Map())
  const [players, setPlayers] = useState<PlayerState[]>([])

  // Grenaden + Trails
  const grenadesRef = useRef<Map<string, Grenade>>(new Map())
  const [grenades, setGrenades] = useState<Grenade[]>([])
  const trailsRef = useRef<Map<string, Trail>>(new Map())
  const [trails, setTrails] = useState<Trail[]>([])

  // Death-Marker
  const deathMarkersRef = useRef<DeathMarker[]>([])
  const [deathMarkers, setDeathMarkers] = useState<DeathMarker[]>([])

  // Flush
  const flushTimer = useRef<number | null>(null)
  const scheduleFlush = () => {
    if (flushTimer.current != null) return
    flushTimer.current = window.setTimeout(() => {
      flushTimer.current = null
      setPlayers(Array.from(playersRef.current.values()))
      setGrenades(Array.from(grenadesRef.current.values()))
      setTrails(Array.from(trailsRef.current.values()))
      setDeathMarkers([...deathMarkersRef.current])
    }, 66)
  }
  useEffect(() => {
    return () => {
      if (flushTimer.current != null) {
        window.clearTimeout(flushTimer.current)
        flushTimer.current = null
      }
    }
  }, [])

  // Runden-/Map-Reset
  const clearRoundArtifacts = () => {
    deathMarkersRef.current = []
    trailsRef.current.clear()
    grenadesRef.current.clear()
    scheduleFlush()
  }
  useEffect(() => {
    if (activeMapKey) clearRoundArtifacts()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeMapKey])

  /* ───────── Meta-Callbacks ───────── */
  const handleMetaMap = (key: string) => setActiveMapKey(key.toLowerCase())

  const handleMetaPlayersSnapshot = (list: Array<{ steamId: string|number; name?: string; team?: any }>) => {
    for (const p of list) {
      const id = String(p.steamId ?? '')
      if (!id) continue
      const old = playersRef.current.get(id)
      playersRef.current.set(id, {
        id,
        name: p.name ?? old?.name ?? null,
        team: mapTeam(p.team ?? old?.team),
        x: old?.x ?? 0, y: old?.y ?? 0, z: old?.z ?? 0,
        yaw: old?.yaw ?? null,
        alive: old?.alive,
        hasBomb: old?.hasBomb ?? false,
      })
    }
    scheduleFlush()
  }

  const handleMetaPlayerJoin = (p: any) => {
    const id = String(p?.steamId ?? p?.id ?? p?.name ?? '')
    if (!id) return
    const old = playersRef.current.get(id)
    playersRef.current.set(id, {
      id,
      name: p?.name ?? old?.name ?? null,
      team: mapTeam(p?.team ?? old?.team),
      x: old?.x ?? 0, y: old?.y ?? 0, z: old?.z ?? 0,
      yaw: old?.yaw ?? null,
      alive: true,
      hasBomb: old?.hasBomb ?? false,
    })
    scheduleFlush()
  }

  const handleMetaPlayerLeave = (steamId: string | number) => {
    const id = String(steamId)
    const old = playersRef.current.get(id)
    if (old) {
      playersRef.current.set(id, { ...old, alive: false })
      scheduleFlush()
    }
  }

  /* ───────── Positions-Callbacks ───────── */
  const addDeathMarker = (x:number, y:number, idHint?: string) => {
    deathMarkersRef.current.push({ id: idHint ?? `d#${Date.now()}`, x, y, t: Date.now() })
  }

  const upsertPlayer = (e: any) => {
    const id = String(e.steamId ?? e.steam_id ?? e.userId ?? e.playerId ?? e.id ?? e.name ?? '')
    if (!id) return
    const pos = e.pos ?? e.position ?? e.location ?? e.coordinates
    const x = Number(e.x ?? pos?.x ?? (Array.isArray(pos) ? pos?.[0] : undefined))
    const y = Number(e.y ?? pos?.y ?? (Array.isArray(pos) ? pos?.[1] : undefined))
    const z = Number(e.z ?? pos?.z ?? (Array.isArray(pos) ? pos?.[2] : 0))
    if (!Number.isFinite(x) || !Number.isFinite(y)) return

    const yaw = Number(
      e.yaw ??
      e.viewAngle?.yaw ??
      e.view?.yaw ??
      e.aim?.yaw ??
      e.ang?.y ??
      e.angles?.y ??
      e.rotation?.yaw
    )

    const old = playersRef.current.get(id)
    const nextAlive = (e.alive !== undefined) ? !!e.alive : old?.alive
    const hp = Number(
      e.hp ?? e.health ?? e.state?.health
    )
    const armor = Number(
      e.armor ?? e.state?.armor
    )
    const helmet = Boolean(
      e.helmet ?? e.hasHelmet ?? e.state?.helmet
    )
    const defuse = Boolean(
      e.defuse ?? e.hasDefuse ?? e.hasDefuser ?? e.state?.defusekit
    )
    const hasBomb = detectHasBomb(e) || old?.hasBomb

    if (old?.alive !== false && nextAlive === false) addDeathMarker(x, y, id)

    playersRef.current.set(id, {
      id,
      name: e.name ?? old?.name ?? null,
      team: mapTeam(e.team ?? old?.team),
      x, y, z,
      yaw: Number.isFinite(yaw) ? yaw : old?.yaw ?? null,
      alive: nextAlive,
      hasBomb: !!hasBomb,
      hp: Number.isFinite(hp) ? hp : old?.hp ?? null,
      armor: Number.isFinite(armor) ? armor : old?.armor ?? null,
      helmet: helmet ?? old?.helmet ?? null,
      defuse: defuse ?? old?.defuse ?? null,
    })
  }

  const handlePlayersAll = (msg: any) => {
    const ap = msg?.allplayers
    if (!ap || typeof ap !== 'object') return

    let total = 0, aliveCount = 0
    for (const key of Object.keys(ap)) {
      const p = ap[key]
      const pos = parseVec3String(p.position)
      const fwd = parseVec3String(p.forward)
      const yaw = normalizeDeg(Math.atan2(fwd.y, fwd.x) * RAD2DEG)
      const id = String(key)
      const old = playersRef.current.get(id)
      const isAlive = p.state?.health > 0 || p.state?.health == null
      const hp = Number(p.state?.health)
      const armor = Number(p.state?.armor)
      const helmet = !!p.state?.helmet
      const defuse = !!p.state?.defusekit
      const hasBomb = detectHasBomb(p) || old?.hasBomb

      if ((old?.alive ?? true) && !isAlive) addDeathMarker(pos.x, pos.y, id)

      playersRef.current.set(id, {
        id,
        name: p.name ?? old?.name ?? null,
        team: mapTeam(p.team ?? old?.team),
        x: pos.x, y: pos.y, z: pos.z,
        yaw,
        alive: isAlive,
        hasBomb: !!hasBomb,
        hp: Number.isFinite(hp) ? hp : old?.hp ?? null,
        armor: Number.isFinite(armor) ? armor : old?.armor ?? null,
        helmet: helmet ?? old?.helmet ?? null,
        defuse: defuse ?? old?.defuse ?? null,
      })

      total++
      if (isAlive) aliveCount++
    }

    if (total > 0 && aliveCount === total && (deathMarkersRef.current.length > 0 || trailsRef.current.size > 0 || grenadesRef.current.size > 0)) {
      clearRoundArtifacts()
    }

    scheduleFlush()
  }

  const normalizeGrenades = (raw: any): Grenade[] => {
    if (!raw) return []
    const pickTeam = (t: any): 'T' | 'CT' | string | null => {
      const s = mapTeam(t)
      return s === 'T' || s === 'CT' ? s : (typeof t === 'string' ? t : null)
    }
    if (Array.isArray(raw)) {
      return raw.map((g: any, i: number) => {
        const pos = g.pos ?? g.position ?? g.location ?? {}
        const xyz = Array.isArray(pos) ? { x: pos[0], y: pos[1], z: pos[2] } :
                  typeof pos === 'string' ? parseVec3String(pos) : pos
        return {
          id: String(g.id ?? `${g.type ?? 'nade'}#${i}`),
          kind: (String(g.type ?? g.kind ?? 'unknown').toLowerCase() as Grenade['kind']),
          x: asNum(g.x ?? xyz?.x), y: asNum(g.y ?? xyz?.y), z: asNum(g.z ?? xyz?.z),
          radius: Number.isFinite(Number(g.radius)) ? Number(g.radius) : null,
          expiresAt: Number.isFinite(Number(g.expiresAt)) ? Number(g.expiresAt) : null,
          team: pickTeam(g.team ?? g.owner_team ?? g.side ?? null),
        }
      })
    }
    const buckets: Record<string, string[]> = {
      smoke:   ['smokes', 'smoke', 'smokegrenade', 'smokegrenades'],
      molotov: ['molotov', 'molotovs', 'inferno', 'fire', 'incendiary'],
      he:      ['he', 'hegrenade', 'hegrenades', 'explosive'],
      flash:   ['flash', 'flashbang', 'flashbangs'],
      decoy:   ['decoy', 'decoys'],
    }
    const out: Grenade[] = []
    const push = (kind: Grenade['kind'], list: any) => {
      if (!list) return
      const arr = Array.isArray(list) ? list : Object.values(list)
      let i = 0
      for (const g of arr) {
        const pos = g?.pos ?? g?.position ?? g?.location
        const xyz = Array.isArray(pos) ? { x: pos[0], y: pos[1], z: pos[2] } :
                  typeof pos === 'string' ? parseVec3String(pos) :
                  (pos || { x: g?.x, y: g?.y, z: g?.z })
        const id = String(
          g?.id ?? g?.entityid ?? g?.entindex ??
          `${kind}#${asNum(xyz?.x)}:${asNum(xyz?.y)}:${asNum(xyz?.z)}:${i++}`
        )
        out.push({
          id, kind,
          x: asNum(xyz?.x), y: asNum(xyz?.y), z: asNum(xyz?.z),
          radius: Number.isFinite(Number(g?.radius)) ? Number(g?.radius) : null,
          expiresAt: Number.isFinite(Number(g?.expiresAt)) ? Number(g?.expiresAt) : null,
          team: pickTeam(g?.team ?? g?.owner_team ?? g?.side ?? null),
        })
      }
    }
    for (const [kind, keys] of Object.entries(buckets)) {
      for (const k of keys) if ((raw as any)[k]) push(kind as Grenade['kind'], (raw as any)[k])
    }
    if (out.length === 0 && typeof raw === 'object') {
      for (const [k, v] of Object.entries(raw)) {
        const kk = k.toLowerCase()
        const kind =
          kk.includes('smoke') ? 'smoke' :
          kk.includes('flash') ? 'flash' :
          kk.includes('molotov') || kk.includes('inferno') || kk.includes('fire') ? 'molotov' :
          kk.includes('decoy') ? 'decoy' :
          kk.includes('he') ? 'he' : 'unknown'
        push(kind as Grenade['kind'], v)
      }
    }
    return out
  }

  const handleGrenades = (g: any) => {
    const list = normalizeGrenades(g)

    // Trails updaten
    const seen = new Set<string>()
    const now = Date.now()
    for (const it of list) {
      seen.add(it.id)
      const prev = trailsRef.current.get(it.id) ?? { id: it.id, kind: it.kind, pts: [], lastSeen: 0 }
      const last = prev.pts[prev.pts.length - 1]
      if (!last || Math.hypot(it.x - last.x, it.y - last.y) > 1) {
        prev.pts.push({ x: it.x, y: it.y })
        if (prev.pts.length > UI.trail.maxPoints) prev.pts = prev.pts.slice(-UI.trail.maxPoints)
      }
      prev.kind = it.kind
      prev.lastSeen = now
      trailsRef.current.set(it.id, prev)
    }
    // Trails ausdünnen
    for (const [id, tr] of trailsRef.current) {
      if (!seen.has(id) && now - tr.lastSeen > UI.trail.fadeMs) {
        trailsRef.current.delete(id)
      }
    }

    // aktuelle Nades übernehmen
    const next = new Map<string, Grenade>()
    for (const it of list) next.set(it.id, it)
    grenadesRef.current = next

    scheduleFlush()
  }

  // erster Flush
  useEffect(() => {
    if (!playersRef.current && !grenadesRef.current) return
    scheduleFlush()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  /* ───────── Overview + Radarbild ───────── */
  const [overview, setOverview] = useState<Overview | null>(null)
  const overviewCandidates = (mapKey: string) => {
    const base = mapKey
    return [
      `/assets/resource/overviews/${base}.json`,
      `/assets/resource/overviews/${base}_lower.json`,
      `/assets/resource/overviews/${base}_v1.json`,
      `/assets/resource/overviews/${base}_v2.json`,
      `/assets/resource/overviews/${base}_s2.json`,
    ]
  }

  const parseOverviewJson = (j: any): Overview | null => {
    const posX = Number(j?.posX ?? j?.pos_x)
    const posY = Number(j?.posY ?? j?.pos_y)
    const scale = Number(j?.scale)
    const rotate = Number(j?.rotate ?? 0)
    if (![posX, posY, scale].every(Number.isFinite)) return null
    return { posX, posY, scale, rotate }
  }

  const parseValveKvOverview = (txt: string): Overview | null => {
    const clean = txt.replace(/\/\/.*$/gm, '')
    const pick = (k: string) => { const m = clean.match(new RegExp(`"\\s*${k}\\s*"\\s*"([^"]+)"`)); return m ? Number(m[1]) : NaN }
    const posX = pick('pos_x'), posY = pick('pos_y'), scale = pick('scale')
    const r = pick('rotate'); const rotate = Number.isFinite(r) ? r : 0
    if (![posX, posY, scale].every(Number.isFinite)) return null
    return { posX, posY, scale, rotate }
  }

  useEffect(() => {
    let cancel = false
    ;(async () => {
      if (!activeMapKey) { setOverview(null); return }
      for (const path of overviewCandidates(activeMapKey)) {
        try {
          const res = await fetch(path, { cache: 'no-store' })
          if (!res.ok) continue
          const txt = await res.text()
          let ov: Overview | null = null
          try { ov = parseOverviewJson(JSON.parse(txt)) } catch { ov = parseValveKvOverview(txt) }
          if (ov && !cancel) { setOverview(ov); return }
        } catch {}
      }
      if (!cancel) setOverview(null)
    })()
    return () => { cancel = true }
  }, [activeMapKey])

  const { folderKey, imageCandidates } = useMemo(() => {
    if (!activeMapKey) return { folderKey: null as string | null, imageCandidates: [] as string[] }
    const short = activeMapKey.startsWith('de_') ? activeMapKey.slice(3) : activeMapKey
    const base = `/assets/img/radar/${activeMapKey}`
    return {
      folderKey: short,
      imageCandidates: [
        `${base}/de_${short}_radar_psd.png`,
        `${base}/de_${short}_lower_radar_psd.png`,
        `${base}/de_${short}_v1_radar_psd.png`,
        `${base}/de_${short}_radar.png`,
      ],
    }
  }, [activeMapKey])

  const [srcIdx, setSrcIdx] = useState(0)
  useEffect(() => { setSrcIdx(0) }, [folderKey])
  const currentSrc = imageCandidates[srcIdx]

  const [imgSize, setImgSize] = useState<{ w: number; h: number } | null>(null)

  /* ───────── Welt → Pixel ───────── */
  const worldToPx: Mapper = useMemo(() => {
    if (!imgSize || !overview) {
      return (xw, yw) => {
        if (!imgSize) return { x: 0, y: 0 }
        const R = 4096
        const span = Math.min(imgSize.w, imgSize.h)
        const k = span / (2 * R)
        return { x: imgSize.w / 2 + xw * k, y: imgSize.h / 2 - yw * k }
      }
    }
    const { posX, posY, scale, rotate = 0 } = overview
    const w = imgSize.w, h = imgSize.h
    const cx = w / 2, cy = h / 2
    const bases: ((xw: number, yw: number) => { x: number; y: number })[] = [
      (xw, yw) => ({ x: (xw - posX) / scale, y: (posY - yw) / scale }),
      (xw, yw) => ({ x: (posX - xw) / scale, y: (posY - yw) / scale }),
      (xw, yw) => ({ x: (xw - posX) / scale, y: (yw - posY) / scale }),
      (xw, yw) => ({ x: (posX - xw) / scale, y: (yw - posY) / scale }),
    ]
    const rotSigns = [1, -1]
    const candidates: Mapper[] = []
    for (const base of bases) {
      for (const s of rotSigns) {
        const theta = (rotate * s * Math.PI) / 180
        candidates.push((xw, yw) => {
          const p = base(xw, yw)
          if (rotate === 0) return p
          const dx = p.x - cx, dy = p.y - cy
          const xr = dx * Math.cos(theta) - dy * Math.sin(theta)
          const yr = dx * Math.sin(theta) + dy * Math.cos(theta)
          return { x: cx + xr, y: cy + yr }
        })
      }
    }
    if (players.length === 0) return candidates[0]
    const score = (mapFn: Mapper) => {
      let inside = 0
      for (const p of players) {
        const { x, y } = mapFn(p.x, p.y)
        if (Number.isFinite(x) && Number.isFinite(y) && x >= 0 && y >= 0 && x <= w && y <= h) inside++
      }
      return inside
    }
    let best = candidates[0], bestScore = -1
    for (const m of candidates) {
      const s = score(m)
      if (s > bestScore) { bestScore = s; best = m }
    }
    return best
  }, [imgSize, overview, players])

  const unitsToPx = useMemo(() => {
    if (!imgSize) return (u: number) => u
    if (overview) {
      const scale = overview.scale
      return (u: number) => u / scale
    }
    const R = 4096
    const span = Math.min(imgSize.w, imgSize.h)
    const k = span / (2 * R)
    return (u: number) => u * k
  }, [imgSize, overview])

  /* ───────── Status-Badge ───────── */
  const WsDot = ({ status, label }: { status: WsStatus, label: string }) => {
    const color =
      status === 'open' ? 'bg-green-500' :
      status === 'connecting' ? 'bg-amber-500' :
      status === 'error' ? 'bg-red-500' :
      'bg-neutral-400'
    const txt =
      status === 'open' ? 'verbunden' :
      status === 'connecting' ? 'verbinde…' :
      status === 'error' ? 'Fehler' :
      status === 'closed' ? 'getrennt' : '—'
    return (
      <span className="inline-flex items-center gap-1 text-xs opacity-80">
        <span className="font-medium">{label}</span>
        <span className={`inline-block w-2.5 h-2.5 rounded-full ${color}`} />
        {txt}
      </span>
    )
  }

  /* ───────── Render (Fix A: reines Flex-Layout) ───────── */
  return (
    <div className="h-full min-h-0 w-full flex flex-col overflow-hidden">
      {/* Header */}
      <div className="mb-4 shrink-0 flex items-center justify-between">
        <h2 className="text-xl font-semibold">Live Radar</h2>
        <div className="flex items-center gap-4">
          <div className="text-sm opacity-80">
            {activeMapKey ? activeMapKey.replace(/^de_/,'').replace(/_/g,' ').toUpperCase() : '—'}
          </div>
          <WsDot status={metaWsStatus} label="Meta" />
          <WsDot status={posWsStatus}  label="Pos" />
        </div>
      </div>

      {/* Unsichtbare WS-Clients */}
      <MetaSocket
        url={metaUrl}
        onStatus={setMetaWsStatus}
        onMap={(k)=> setActiveMapKey(k.toLowerCase())}
        onPlayersSnapshot={handleMetaPlayersSnapshot}
        onPlayerJoin={handleMetaPlayerJoin}
        onPlayerLeave={handleMetaPlayerLeave}
      />
      <PositionsSocket
        url={posUrl}
        onStatus={setPosWsStatus}
        onMap={(k)=> setActiveMapKey(String(k).toLowerCase())}
        onPlayerUpdate={(p)=> { upsertPlayer(p); scheduleFlush() }}
        onPlayersAll={(m)=> { handlePlayersAll(m); scheduleFlush() }}
        onGrenades={(g)=> { handleGrenades(g); scheduleFlush() }}
      />

      {/* Inhalt: 3-Spalten-Layout (T | Radar | CT) */}
      <div className="flex-1 min-h-0">
        {!activeMapKey ? (
          <div className="h-full grid place-items-center">
            <div className="px-4 py-3 rounded bg-amber-100 text-amber-900 dark:bg-amber-900/30 dark:text-amber-100">
              Keine Map erkannt.
            </div>
          </div>
        ) : (
          <div className="h-full min-h-0 grid grid-cols-[minmax(180px,240px)_1fr_minmax(180px,240px)] gap-4">
            {/* Left: T */}
            <TeamSidebar
              team="T"
              players={players
                .filter(p => p.team === 'T')
                .map(p => ({
                  id: p.id, name: p.name, hp: p.hp, armor: p.armor, helmet: p.helmet,
                  defuse: p.defuse, hasBomb: p.hasBomb, alive: p.alive
                }))
              }
            />

            {/* Center: Radar */}
            <div className="relative min-h-0 rounded-lg overflow-hidden border border-neutral-700 bg-neutral-800">
              {currentSrc ? (
                <div className="absolute inset-0">
                  {/* Bild füllt Container (Letterboxing via object-contain) */}
                  <img
                    key={currentSrc}
                    src={currentSrc}
                    alt={activeMapKey ?? 'map'}
                    className="absolute inset-0 h-full w-full object-contain object-center"
                    onLoad={(e) => {
                      const img = e.currentTarget
                      setImgSize({ w: img.naturalWidth, h: img.naturalHeight })
                    }}
                    onError={() => {
                      if (srcIdx < imageCandidates.length - 1) setSrcIdx(i => i + 1)
                    }}
                  />

                  {/* Overlay skaliert deckungsgleich zum Bild */}
                  {imgSize && (
                    <svg
                      className="absolute inset-0 h-full w-full object-contain pointer-events-none"
                      viewBox={`0 0 ${imgSize.w} ${imgSize.h}`}
                      preserveAspectRatio="xMidYMid meet"
                    >
                      {/* Trails */}
                      {trails.map(tr => {
                        const pts = tr.pts.map(p => {
                          const q = worldToPx(p.x, p.y)
                          return `${q.x},${q.y}`
                        }).join(' ')
                        if (!pts) return null
                        return (
                          <polyline
                            key={`trail-${tr.id}`}
                            points={pts}
                            fill="none"
                            stroke={UI.trail.stroke}
                            strokeWidth={UI.trail.widthPx}
                            strokeLinecap="round"
                            strokeLinejoin="round"
                          />
                        )
                      })}

                      {/* Grenades */}
                      {grenades.map((g) => {
                        const P = worldToPx(g.x, g.y)
                        const defR =
                          g.kind === 'smoke'  ? 150 :
                          g.kind === 'molotov'? 120 :
                          g.kind === 'he'     ? 40  :
                          g.kind === 'flash'  ? 36  :
                          g.kind === 'decoy'  ? 80  : 60
                        const rPx = Math.max(UI.nade.minRadiusPx, unitsToPx(g.radius ?? defR))
                        const stroke = g.team === 'CT' ? UI.nade.teamStrokeCT
                                    : g.team === 'T'  ? UI.nade.teamStrokeT
                                    : UI.nade.stroke
                        const sw = Math.max(1, Math.sqrt(rPx) * 0.6)
                        if (!Number.isFinite(P.x) || !Number.isFinite(P.y)) return null

                        if (g.kind === 'smoke') {
                          return <circle key={g.id} cx={P.x} cy={P.y} r={rPx} fill={UI.nade.smokeFill} stroke={stroke} strokeWidth={sw} />
                        }
                        if (g.kind === 'molotov') {
                          return <circle key={g.id} cx={P.x} cy={P.y} r={rPx} fill={UI.nade.fireFill} stroke={stroke} strokeWidth={sw} />
                        }
                        if (g.kind === 'decoy') {
                          return <circle key={g.id} cx={P.x} cy={P.y} r={rPx} fill={UI.nade.decoyFill} stroke={stroke} strokeWidth={sw} strokeDasharray="6,4" />
                        }
                        if (g.kind === 'flash') {
                          return (
                            <g key={g.id}>
                              <circle cx={P.x} cy={P.y} r={rPx*0.6} fill="none" stroke={stroke} strokeWidth={sw} />
                              <circle cx={P.x} cy={P.y} r={Math.max(2, rPx*0.25)} fill={UI.nade.flashFill} stroke={stroke} strokeWidth={Math.max(1, sw*0.8)} />
                              <line x1={P.x-rPx*0.9} y1={P.y} x2={P.x+rPx*0.9} y2={P.y} stroke={stroke} strokeWidth={Math.max(1, sw*0.6)} strokeLinecap="round"/>
                              <line x1={P.x} y1={P.y-rPx*0.9} x2={P.x} y2={P.y+rPx*0.9} stroke={stroke} strokeWidth={Math.max(1, sw*0.6)} strokeLinecap="round"/>
                            </g>
                          )
                        }
                        return <circle key={g.id} cx={P.x} cy={P.y} r={Math.max(4, rPx*0.4)} fill={g.kind === 'he' ? UI.nade.heFill : '#999'} stroke={stroke} strokeWidth={Math.max(1, sw*0.8)} />
                      })}

                      {/* Spieler */}
                      {players
                        .filter(p => (p.team === 'CT' || p.team === 'T') && p.alive !== false)
                        .map((p) => {
                          const A = worldToPx(p.x, p.y)
                          const base = Math.min(imgSize.w, imgSize.h)
                          const r = Math.max(UI.player.minRadiusPx, base * UI.player.radiusRel)
                          const dirLenPx = Math.max(UI.player.dirMinLenPx, r * UI.player.dirLenRel)
                          const stroke = p.hasBomb ? UI.player.bombStroke : UI.player.stroke
                          const strokeW = Math.max(1, r * UI.player.lineWidthRel)
                          const fillColor = p.team === 'CT' ? UI.player.fillCT : UI.player.fillT
                          const dirColor  = UI.player.dirColor === 'auto' ? contrastStroke(fillColor) : UI.player.dirColor

                          let dxp = 0, dyp = 0
                          if (Number.isFinite(p.yaw as number)) {
                            const yawRad = (Number(p.yaw) * Math.PI) / 180
                            const STEP_WORLD = 200
                            const B = worldToPx(
                              p.x + Math.cos(yawRad) * STEP_WORLD,
                              p.y + Math.sin(yawRad) * STEP_WORLD
                            )
                            dxp = B.x - A.x
                            dyp = B.y - A.y
                            const cur = Math.hypot(dxp, dyp) || 1
                            dxp *= dirLenPx / cur
                            dyp *= dirLenPx / cur
                          }

                          return (
                            <g key={p.id}>
                              <circle
                                cx={A.x} cy={A.y} r={r}
                                fill={fillColor} stroke={stroke}
                                strokeWidth={Math.max(1, r*0.3)}
                              />
                              {Number.isFinite(p.yaw as number) && (
                                <line
                                  x1={A.x} y1={A.y} x2={A.x + dxp} y2={A.y + dyp}
                                  stroke={dirColor} strokeWidth={strokeW} strokeLinecap="round"
                                />
                              )}
                            </g>
                          )
                        })}

                      {/* Death-Marker */}
                      {deathMarkers.map(dm => {
                        const P = worldToPx(dm.x, dm.y)
                        const s = UI.death.sizePx
                        return (
                          <g key={`death-${dm.t}-${dm.x}-${dm.y}`}>
                            <line x1={P.x - s} y1={P.y - s} x2={P.x + s} y2={P.y + s}
                                  stroke={UI.death.stroke} strokeWidth={UI.death.lineWidthPx} strokeLinecap="round" />
                            <line x1={P.x - s} y1={P.y + s} x2={P.x + s} y2={P.y - s}
                                  stroke={UI.death.stroke} strokeWidth={UI.death.lineWidthPx} strokeLinecap="round" />
                          </g>
                        )
                      })}
                    </svg>
                  )}
                </div>
              ) : (
                <div className="absolute inset-0 grid place-items-center p-6 text-center">
                  Keine Radar-Grafik gefunden.
                </div>
              )}
            </div>

            {/* Right: CT */}
            <TeamSidebar
              team="CT"
              align="right"
              players={players
                .filter(p => p.team === 'CT')
                .map(p => ({
                  id: p.id, name: p.name, hp: p.hp, armor: p.armor, helmet: p.helmet,
                  defuse: p.defuse, hasBomb: p.hasBomb, alive: p.alive
                }))
              }
            />
          </div>
        )}
      </div>
    </div>
  )
}
