"""
Power module — P1 Profiles + TH Temperature.

Two consumer-first screens on the fixed 640×480 stage. The R36S is a FANLESS
device with one battery and no rich fuel-gauge telemetry, so we surface only
what is REAL and say it in human words — no milliamps, no cycle counts, no
charge-history charts, no governor strings (the owner's rule: human concepts,
not a sensor dashboard).

  P1Profiles  Saver / Balanced / Performance picker (real governor apply).
              Each card reads in plain English ("Stays cool · longest
              battery"); a calm summary line shows battery + heat in words.
              Y opens Temperature. X reveals a small technical tier.
  THThermals  "Temperature" — a verdict in words (Cool / Warm / Getting hot)
              from the real thermal zones, the heat sources the device
              actually reports, and a passive-cooling note. No charts.

(An earlier P2 "battery health" screen was removed — its mAh / cycles /
charge-limit content is fiction this hardware can't provide.)

Class shape mirrors the other screens: `name`, `__init__(self, app, …)`,
`on_action(self, action)`, `update(self, dt_ms)`, `draw(self, surf)`.
"""

from __future__ import annotations

import pygame

from ..design import tokens as t
from ..shared import widgets as w
from ..shared import fonts, icons, render
from ..shared import backend
from .. import powerops

STAGE_W = t.VIEWPORT_WIDTH
STAGE_H = t.VIEWPORT_HEIGHT
SAFE = t.Space.SAFE_AREA


# ─────────────────────────────────────────────────────────────────────
# module-level helpers (charts, gauges, profile cards, selectors, tech)
# ─────────────────────────────────────────────────────────────────────


def _tinted_chip(box, tint, glyph, glyph_px, *, radius=8, border=0.35, fill=0.12):
    """A small rounded icon chip: tinted fill + faint border + centered glyph."""
    chip = pygame.Surface((box, box), pygame.SRCALPHA)
    pygame.draw.rect(chip, t.with_alpha(tint, fill), (0, 0, box, box), border_radius=radius)
    if border:
        pygame.draw.rect(chip, t.with_alpha(tint, border), (0, 0, box, box),
                         width=1, border_radius=radius)
    g = icons.icon(glyph, glyph_px, tint)
    chip.blit(g, ((box - glyph_px) // 2, (box - glyph_px) // 2))
    return chip


def _profile_card(surf, rect, *, key, title, sub, feel, feel_kind, eta, icon_name,
                  active=False, focused=False):
    """
    P1 profile card. `key` ∈ {saver,balanced,perf} drives the icon tint.
    Active card uses the elevated gradient body; focused adds the signature
    glow + gradient border. `feel_kind` ∈ {None,"on","warn"} colors the
    plain-English feel line at the bottom.
    """
    radius = 12
    tints = {"saver": t.Color.SUCCESS, "balanced": t.Color.ACCENT_CYAN,
             "perf": t.Color.ACCENT_PEACH}
    tint = tints.get(key, t.Color.ACCENT_CYAN)

    if active:
        body = render.rounded_gradient((rect.w, rect.h), t.Color.SURFACE_ELEVATED,
                                       t.Color.SURFACE_BASE, radius, 160)
    else:
        body = render.rounded_gradient((rect.w, rect.h), t.Color.SURFACE_BASE,
                                       t.Color.BG_MID, radius, 160)
        pygame.draw.rect(body, t.Color.BORDER_SOFT, (0, 0, rect.w, rect.h),
                         width=1, border_radius=radius)

    pad = 12
    # top row: tinted icon chip + ETA / ACTIVE line
    box = 32
    chip = _tinted_chip(box, tint, icon_name, 18, radius=8, border=0, fill=0.16)
    body.blit(chip, (pad, pad))

    # live/eta line, right-aligned in the top row
    if active:
        # "● ACTIVE · 2 h 48 m" in cyan with a glowing dot
        eta_col = t.Color.ACCENT_CYAN
        dot_r = 3
        eta_txt = fonts.render(t.Type.META_SM, f"ACTIVE · {eta}", eta_col)
        ex = rect.w - pad - eta_txt.get_width()
        body.blit(eta_txt, (ex, pad + (box - eta_txt.get_height()) // 2))
        pygame.draw.circle(body, eta_col, (ex - 6, pad + box // 2), dot_r)
    else:
        eta_txt = fonts.render(t.Type.META_SM, f"~ {eta}", t.Color.TEXT_MUTED)
        body.blit(eta_txt, (rect.w - pad - eta_txt.get_width(),
                            pad + (box - eta_txt.get_height()) // 2))

    # title
    ty = pad + box + 8
    title_style = t.TextStyle(t.SPACE_GROTESK_BOLD, 17, "Bold", -0.3)
    ts = fonts.render(title_style, title, t.Color.TEXT_PRIMARY)
    body.blit(ts, (pad, ty))

    # plain-English subtitle (wrapped to the card width)
    sy = ty + ts.get_height() + 5
    sy = _wrap_text(body, t.Type.BODY_SM, sub, t.Color.TEXT_SECONDARY,
                    pad, sy, rect.w - pad * 2, line_h=15)

    # feel line pinned to the bottom, above a dashed divider. Wraps to up
    # to two lines so the plain-English verdict is never truncated.
    feel_col = {"on": t.Color.ACCENT_CYAN, "warn": t.Color.ACCENT_PEACH}.get(
        feel_kind, t.Color.TEXT_SECONDARY)
    maxw = rect.w - pad * 2
    line_h = 14
    feel_lines = _wrap_lines(t.Type.BODY_SM, feel, maxw)
    block_h = line_h * len(feel_lines)
    w.dashed_line(body, (pad, rect.h - pad - block_h - 7),
                  (rect.w - pad, rect.h - pad - block_h - 7),
                  t.Color.BORDER_SOFT, dash=3, gap=3, width=1)
    ly = rect.h - pad - block_h
    for line in feel_lines:
        body.blit(fonts.render(t.Type.BODY_SM, line, feel_col), (pad, ly))
        ly += line_h

    if focused:
        w.draw_focused_card(surf, rect, radius, scale=1.0, base_surface=body)
    else:
        surf.blit(body, rect.topleft)


def _gauge(surf, rect, *, label, icon_name, icon_color, temp, unit, level_kind,
           marker_frac, scale_labels, tech_node=None, focused=False):
    """
    TH temperature gauge: uppercase label + tinted glyph + big mono temp +
    a gradient thermal bar with a marker at `marker_frac` (0..1). When
    `tech_node` is provided (only behind the X toggle) it appears as a tiny
    sensor-path line in the top-right.
    """
    radius = 12
    body = render.rounded_gradient((rect.w, rect.h), t.Color.SURFACE_BASE,
                                   t.Color.BG_MID, radius, 160)
    if not focused:
        pygame.draw.rect(body, t.Color.BORDER_SOFT, (0, 0, rect.w, rect.h),
                         width=1, border_radius=radius)

    pad = 12
    # top: glyph + label, with optional tech node on the right
    g = icons.icon(icon_name, 14, icon_color)
    body.blit(g, (pad, pad + 1))
    lbl = fonts.render(t.Type.EYEBROW, label, t.Color.TEXT_MUTED, upper=True)
    body.blit(lbl, (pad + g.get_width() + 6, pad + (g.get_height() - lbl.get_height()) // 2))
    if tech_node:
        node = fonts.render(t.Type.META_SM, tech_node, t.Color.TEXT_MUTED)
        maxw = rect.w - pad - (pad + g.get_width() + 6 + lbl.get_width() + 6)
        if node.get_width() > maxw and maxw > 8:
            node = node.subsurface((0, 0, maxw, node.get_height()))
        body.blit(node, (rect.w - pad - node.get_width(), pad + 1))

    # big temperature readout
    state_col = {"warn": t.Color.ACCENT_PEACH, "ok": t.Color.SUCCESS}.get(
        level_kind, t.Color.TEXT_PRIMARY)
    num_style = t.TextStyle(t.SPACE_GROTESK_BOLD, 32, "Bold", -0.6)
    num = fonts.render(num_style, str(temp), state_col)
    ny = pad + max(g.get_height(), lbl.get_height()) + 8
    body.blit(num, (pad, ny))
    us = fonts.render(t.Type.STATE, unit, t.Color.TEXT_SECONDARY)
    body.blit(us, (pad + num.get_width() + 4, ny + num.get_height() - us.get_height() - 3))

    # thermal bar: a 4-stop gradient (mint→cyan→peach→error) painted column
    # by column to match the HTML stops at 0/35/70/95%, then dimmed.
    bar_h = 8
    bar_y = rect.h - pad - 12 - bar_h
    bar_w = rect.w - pad * 2
    bar = pygame.Surface((bar_w, bar_h), pygame.SRCALPHA)
    stops = [t.Color.SUCCESS, t.Color.ACCENT_CYAN, t.Color.ACCENT_PEACH, t.Color.ERROR]
    for cx in range(bar_w):
        frac = cx / max(bar_w - 1, 1)
        # match HTML stops at 0/35/70/95%
        if frac <= 0.35:
            col = render._lerp(stops[0], stops[1], frac / 0.35)
        elif frac <= 0.70:
            col = render._lerp(stops[1], stops[2], (frac - 0.35) / 0.35)
        else:
            col = render._lerp(stops[2], stops[3], min((frac - 0.70) / 0.25, 1.0))
        bar.fill(col, (cx, 0, 1, bar_h))
    barmask = render._round_mask(bar_w, bar_h, 999)
    bar.blit(barmask, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
    # darken (HTML overlays rgba(15,11,31,.45))
    dim = pygame.Surface((bar_w, bar_h), pygame.SRCALPHA)
    pygame.draw.rect(dim, t.with_alpha(t.Color.BG_DEEP, 0.45), (0, 0, bar_w, bar_h),
                     border_radius=999)
    bar.blit(dim, (0, 0))
    body.blit(bar, (pad, bar_y))
    # marker
    mk_col = {"warn": t.Color.ACCENT_PEACH, "ok": t.Color.SUCCESS}.get(
        level_kind, t.Color.ACCENT_ROSE)
    mx = pad + int(bar_w * max(0.0, min(marker_frac, 1.0)))
    mcy = bar_y + bar_h // 2
    pygame.draw.circle(body, t.Color.TEXT_PRIMARY, (mx, mcy), 7)
    pygame.draw.circle(body, mk_col, (mx, mcy), 7, 2)

    # scale labels under the bar
    sca_y = bar_y + bar_h + 2
    n = len(scale_labels)
    for i, text in enumerate(scale_labels):
        s = fonts.render(t.Type.META_SM, text, t.Color.TEXT_MUTED)
        if i == 0:
            sx = pad
        elif i == n - 1:
            sx = rect.w - pad - s.get_width()
        else:
            sx = pad + bar_w * i / (n - 1) - s.get_width() // 2
        body.blit(s, (int(sx), sca_y))

    if focused:
        w.draw_focused_card(surf, rect, radius, scale=1.0, base_surface=body)
    else:
        surf.blit(body, rect.topleft)


def _info_strip(surf, rect, *, icon_name, icon_color, title, sub, right_widget=None,
                tint_bg=None, gradient_chip=True):
    """
    A horizontal strip used for "auto-switch per game" (P1) and the
    "charge limit" row (P2): tinted icon chip + title/sub stack on the left,
    and a caller-supplied right widget (a badge or a segmented selector).
    `right_widget(body, rx_max)` is a callback that draws on the strip body
    and returns the x where its content starts (so the meta won't overlap).
    """
    radius = 10
    body = pygame.Surface((rect.w, rect.h), pygame.SRCALPHA)
    bg = tint_bg if tint_bg is not None else t.with_alpha(t.Color.BG_DEEP, 0.5)
    pygame.draw.rect(body, bg, (0, 0, rect.w, rect.h), border_radius=radius)
    pygame.draw.rect(body, t.Color.BORDER_SOFT, (0, 0, rect.w, rect.h),
                     width=1, border_radius=radius)

    pad = 12
    box = 32
    if gradient_chip:
        chip = render.rounded_gradient(
            (box, box), t.with_alpha(t.Color.ACCENT_ROSE, 0.18)[:3],
            t.with_alpha(t.Color.ACCENT_CYAN, 0.18)[:3], 8, 135)
        pygame.draw.rect(chip, t.Color.BORDER_SOFT, (0, 0, box, box),
                         width=1, border_radius=8)
        chip.blit(icons.icon(icon_name, 16, icon_color), ((box - 16) // 2, (box - 16) // 2))
    else:
        chip = pygame.Surface((box, box), pygame.SRCALPHA)
        chip.blit(icons.icon(icon_name, 18, icon_color), ((box - 18) // 2, (box - 18) // 2))
    body.blit(chip, (pad, (rect.h - box) // 2))

    # right widget first (returns left edge of its content)
    content_left = rect.w - pad
    if right_widget is not None:
        content_left = right_widget(body, rect.w - pad)

    # meta stack
    mx = pad + box + 12
    title_s = fonts.render(t.Type.STATE, title, t.Color.TEXT_PRIMARY)
    sub_s = fonts.render(t.Type.META_SM, sub, t.Color.TEXT_MUTED)
    # clip meta so it never runs under the right widget
    maxw = content_left - 10 - mx
    if maxw > 8:
        if title_s.get_width() > maxw:
            title_s = title_s.subsurface((0, 0, maxw, title_s.get_height()))
        if sub_s.get_width() > maxw:
            sub_s = sub_s.subsurface((0, 0, maxw, sub_s.get_height()))
    stack_h = title_s.get_height() + 2 + sub_s.get_height()
    my = (rect.h - stack_h) // 2
    body.blit(title_s, (mx, my))
    body.blit(sub_s, (mx, my + title_s.get_height() + 2))

    surf.blit(body, rect.topleft)


def _tip_strip(surf, rect, *, icon_name, icon_color, segments, tint):
    """
    Cyan-tinted info banner (TH passive-cooling tip). `segments` is a list
    of (text, bold) tuples that wrap across the strip; bold segments use
    the primary text color.
    """
    radius = 8
    body = pygame.Surface((rect.w, rect.h), pygame.SRCALPHA)
    pygame.draw.rect(body, t.with_alpha(tint, 0.06), (0, 0, rect.w, rect.h),
                     border_radius=radius)
    pygame.draw.rect(body, t.with_alpha(tint, 0.30), (0, 0, rect.w, rect.h),
                     width=1, border_radius=radius)
    pad = 12
    g = icons.icon(icon_name, 14, icon_color)
    body.blit(g, (pad, pad + 1))
    tx = pad + g.get_width() + 8
    _wrap_segments(body, t.Type.BODY_SM, segments, tx, pad,
                   rect.w - tx - pad, line_h=15,
                   normal=t.Color.TEXT_SECONDARY, strong=t.Color.TEXT_PRIMARY)
    surf.blit(body, rect.topleft)


def _tech_card(surf, rect, *, title, rows):
    """
    The behind-X technical card — the ONLY place raw numbers / clocks /
    sensor paths appear. Uppercase eyebrow header over a two-column
    key/value grid (mono), exactly like diagnostics / W6.
    """
    body = render.rounded_gradient((rect.w, rect.h), t.Color.SURFACE_BASE,
                                   t.Color.BG_MID, 10, 160)
    pygame.draw.rect(body, t.Color.BORDER_SOFT, (0, 0, rect.w, rect.h),
                     width=1, border_radius=10)
    hdr = fonts.render(t.Type.EYEBROW, title, t.Color.TEXT_MUTED, upper=True)
    body.blit(hdr, (12, 8))
    gy = 8 + hdr.get_height() + 6
    line_h = 16
    col_w = (rect.w - 24) // 2
    half = (len(rows) + 1) // 2
    kv_col = {"ok": t.Color.SUCCESS, "warn": t.Color.ACCENT_PEACH,
              "cyan": t.Color.ACCENT_CYAN, None: t.Color.TEXT_SECONDARY}
    for i, (key, value, state) in enumerate(rows):
        col = i // half
        row = i % half
        cx = 12 + col * col_w
        ry = gy + row * line_h
        if ry + line_h > rect.h - 4:
            continue
        k = fonts.render(t.Type.META_SM, key, t.Color.TEXT_MUTED)
        body.blit(k, (cx, ry))
        v = fonts.render(t.Type.META_SM, value, kv_col.get(state, t.Color.TEXT_SECONDARY))
        key_w = 92
        vx = cx + key_w
        maxw = cx + col_w - vx - 6
        if maxw > 6 and v.get_width() > maxw:
            v = v.subsurface((0, 0, maxw, v.get_height()))
        body.blit(v, (vx, ry))
    surf.blit(body, rect.topleft)


# ─── tiny text helpers ───────────────────────────────────────────────


def _wrap_lines(style, text, max_w):
    """Greedy word-wrap → list of lines that each fit within max_w."""
    lines, line = [], ""
    for word in text.split(" "):
        trial = (line + " " + word).strip()
        if fonts.measure(style, trial)[0] <= max_w or not line:
            line = trial
        else:
            lines.append(line)
            line = word
    if line:
        lines.append(line)
    return lines


def _wrap_text(surf, style, text, color, x, y, max_w, *, line_h):
    """Greedy word-wrap of a single string; returns the y after the block."""
    for line in _wrap_lines(style, text, max_w):
        surf.blit(fonts.render(style, line, color), (x, y))
        y += line_h
    return y


def _wrap_segments(surf, style, segments, x, y, max_w, *, line_h, normal, strong):
    """
    Word-wrap a list of (text, bold) segments as a single flowing paragraph.
    Bold segments render in `strong`, others in `normal`.
    """
    cx, cy = x, y
    space_w = fonts.measure(style, " ")[0]
    for text, bold in segments:
        col = strong if bold else normal
        for i, word in enumerate(text.split(" ")):
            if word == "":
                continue
            ww = fonts.measure(style, word)[0]
            if cx > x and cx + ww > x + max_w:
                cx = x
                cy += line_h
            surf.blit(fonts.render(style, word, col), (cx, cy))
            cx += ww + space_w
    return cy + line_h


# ═════════════════════════════════════════════════════════════════════
# P1 — Power profiles
# ═════════════════════════════════════════════════════════════════════


class P1Profiles:
    """
    Saver / Balanced / Performance picker. Three cards read in plain
    English; the active one carries the rose→cyan signature. A per-game
    "auto-switch" strip sits below. X reveals the technical tier (the only
    place CPU/GPU clocks + governor appear) — keeping the default view
    clean per the consumer-language rule.
    """
    name = "power-profiles"

    def __init__(self, app, active=1):
        self.app = app
        self.focus = active          # which card is focused (starts on active)
        self.active = active         # which profile is currently applied
        self.advanced = False        # X → technical tier

        # (key, title, sub, feel, feel_kind, eta, icon)
        self.profiles = [
            ("saver", "Saver",
             "Easy on the battery. Great for chip-tune emulators "
             "(NES/GB/MD).",
             "Stays cool · longest battery", None,
             "4 h 50 m", "ph-battery-low"),
            ("balanced", "Balanced",
             "Sweet spot for PS1 · GBA · SNES · most PSP titles.",
             "Comfortable warmth · most games", "on",
             "2 h 48 m", "ph-broadcast"),
            ("perf", "Performance",
             "All cores unlocked. N64 · Dreamcast · heavy PSP/PCSX2.",
             "Can get warm · heaviest emulators", "warn",
             "1 h 35 m", "ph-game-controller"),
        ]
        # technical tier — the ONLY place clocks / governor appear.
        self._tech_rows = [
            ("cpu max", "1.5 GHz", None),
            ("gpu max", "520 MHz", None),
            ("governor", "schedutil", "cyan"),
            ("a-cores", "4 online", None),
            ("ddr", "786 MHz", None),
            ("ac source", "5V / 2A", "ok"),
        ]
        # screen profile key → powerops profile (saver≈powersave, perf≈performance)
        self._prof_key = {"saver": "powersave", "balanced": "balanced",
                          "perf": "performance"}
        # Live overlay: when the backend is on, reflect the REAL governor +
        # battery + thermals; otherwise the demo values above stand (dev).
        self._st = backend.power_status()
        if self._st is not None:
            idx = {"powersave": 0, "balanced": 1, "performance": 2}.get(
                self._st.get("profile"))
            if idx is not None:
                self.active = idx
                if active == 1:          # caller didn't force focus → follow active
                    self.focus = idx
            real = self._real_tech_rows(self._st)
            if real:
                self._tech_rows = real

    def _real_tech_rows(self, st):
        rows = []
        if st.get("governor"):
            rows.append(("governor", st["governor"], "cyan"))
        bat = st.get("battery") or {}
        if bat.get("percent") is not None:
            rows.append(("battery", f"{bat['percent']}%", None))
        th = st.get("thermals") or []
        if th:
            hot = th[0]
            rows.append(("temp", f"{hot['c']:.0f} °C · {hot['label']}",
                         "ok" if hot["c"] < 70 else "warn"))
        avail = st.get("available") or []
        if avail:
            rows.append(("governors", ", ".join(avail[:4]), None))
        return rows

    def on_action(self, action):
        if action == "left":
            self.focus = max(0, self.focus - 1)
        elif action == "right":
            self.focus = min(len(self.profiles) - 1, self.focus + 1)
        elif action == "a":
            name = self.profiles[self.focus][1]
            prof = self._prof_key[self.profiles[self.focus][0]]
            res = backend.power_set_profile(prof)   # None=not live, True/False=live
            if res is False:
                self.app.toast("Couldn't apply — governor unavailable")
            else:
                self.active = self.focus
                self.app.toast(f"{name} active")
        elif action == "y":
            self.app.push(THThermals(self.app))
        elif action == "x":
            self.advanced = not self.advanced
            self.app.toast("Technical details on" if self.advanced
                           else "Technical details off")
        elif action == "b":
            self.app.pop()

    def update(self, dt_ms):
        pass

    def draw(self, surf):
        w.status_bar(surf, title="Power profiles", back=True)
        self._draw_hero(surf)

        # three profile cards
        top = 96
        gap = 10
        card_w = (STAGE_W - SAFE * 2 - gap * 2) // 3
        card_h = 168
        xs = [SAFE, SAFE + card_w + gap, SAFE + (card_w + gap) * 2]
        for i, p in enumerate(self.profiles):
            key, title, sub, feel, feel_kind, eta, icon_name = p
            rect = pygame.Rect(xs[i], top, card_w, card_h)
            _profile_card(surf, rect, key=key, title=title, sub=sub, feel=feel,
                          feel_kind=feel_kind, eta=eta, icon_name=icon_name,
                          active=(i == self.active), focused=(i == self.focus))

        y = top + card_h + 10
        if self.advanced:
            # technical card replaces the summary strip when revealed
            rect = pygame.Rect(SAFE, y, STAGE_W - SAFE * 2, 448 - y - 8)
            _tech_card(surf, rect, title="TECHNICAL DETAILS", rows=self._tech_rows)
        else:
            self._draw_summary(surf, y)

        x_hint = "show technical details" if not self.advanced else "hide technical"
        w.footer_hints(surf, [("A", "activate"), ("Y", "temperature"),
                              ("X", x_hint), ("B", "back")])

    def _draw_hero(self, surf):
        # "<active profile name>" + "on AC · charging" meta on the right
        title = fonts.render(t.Type.HERO, self.profiles[self.active][1],
                             t.Color.TEXT_PRIMARY)
        surf.blit(title, (SAFE, 56))
        # real battery when live; else the demo "on AC · charging"
        bat = (self._st or {}).get("battery") if self._st else None
        if bat and bat.get("percent") is not None:
            st = (bat.get("status") or "").lower()
            word = {"charging": "charging", "discharging": "on battery",
                    "full": "full", "not charging": "idle"}.get(st, st or "battery")
            lead_txt, val_txt = f"{bat['percent']}% · ", word
        else:
            lead_txt, val_txt = "on AC · ", "charging"
        meta_lead = fonts.render(t.Type.META_SM, lead_txt, t.Color.TEXT_MUTED)
        meta_b = fonts.render(t.Type.META_SM, val_txt, t.Color.ACCENT_CYAN)
        rx = STAGE_W - SAFE - meta_b.get_width()
        cy = 56 + (title.get_height() - meta_b.get_height()) // 2
        surf.blit(meta_b, (rx, cy))
        surf.blit(meta_lead, (rx - meta_lead.get_width(), cy))

    def _draw_summary(self, surf, y):
        """A calm, human one-liner under the cards: battery state + how warm
        the device runs. No dashboard numbers — the raw °C / governor live
        behind X. Real when live; a calm default off-device."""
        rect = pygame.Rect(SAFE, y, STAGE_W - SAFE * 2, 56)
        st = self._st or {}
        bat = st.get("battery") or {}
        pct = bat.get("percent")
        status = (bat.get("status") or "").lower()
        if pct is None:                                   # dev / no reading
            title, icon_name = "On AC · charging", "ph-battery-charging-vertical"
        else:
            word = {"charging": "charging", "discharging": "on battery",
                    "full": "full", "not charging": "idle"}.get(status, "battery")
            title = f"Battery {pct}% · {word}"
            if status == "charging":
                icon_name = "ph-battery-charging-vertical"
            elif pct >= 60:
                icon_name = "ph-battery-high"
            elif pct >= 25:
                icon_name = "ph-battery-medium"
            else:
                icon_name = "ph-battery-low"
        th = st.get("thermals") or []
        verdict = (powerops.temp_verdict(th[0]["c"]) if th else None) or "cool"
        sub = {"cool": "Running cool", "warm": "A little warm",
               "hot": "Getting hot"}[verdict]
        icon_col = {"cool": t.Color.SUCCESS, "warm": t.Color.ACCENT_PEACH,
                    "hot": t.Color.ERROR}[verdict]
        _info_strip(surf, rect, icon_name=icon_name, icon_color=icon_col,
                    title=title, sub=sub, gradient_chip=False)


# ═════════════════════════════════════════════════════════════════════
# TH — Temperature (fanless, human language)
# ═════════════════════════════════════════════════════════════════════


class THThermals:
    """
    Temperature — a calm, HUMAN readout for the FANLESS R36S.

    No charts, no throttle thresholds, no sensor paths, no governor — just a
    verdict in plain words (Cool / Warm / Getting hot), the few real heat
    sources the device actually reports, and one line about passive cooling.
    (The owner's rule: human concepts, not a sensor dashboard.)

    Live data comes from backend.power_thermals() (real thermal zones);
    off-device it shows a calm demo. Reached from P1 via the Y button.
    """
    name = "thermals"

    # verdict → (word, reassurance, color)
    _VERDICT = {
        "cool": ("Cool", "Running normally — nice and calm.", t.Color.SUCCESS),
        "warm": ("Warm", "Normal for heavy games — nothing to worry about.",
                 t.Color.ACCENT_PEACH),
        "hot":  ("Getting hot", "Maybe a short breather between sessions.",
                 t.Color.ERROR),
    }
    _ICON = {"cpu": "ph-broadcast", "gpu": "ph-game-controller",
             "battery": "ph-battery-charging-vertical", "other": "ph-info"}

    def __init__(self, app):
        self.app = app
        zones = backend.power_thermals()           # live list, or None off-device
        self._demo = zones is None
        if self._demo:
            zones = [{"label": "soc-thermal", "c": 52.0},
                     {"label": "gpu-thermal", "c": 47.0}]
        self._summary = powerops.summarize_thermals(zones)

    def on_action(self, action):
        if action == "b":
            self.app.pop()

    def update(self, dt_ms):
        pass

    def draw(self, surf):
        w.status_bar(surf, title="Temperature", back=True)
        self._draw_hero(surf)
        parts = self._summary.get("parts") or []
        if parts:
            self._draw_parts(surf, parts)
        else:
            msg = fonts.render(t.Type.BODY,
                               "No temperature sensors reported on this device.",
                               t.Color.TEXT_MUTED)
            surf.blit(msg, (SAFE, 150))
        self._draw_tip(surf)
        w.footer_hints(surf, [("B", "back")])

    def _draw_hero(self, surf):
        verdict = self._summary.get("verdict") or "cool"
        word, line, col = self._VERDICT[verdict]
        title = fonts.render(t.Type.HERO, word, t.Color.TEXT_PRIMARY)
        dot_r = 6
        pygame.draw.circle(surf, col, (SAFE + dot_r, 50 + title.get_height() // 2),
                           dot_r)
        surf.blit(title, (SAFE + dot_r * 2 + 12, 50))
        sub = fonts.render(t.Type.BODY, line, t.Color.TEXT_SECONDARY)
        surf.blit(sub, (SAFE, 92))
        pygame.draw.line(surf, t.Color.BORDER_SOFT, (SAFE, 122),
                         (STAGE_W - SAFE, 122), 1)

    def _draw_parts(self, surf, parts):
        top, gap = 138, 10
        n = len(parts)
        gw = (STAGE_W - SAFE * 2 - gap * (n - 1)) // n
        gh = 112
        for i, p in enumerate(parts):
            rect = pygame.Rect(SAFE + i * (gw + gap), top, gw, gh)
            v = p.get("verdict") or "cool"
            col = self._VERDICT[v][2]
            frac = max(0.0, min(1.0, (p["c"] - 30) / 60.0))
            _gauge(surf, rect, label=p["name"],
                   icon_name=self._ICON.get(p["kind"], "ph-info"), icon_color=col,
                   temp=int(round(p["c"])), unit="°C",
                   level_kind=("ok" if v == "cool" else "warn"),
                   marker_frac=frac, scale_labels=["Cool", "Hot"], tech_node=None)

    def _draw_tip(self, surf):
        tip_h = 52
        rect = pygame.Rect(SAFE, 448 - 8 - tip_h, STAGE_W - SAFE * 2, tip_h)
        _tip_strip(surf, rect, icon_name="ph-info", icon_color=t.Color.ACCENT_CYAN,
                   tint=t.Color.ACCENT_CYAN,
                   segments=[
                       ("Passive cooling.", True),
                       (" The R36S has no fan — the back vents do the work. "
                        "Heavy emulators run warmer; that's normal.", False),
                   ])
