"""
wivexx_perf_osd.py — transparent in-game performance OSD  (v1.0)
═══════════════════════════════════════════════════════════════════════
The web HUD (perf_overlay) can't be made see-through — a windowed WebView2
surface composites through DirectComposition, which ignores window transparency.
So this is the GAMING variant: a Tk top-level whose pixels are driven by
UpdateLayeredWindow (per-pixel alpha, reusing wivexx_ball_overlay._Layered) so the
game shows THROUGH it. It is:
  • truly translucent (adjustable opacity), drawn with PIL,
  • CLICK-THROUGH (WS_EX_TRANSPARENT) — clicks pass to the game underneath,
  • topmost + no-activate + tool-window, so it floats over borderless-fullscreen.

Fed by wivexx_sysmon.snapshot(). Same metric set as the web HUD.

Public API (driven from wivexx_gui, on the tk sidecar root):
    osd = PerfOSD(root)
    osd.show() / osd.hide() / osd.toggle() / osd.is_shown()
    osd.set_opacity(0.0..1.0) / osd.set_metrics([...]) / osd.set_corner("tr")
"""

from typing import Optional

from PIL import Image, ImageDraw, ImageFont

_INK = (13, 13, 13)
_VOLT = (193, 247, 58)
_BONE = (242, 240, 234)
_STONE = (138, 138, 133)
_STONE_DIM = (85, 85, 79)
_AMBER = (224, 160, 48)
_DANGER = (224, 85, 58)
_GRAPHITE = (40, 40, 37)

_LABEL = {"cpu": "CPU", "gpu": "GPU", "vram": "VRAM", "ram": "RAM",
          "disk": "DISK", "net": "NET", "speed": "SPEED", "fps": "FPS"}
_ALL = ["cpu", "gpu", "vram", "ram", "disk", "net", "speed", "fps"]

_W = 290                 # panel width (px)
_PAD = 14
_HEAD_H = 30
_ROW_H = 28
_SS = 2                  # supersample for crisp text/edges


def _font(sz, bold=False):
    for p in (("C:/Windows/Fonts/consolab.ttf",) if bold else ("C:/Windows/Fonts/consola.ttf",)):
        try:
            return ImageFont.truetype(p, sz)
        except Exception:
            pass
    return ImageFont.load_default()


def _load_color(frac):
    if frac is None:
        return _STONE_DIM
    if frac >= 0.90:
        return _DANGER
    if frac >= 0.70:
        return _AMBER
    return _VOLT


def _metric(key, s):
    """→ (bar: bool, frac, main, sub) — mirrors the web HUD's metric()."""
    g = s.get("gpu") or {}
    c = s.get("cpu") or {}
    r = s.get("ram") or {}
    n = s.get("net") or {}
    d = s.get("disk") or {}
    sp = s.get("speed") or {}

    def pct(v):
        return None if v is None else max(0.0, min(1.0, v / 100.0))

    if key == "cpu":
        p, t = c.get("pct"), c.get("temp_c")
        return (True, pct(p), ("%.0f%%" % p) if p is not None else "—",
                ("%.0f°" % t) if t is not None else "")
    if key == "gpu":
        p, t = g.get("pct"), g.get("temp_c")
        return (True, pct(p), ("%.0f%%" % p) if p is not None else "—",
                ("%.0f°" % t) if t is not None else "")
    if key == "vram":
        mp, u, tot = g.get("mem_pct"), g.get("mem_used_mb"), g.get("mem_total_mb")
        return (True, pct(mp), ("%.0f%%" % mp) if mp is not None else "—",
                ("%.1fG" % (u / 1024)) if u else "")
    if key == "ram":
        p, u = r.get("pct"), r.get("used_gb")
        return (True, pct(p), ("%.0f%%" % p) if p is not None else "—",
                ("%.0fG" % u) if u else "")
    if key == "disk":
        p = d.get("pct")
        return (True, pct(p), ("%.0f%%" % p) if p is not None else "—", "C:")
    if key == "net":
        def f(k):                       # v1.56: adaptive KB/s < MB/s < GB/s
            if k is None:
                return "—"
            if k < 1024:
                return ("%.0f KB/s" % k) if k >= 10 else ("%.1f KB/s" % k)
            m = k / 1024.0
            if m < 1024:
                return ("%.0f MB/s" % m) if m >= 10 else ("%.1f MB/s" % m)
            g = m / 1024.0
            return ("%.0f GB/s" % g) if g >= 10 else ("%.2f GB/s" % g)
        return (False, None, "↓" + f(n.get("down_kbps")), "↑" + f(n.get("up_kbps")))
    if key == "speed":
        if sp.get("down_mbps") is not None:
            return (False, None, "↓%.0fM" % sp["down_mbps"],
                    ("↑%.0fM" % sp["up_mbps"]) if sp.get("up_mbps") is not None else "")
        return (False, None, "—", "")
    if key == "fps":
        fps = s.get("fps")
        return (False, None, ("%.0f" % fps) if fps else "—", "fps" if fps else "RTSS")
    return (False, None, "—", "")


def render(snap: dict, metrics: list, opacity: float) -> Image.Image:
    """Build the translucent OSD panel as an RGBA image (per-pixel alpha)."""
    rows = [m for m in metrics if m in _ALL] or ["cpu", "gpu", "ram"]
    h = _HEAD_H + _ROW_H * len(rows) + _PAD
    W, H, ss = _W, h, _SS
    pa = max(0.25, min(1.0, float(opacity)))
    bg_a = int(210 * pa)            # panel translucency
    img = Image.new("RGBA", (W * ss, H * ss), (0, 0, 0, 0))
    dr = ImageDraw.Draw(img)

    # rounded translucent panel + faint NEUTRAL hairline (no green outline — the only
    # brand accents left are the status dot + the metric bar fills)
    rad = 14 * ss
    dr.rounded_rectangle([1 * ss, 1 * ss, (W - 1) * ss, (H - 1) * ss], radius=rad,
                         fill=_INK + (bg_a,), outline=_BONE + (int(38 * pa),), width=max(1, ss))

    fb = _font(11 * ss, bold=True)
    fr = _font(11 * ss)
    fs = _font(9 * ss)
    px = _PAD * ss
    # header: ◉ PERFORMANCE
    dr.ellipse([px, 13 * ss, px + 7 * ss, 20 * ss], fill=_VOLT + (255,))
    dr.text((px + 13 * ss, 8 * ss), "PERFORMANCE", font=fb, fill=_BONE + (255,))

    bar_x = px + 46 * ss
    bar_w = 92 * ss
    for i, key in enumerate(rows):
        y = (_HEAD_H + i * _ROW_H) * ss
        cy = y + 13 * ss
        bar, frac, main, sub = _metric(key, snap)
        dr.text((px, y + 6 * ss), _LABEL.get(key, key), font=fr, fill=_STONE + (255,))
        if bar:
            dr.rounded_rectangle([bar_x, cy - 4 * ss, bar_x + bar_w, cy + 4 * ss],
                                 radius=4 * ss, fill=_GRAPHITE + (int(220 * pa),))
            if frac is not None:
                fw = max(4 * ss, int(bar_w * frac))
                dr.rounded_rectangle([bar_x, cy - 4 * ss, bar_x + fw, cy + 4 * ss],
                                     radius=4 * ss, fill=_load_color(frac) + (255,))
            dr.text((bar_x + bar_w + 9 * ss, y + 6 * ss), main, font=fr, fill=_BONE + (255,))
            if sub:
                sw = dr.textlength(sub, font=fs)
                dr.text((W * ss - px - sw, y + 7 * ss), sub, font=fs, fill=_STONE + (255,))
        else:
            dr.text((bar_x, y + 6 * ss), main, font=fr, fill=_BONE + (255,))
            if sub:
                dr.text((bar_x + 72 * ss, y + 7 * ss), sub, font=fs, fill=_STONE + (255,))

    if ss != 1:
        img = img.resize((W, H), Image.BILINEAR)
    return img


class PerfOSD:
    """A transparent, click-through, topmost metrics OSD on the tk sidecar root."""

    def __init__(self, root, metrics: Optional[list] = None,
                 opacity: float = 0.82, corner: str = "tl") -> None:
        self._root = root
        self._metrics = [m for m in (metrics or ["cpu", "gpu", "vram", "ram", "net", "speed"])
                         if m in _ALL]
        self._opacity = opacity
        self._corner = corner
        self._win = None
        self._layered = None
        self._shown = False
        self._running = False
        self._size = None

    # ── lifecycle ─────────────────────────────────────────────────────────────
    def _panel_size(self):
        return (_W, _HEAD_H + _ROW_H * (len(self._metrics) or 1) + _PAD)

    def _build(self):
        import tkinter as tk
        w, h = self._panel_size()
        self._size = (w, h)
        win = tk.Toplevel(self._root)
        self._win = win
        win.overrideredirect(True)
        win.attributes("-topmost", True)
        try:
            win.wm_attributes("-toolwindow", True)
        except Exception:
            pass
        sw = win.winfo_screenwidth()
        margin = 22
        x = sw - w - margin if "r" in self._corner else margin
        y = margin if "t" in self._corner else win.winfo_screenheight() - h - margin
        win.geometry(f"{w}x{h}+{x}+{y}")
        win.update_idletasks()
        try:
            from wivexx_ball_overlay import _Layered
            self._layered = _Layered(win.winfo_id(), (w, h))
        except Exception:
            self._layered = None
        self._apply_styles()

    def _apply_styles(self):
        """Click-through (WS_EX_TRANSPARENT) + no-activate + tool-window + topmost.
        WS_EX_LAYERED is already set by _Layered for the per-pixel alpha."""
        try:
            import ctypes
            from ctypes import wintypes
            u = ctypes.windll.user32
            try:
                getL, setL, LT = u.GetWindowLongPtrW, u.SetWindowLongPtrW, ctypes.c_ssize_t
            except AttributeError:
                getL, setL, LT = u.GetWindowLongW, u.SetWindowLongW, ctypes.c_long
            getL.restype = LT; getL.argtypes = [wintypes.HWND, ctypes.c_int]
            setL.restype = LT; setL.argtypes = [wintypes.HWND, ctypes.c_int, LT]
            GA_ROOT = 2
            hwnd = u.GetAncestor(self._win.winfo_id(), GA_ROOT) or self._win.winfo_id()
            GWL_EXSTYLE = -20
            WS_EX_TRANSPARENT = 0x20
            WS_EX_TOOLWINDOW = 0x80
            WS_EX_NOACTIVATE = 0x08000000
            WS_EX_TOPMOST = 0x8
            cur = getL(hwnd, GWL_EXSTYLE)
            setL(hwnd, GWL_EXSTYLE,
                 cur | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_TOPMOST)
        except Exception:
            pass

    def _draw(self):
        if not self._layered:
            return
        snap = {}
        try:
            import wivexx_sysmon
            snap = wivexx_sysmon.snapshot()
        except Exception:
            pass
        try:
            img = render(snap, self._metrics, self._opacity)
            self._layered.present(img)
        except Exception:
            pass

    def _tick(self):
        if not self._shown:
            self._running = False
            return
        self._draw()
        try:
            self._win.attributes("-topmost", True)
        except Exception:
            pass
        try:
            self._root.after(1000, self._tick)
        except Exception:
            self._running = False

    # ── public ────────────────────────────────────────────────────────────────
    def show(self):
        try:
            import wivexx_sysmon
            wivexx_sysmon.start(1.2)
        except Exception:
            pass
        if self._win is None:
            self._build()
        try:
            self._win.deiconify()
            self._win.attributes("-topmost", True)
        except Exception:
            pass
        self._shown = True
        self._draw()
        if not self._running:
            self._running = True
            self._tick()

    def hide(self):
        self._shown = False
        try:
            if self._win is not None:
                self._win.withdraw()
        except Exception:
            pass

    def toggle(self):
        if self._shown:
            self.hide()
        else:
            self.show()
        return self._shown

    def is_shown(self):
        return self._shown

    def set_opacity(self, f):
        self._opacity = max(0.25, min(1.0, float(f)))
        if self._shown:
            self._draw()

    def set_metrics(self, metrics):
        m = [x for x in metrics if x in _ALL]
        if not m:
            return
        self._metrics = m
        # size changed → rebuild the layered surface
        if self._win is not None:
            was = self._shown
            try:
                self._win.destroy()
            except Exception:
                pass
            self._win = None
            self._layered = None
            if was:
                self.show()

    def set_corner(self, corner):
        self._corner = corner


if __name__ == "__main__":
    # offline render preview (no tk window) — composite over a fake game frame
    snap = {"cpu": {"pct": 42, "temp_c": None}, "gpu": {"pct": 73, "temp_c": 68,
            "mem_pct": 88, "mem_used_mb": 7200, "mem_total_mb": 8192},
            "ram": {"pct": 51, "used_gb": 33}, "net": {"down_kbps": 2400, "up_kbps": 180},
            "speed": {"down_mbps": 458, "up_mbps": 116}, "fps": None}
    panel = render(snap, ["cpu", "gpu", "vram", "ram", "net", "speed"], 0.82)
    bg = Image.new("RGBA", panel.size, (60, 90, 130, 255))   # fake game
    bg.alpha_composite(panel)
    bg.convert("RGB").save("_osd_preview.png")
    print("wrote _osd_preview.png", panel.size)
