// frontend\src\components\ui\PerformanceMonitor.tsx

import React from 'react'
import { subscribeSSE } from '../../lib/sseSingleton'

type Props = {
  mode?: 'inline' | 'floating'
  className?: string
  /** Server perf poll Intervall */
  pollMs?: number
  /** kompakter, header-freundlicher Modus */
  compact?: boolean
}

const clamp01 = (v: number) => Math.max(0, Math.min(1, v))

function barTone(t: 'good' | 'warn' | 'bad') {
  if (t === 'good') return 'bg-emerald-500'
  if (t === 'warn') return 'bg-amber-500'
  return 'bg-red-500'
}

function formatMs(v: number | null) {
  if (v == null) return '–'
  return `${Math.round(v)}ms`
}

function formatPct(v: number | null) {
  if (v == null) return '–'
  return `${Math.round(v)}%`
}

function formatBytes(bytes: number | null) {
  if (bytes == null || !Number.isFinite(bytes) || bytes <= 0) return '–'
  const units = ['B', 'KB', 'MB', 'GB', 'TB']
  let v = bytes
  let i = 0
  while (v >= 1024 && i < units.length - 1) {
    v /= 1024
    i++
  }
  const digits = i === 0 ? 0 : v >= 10 ? 1 : 2
  return `${v.toFixed(digits)} ${units[i]}`
}

function formatBytesCompact(bytes: number | null) {
  if (bytes == null || !Number.isFinite(bytes) || bytes <= 0) return '–'
  const units = ['B', 'KB', 'MB', 'GB', 'TB']
  let v = bytes
  let i = 0

  while (v >= 1024 && i < units.length - 1) {
    v /= 1024
    i++
  }

  const rounded =
    i === 0 ? Math.round(v) :
    v >= 100 ? Math.round(v) :
    v >= 10 ? Number(v.toFixed(0)) :
    Number(v.toFixed(1))

  return `${rounded} ${units[i]}`
}

function useFps(sampleMs = 1000) {
  const [fps, setFps] = React.useState<number | null>(null)

  React.useEffect(() => {
    let raf = 0
    let last = performance.now()
    let frames = 0
    let alive = true
    let running = false

    const loop = (t: number) => {
      if (!alive || !running) return
      frames += 1

      const dt = t - last
      if (dt >= sampleMs) {
        const next = Math.round((frames * 1000) / dt)
        setFps(next)
        frames = 0
        last = t
      }

      raf = requestAnimationFrame(loop)
    }

    const start = () => {
      if (!alive) return
      if (document.hidden) return
      if (running) return
      running = true
      last = performance.now()
      frames = 0
      raf = requestAnimationFrame(loop)
    }

    const stop = () => {
      running = false
      cancelAnimationFrame(raf)
    }

    const onVis = () => {
      if (document.hidden) stop()
      else start()
    }

    start()
    document.addEventListener('visibilitychange', onVis)

    return () => {
      alive = false
      document.removeEventListener('visibilitychange', onVis)
      stop()
    }
  }, [sampleMs])

  return fps
}

export default function PerformanceMonitor({
  mode = 'inline',
  className,
  pollMs = 3000,
  compact = false,
}: Props) {
  const fps = useFps(1000)

  const [ping, setPing] = React.useState<number | null>(null)
  const [cpu, setCpu] = React.useState<number | null>(null)
  const [diskFreeBytes, setDiskFreeBytes] = React.useState<number | null>(null)
  const [diskTotalBytes, setDiskTotalBytes] = React.useState<number | null>(null)
  const [diskUsedPercent, setDiskUsedPercent] = React.useState<number | null>(null)

  const LOW_FREE_BYTES = 5 * 1024 * 1024 * 1024 // 5 GB
  const RESET_BYTES = 8 * 1024 * 1024 * 1024    // Hysterese (8 GB)
  const emergencyRef = React.useRef(false)


  React.useEffect(() => {
    const url = `/api/perf/stream?ms=${encodeURIComponent(String(pollMs))}`

    const unsub = subscribeSSE<any>(url, 'perf', (data) => {
      const v = typeof data?.cpuPercent === 'number' ? data.cpuPercent : null
      const free = typeof data?.diskFreeBytes === 'number' ? data.diskFreeBytes : null
      const total = typeof data?.diskTotalBytes === 'number' ? data.diskTotalBytes : null
      const usedPct = typeof data?.diskUsedPercent === 'number' ? data.diskUsedPercent : null

      setCpu(v)
      setDiskFreeBytes(free)
      setDiskTotalBytes(total)
      setDiskUsedPercent(usedPct)

      const serverMs = typeof data?.serverMs === 'number' ? data.serverMs : null
      setPing(serverMs != null ? Math.max(0, Date.now() - serverMs) : null)
    })

    return () => unsub()
  }, [pollMs])


  // -------------------------
  // Meter config
  // -------------------------
  const pingTone: 'good' | 'warn' | 'bad' =
    ping == null ? 'bad' : ping <= 120 ? 'good' : ping <= 300 ? 'warn' : 'bad'
  const fpsTone: 'good' | 'warn' | 'bad' =
    fps == null ? 'bad' : fps >= 55 ? 'good' : fps >= 30 ? 'warn' : 'bad'
  const cpuTone: 'good' | 'warn' | 'bad' =
    cpu == null ? 'bad' : cpu <= 60 ? 'good' : cpu <= 85 ? 'warn' : 'bad'

  // Balken-Füllstände
  const pingFill = clamp01(((ping ?? 999) as number) / 500) // 0..500ms
  const fpsFill = clamp01(((fps ?? 0) as number) / 60)      // 0..60fps
  const cpuFill = clamp01(((cpu ?? 0) as number) / 100)     // 0..100%

  const freePct =
    diskFreeBytes != null && diskTotalBytes != null && diskTotalBytes > 0
      ? diskFreeBytes / diskTotalBytes
      : null

  // "Used %" Anzeige: vom Server, sonst selbst aus freePct berechnen
  const usedPctShown =
    diskUsedPercent != null
      ? diskUsedPercent
      : freePct != null
        ? (1 - freePct) * 100
        : null

  const usedFill = clamp01(((usedPctShown ?? 0) as number) / 100) // 0..1

  // Disk "emergency" Hysterese (absolute Bytes)
  // - wenn free <= LOW_FREE_BYTES => emergency an
  // - bleibt an bis free >= RESET_BYTES
  if (diskFreeBytes == null) {
    emergencyRef.current = false
  } else if (emergencyRef.current) {
    if (diskFreeBytes >= RESET_BYTES) emergencyRef.current = false
  } else {
    if (diskFreeBytes <= LOW_FREE_BYTES) emergencyRef.current = true
  }

  const diskTone: 'good' | 'warn' | 'bad' =
    diskFreeBytes == null
      ? 'bad'
      : emergencyRef.current
        ? 'bad'
        : freePct == null
          ? 'bad'
          : freePct >= 0.15
            ? 'good'
            : freePct >= 0.07
              ? 'warn'
              : 'bad'

  const diskTitle =
    diskFreeBytes == null
      ? 'Disk: –'
      : `Free: ${formatBytes(diskFreeBytes)} / Total: ${formatBytes(diskTotalBytes)} · Used: ${formatPct(usedPctShown)}`

  const wrapperClass =
    mode === 'floating'
      ? 'fixed bottom-4 right-4 z-[80]'
      : compact
        ? 'min-w-0'
        : 'w-full min-w-0'

  const panelClass = compact
    ? `
        w-full min-w-0 rounded-lg border border-gray-200/70 bg-white/70 px-1.5 py-1 shadow-sm backdrop-blur
        dark:border-white/10 dark:bg-white/5
        sm:flex sm:h-[47px] sm:items-center sm:rounded-xl sm:px-2 sm:py-0
      `
    : `
        rounded-lg border border-gray-200/70 bg-white/70 px-2.5 py-1.5 shadow-sm backdrop-blur
        dark:border-white/10 dark:bg-white/5
        grid grid-cols-2 gap-x-3 gap-y-2
        sm:flex sm:items-center sm:gap-3
      `

  if (compact) {
    const metrics = [
      {
        key: 'disk',
        label: 'Disk',
        value: formatBytesCompact(diskFreeBytes),
        fill: usedFill,
        tone: diskTone,
        title: diskTitle,
      },
      {
        key: 'ping',
        label: 'Ping',
        value: formatMs(ping),
        fill: pingFill,
        tone: pingTone,
        title: `Ping: ${formatMs(ping)}`,
      },
      {
        key: 'fps',
        label: 'FPS',
        value: fps != null ? String(fps) : '–',
        fill: fpsFill,
        tone: fpsTone,
        title: `FPS: ${fps != null ? fps : '–'}`,
      },
      {
        key: 'cpu',
        label: 'CPU',
        value: formatPct(cpu),
        fill: cpuFill,
        tone: cpuTone,
        title: `CPU: ${formatPct(cpu)}`,
      },
    ]

    return (
      <div className={`${wrapperClass} ${className ?? ''}`}>
        <div className={panelClass}>
          <div className="grid grid-cols-2 gap-1 md:grid-cols-4 sm:h-full sm:w-full sm:items-center">
            {metrics.map((item) => (
              <div
                key={item.key}
                title={item.title}
                className="min-w-0 rounded-md px-1.5 py-0.5 sm:rounded-lg sm:px-2 sm:py-1"
              >
                <div className="flex items-center justify-between gap-1.5 whitespace-nowrap">
                  <span className="min-w-0 truncate text-[9px] font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400 sm:text-[10px]">
                    {item.label}
                  </span>
                  <span className="shrink-0 whitespace-nowrap text-[10px] font-semibold leading-none tabular-nums text-gray-900 dark:text-gray-100 sm:text-[11px]">
                    {item.value}
                  </span>
                </div>

                <div className="mt-1 h-[3px] overflow-hidden rounded-full bg-gray-200/70 dark:bg-white/10 sm:mt-1.5 sm:h-1">
                  <div
                    className={`h-full rounded-full ${barTone(item.tone)}`}
                    style={{ width: `${Math.round(item.fill * 100)}%` }}
                  />
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    )
  }

  return (
    <div className={`${wrapperClass} ${className ?? ''}`}>
      <div className={panelClass}>
        {/* DISK */}
        <div className="flex items-center gap-2" title={diskTitle}>
          <span className="text-[11px] font-medium text-gray-600 dark:text-gray-300">Disk</span>
          <div className="hidden sm:block h-2 w-14 sm:w-16 rounded-full bg-gray-200/70 dark:bg-white/10 overflow-hidden">
            <div
              className={`h-full ${barTone(diskTone)}`}
              style={{ width: `${Math.round(usedFill * 100)}%` }}
            />
          </div>
          <span className="text-[11px] w-11 tabular-nums text-gray-900 dark:text-gray-100">
            {formatBytes(diskFreeBytes)}
          </span>
        </div>

        {/* PING */}
        <div className="flex items-center gap-2">
          <span className="text-[11px] font-medium text-gray-600 dark:text-gray-300">Ping</span>
          <div className="hidden sm:block h-2 w-14 sm:w-16 rounded-full bg-gray-200/70 dark:bg-white/10 overflow-hidden">
            <div
              className={`h-full ${barTone(pingTone)}`}
              style={{ width: `${Math.round(pingFill * 100)}%` }}
            />
          </div>
          <span className="text-[11px] w-10 tabular-nums text-gray-900 dark:text-gray-100">
            {formatMs(ping)}
          </span>
        </div>

        {/* FPS */}
        <div className="flex items-center gap-2">
          <span className="text-[11px] font-medium text-gray-600 dark:text-gray-300">FPS</span>
          <div className="hidden sm:block h-2 w-14 sm:w-16 rounded-full bg-gray-200/70 dark:bg-white/10 overflow-hidden">
            <div
              className={`h-full ${barTone(fpsTone)}`}
              style={{ width: `${Math.round(fpsFill * 100)}%` }}
            />
          </div>
          <span className="text-[11px] w-8 tabular-nums text-gray-900 dark:text-gray-100">
            {fps != null ? fps : '–'}
          </span>
        </div>

        {/* CPU */}
        <div className="flex items-center gap-2">
          <span className="text-[11px] font-medium text-gray-600 dark:text-gray-300">CPU</span>
          <div className="hidden sm:block h-2 w-14 sm:w-16 rounded-full bg-gray-200/70 dark:bg-white/10 overflow-hidden">
            <div
              className={`h-full ${barTone(cpuTone)}`}
              style={{ width: `${Math.round(cpuFill * 100)}%` }}
            />
          </div>
          <span className="text-[11px] w-10 tabular-nums text-gray-900 dark:text-gray-100">
            {formatPct(cpu)}
          </span>
        </div>
      </div>
    </div>
  )
}
