"""
Updates module — U1.

Recreates the Claude Design handoff (final-GHVscC) OTA home on the 640×480
stage in the H1 console grammar.

Screens (class ⇄ handoff):
  U1Updates ⇄ U1-updates.html  — current version badge + an available
                                  update card (focused) + a NEW/FIX/IMP
                                  changelog grid + an auto-check strip.

The default view stays consumer-first: it shows the version, the download
size, "no reboot needed", and a plain changelog. There is deliberately NO
build hash / sha256 on this screen — the OS handles signing; the user just
sees "signed by AAhead". A drill-down to the full changelog (X) and a
channel switch (Y) are stubbed as toasts for now.

Class shape: name, __init__(self, app, ...), on_action(self, action),
update(self, dt_ms), draw(self, surf). Actions are strings: a/b/x/y/...

This module only defines the screen + module-level helpers; it never edits
main.py or widgets.py.
"""

from __future__ import annotations

import pygame

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

SAFE = t.Space.SAFE_AREA            # 16
STAGE_W = t.VIEWPORT_WIDTH          # 640
STAGE_H = t.VIEWPORT_HEIGHT         # 480
BODY_W = STAGE_W - SAFE * 2         # 608
FOOTER_Y = 448

# changelog tag color map (NEW=mint, FIX=cyan, IMP=peach)
_TAG = {
    "NEW": t.Color.SUCCESS,
    "FIX": t.Color.ACCENT_CYAN,
    "IMP": t.Color.ACCENT_PEACH,
}


# ─── module-level helpers ─────────────────────────────────────────────


def _version_badge(version, label="STABLE", *, box=62):
    """
    The current-version tile: a rounded rose/cyan-tinted card with the
    version in gradient hero type and a tiny channel label in the corner.
    Matches U1's `.badge-card`.
    """
    s = render.rounded_gradient(
        (box, box), t.with_alpha(t.Color.ACCENT_ROSE, 0.22)[:3],
        t.with_alpha(t.Color.ACCENT_CYAN, 0.22)[:3], 14, 135)
    pygame.draw.rect(s, t.with_alpha(t.Color.ACCENT_ROSE, 0.4),
                     (0, 0, box, box), width=1, border_radius=14)
    v = w.gradient_text(t.TextStyle(t.SPACE_GROTESK_BOLD, 18, "Bold", -0.5),
                        version)
    s.blit(v, ((box - v.get_width()) // 2, (box - v.get_height()) // 2 - 2))
    lab = fonts.render(t.TextStyle(t.JETBRAINS_MONO, 8, "Regular", 0.8),
                       label, t.Color.ACCENT_CYAN)
    s.blit(lab, (box - 6 - lab.get_width(), box - 4 - lab.get_height()))
    return s


def _status_dot_line(surf, x, y, *, dot_color, segments):
    """
    A status dot + a row of mono segments joined by " · ". Each segment is a
    (text, color) tuple. Returns the x after the last segment. Used for the
    "● installed · stable" meta line.
    """
    dr = 4
    pygame.draw.circle(surf, dot_color, (x + dr, y + 7), dr)
    cx = x + dr * 2 + 6
    for i, (text, color) in enumerate(segments):
        if i > 0:
            sep = fonts.render(t.Type.META_SM, " · ", t.Color.TEXT_MUTED)
            surf.blit(sep, (cx, y)); cx += sep.get_width()
        g = fonts.render(t.Type.META_SM, text, color)
        surf.blit(g, (cx, y)); cx += g.get_width()
    return cx


def _pill_tag(text, color):
    """A small rounded channel chip (e.g. 'stable')."""
    lbl = fonts.render(t.TextStyle(t.JETBRAINS_MONO, 10, "Regular", 0.0),
                       text, color)
    pw, ph = lbl.get_width() + 16, 18
    s = pygame.Surface((pw, ph), pygame.SRCALPHA)
    pygame.draw.rect(s, t.with_alpha(t.Color.BG_DEEP, 0.4),
                     (0, 0, pw, ph), border_radius=999)
    pygame.draw.rect(s, t.Color.BORDER_SOFT, (0, 0, pw, ph),
                     width=1, border_radius=999)
    s.blit(lbl, ((pw - lbl.get_width()) // 2, (ph - lbl.get_height()) // 2))
    return s


def _avail_card(surf, rect, *, title, sub_segments, cta_label, focused):
    """
    The "an update is ready" callout: a rose→cyan gradient glyph, a hero
    title, a mono sub line of facts, and an inline Install CTA pill. Carries
    the signature glow when focused. Matches U1's `.avail`.
    """
    radius = t.Radius.CARD
    body = pygame.Surface((rect.w, rect.h), pygame.SRCALPHA)
    # rose/cyan tinted fill + border
    fill = render.rounded_gradient(
        (rect.w, rect.h), t.with_alpha(t.Color.ACCENT_ROSE, 0.10)[:3],
        t.with_alpha(t.Color.ACCENT_CYAN, 0.06)[:3], radius, 160)
    body.blit(fill, (0, 0))
    if not focused:
        pygame.draw.rect(body, t.with_alpha(t.Color.ACCENT_ROSE, 0.35),
                         (0, 0, rect.w, rect.h), width=1, border_radius=radius)

    pad = 14
    # ── gradient glyph ──
    gbox = 40
    glyph = render.rounded_gradient((gbox, gbox), t.Color.ACCENT_ROSE,
                                    t.Color.ACCENT_CYAN, 10, 135)
    gic = icons.icon("ph-download-simple", 22, t.Color.BG_MID)
    glyph.blit(gic, ((gbox - 22) // 2, (gbox - 22) // 2))
    body.blit(glyph, (pad, (rect.h - gbox) // 2))

    # ── CTA pill (right; measured first) ──
    cta_ic = icons.icon("ph-download-simple", 14, t.Color.ACCENT_CYAN)
    cta_lbl = fonts.render(t.TextStyle(t.INTER_SEMIBOLD, 13, "SemiBold", 0.0),
                           cta_label, t.Color.TEXT_PRIMARY)
    cta_w = 16 + cta_ic.get_width() + 8 + cta_lbl.get_width() + 16
    cta_h = 40
    cta = render.rounded_fill((cta_w, cta_h), t.Color.SURFACE_ELEVATED, 999)
    pygame.draw.rect(cta, t.Color.BORDER_BRIGHT, (0, 0, cta_w, cta_h),
                     width=1, border_radius=999)
    ccx = 16
    cta.blit(cta_ic, (ccx, (cta_h - cta_ic.get_height()) // 2))
    ccx += cta_ic.get_width() + 8
    cta.blit(cta_lbl, (ccx, (cta_h - cta_lbl.get_height()) // 2))
    cta_x = rect.w - pad - cta_w
    body.blit(cta, (cta_x, (rect.h - cta_h) // 2))

    # ── title + sub (middle column, clear of glyph and CTA) ──
    mx = pad + gbox + 14
    mw = cta_x - 12 - mx
    ttl = fonts.render(t.TextStyle(t.SPACE_GROTESK_BOLD, 18, "Bold", -0.3),
                       title, t.Color.TEXT_PRIMARY)
    if ttl.get_width() > mw:
        ttl = ttl.subsurface((0, 0, max(mw, 1), ttl.get_height()))
    # sub line: list of (text, color) joined by " · "
    sub_parts = []
    for i, (text, color) in enumerate(sub_segments):
        if i > 0:
            sub_parts.append(fonts.render(t.Type.META_SM, " · ", t.Color.TEXT_MUTED))
        sub_parts.append(fonts.render(t.Type.META_SM, text, color))
    sub_h = max((p.get_height() for p in sub_parts), default=0)
    stack_h = ttl.get_height() + 4 + sub_h
    my = (rect.h - stack_h) // 2
    body.blit(ttl, (mx, my))
    sx = mx
    sy = my + ttl.get_height() + 4
    for p in sub_parts:
        body.blit(p, (sx, sy)); sx += p.get_width()

    if focused:
        g, gpos = render.focus_glow(rect, radius)
        surf.blit(g, gpos)
        surf.blit(body, rect.topleft)
        surf.blit(render.gradient_border((rect.w, rect.h), radius), rect.topleft)
    else:
        surf.blit(body, rect.topleft)


def _section_rule(surf, y, label, trailing=None):
    """
    Uppercase eyebrow header with a gradient hairline filling the rest of the
    row, plus an optional muted trailing note. Matches U1's `.changes h3`.
    Returns y below the header.
    """
    lab = fonts.render(t.Type.EYEBROW, label, t.Color.TEXT_MUTED, upper=True)
    surf.blit(lab, (SAFE, y))
    x = SAFE + lab.get_width() + 8
    if trailing:
        tr = fonts.render(t.Type.META_SM, trailing, t.Color.TEXT_MUTED)
        surf.blit(tr, (x, y + (lab.get_height() - tr.get_height()) // 2))
        x += tr.get_width() + 10
    line_y = y + lab.get_height() // 2
    if x < STAGE_W - SAFE:
        # gradient fade from border-soft → transparent
        seg_w = STAGE_W - SAFE - x
        line = render.linear_gradient((seg_w, 1), t.Color.BORDER_SOFT,
                                      t.Color.BG_DEEP, 90).convert_alpha()
        surf.blit(line, (x, line_y))
    return y + lab.get_height() + 8


def _change_item(surf, x, y, max_w, *, tag, text):
    """
    One changelog row: a colored mono tag chip + wrapped prose with optional
    `**bold**` emphasis. Returns y below the item.
    """
    col = _TAG.get(tag, t.Color.TEXT_SECONDARY)
    chip_lbl = fonts.render(t.TextStyle(t.JETBRAINS_MONO, 9, "Regular", 0.4),
                            tag, col)
    cw, ch = chip_lbl.get_width() + 12, chip_lbl.get_height() + 4
    chip = pygame.Surface((cw, ch), pygame.SRCALPHA)
    pygame.draw.rect(chip, t.with_alpha(col, 0.12), (0, 0, cw, ch),
                     border_radius=4)
    chip.blit(chip_lbl, (6, 2))
    surf.blit(chip, (x, y))

    tx = x + cw + 8
    tw = max_w - cw - 8
    return _wrap_rich(surf, text, t.Type.BODY_SM, t.Color.TEXT_SECONDARY,
                      tx, y, tw, line_h=18, max_lines=2,
                      emphasis=t.Color.TEXT_PRIMARY)


def _wrap_rich(surf, text, style, color, x, y, max_w, *, line_h,
               max_lines=2, emphasis=None):
    """Greedy word-wrap honoring `**bold**` → emphasis spans."""
    words = text.split(" ")
    line = ""
    drawn = 0

    def flush(s):
        nonlocal y, drawn
        _render_rich_line(surf, s, style, color, x, y, emphasis)
        y += line_h
        drawn += 1

    for word in words:
        trial = (line + " " + word).strip()
        plain = trial.replace("**", "")
        if fonts.measure(style, plain)[0] <= max_w or not line:
            line = trial
        else:
            flush(line)
            line = word
            if drawn >= max_lines - 1:
                break
    if line and drawn < max_lines:
        flush(line)
    return y


def _render_rich_line(surf, text, style, color, x, y, emphasis):
    if emphasis is None or "**" not in text:
        surf.blit(fonts.render(style, text, color), (x, y))
        return
    cx = x
    for i, chunk in enumerate(text.split("**")):
        if not chunk:
            continue
        col = emphasis if (i % 2 == 1) else color
        g = fonts.render(style, chunk, col)
        surf.blit(g, (cx, y))
        cx += g.get_width()


def _auto_strip(surf, rect, *, note, options, selected):
    """
    Bottom meta strip: a mono note on the left + a small segmented control
    (off / manual / auto) on the right. Matches U1's `.footer-strip`.
    """
    body = pygame.Surface((rect.w, rect.h), pygame.SRCALPHA)
    pygame.draw.rect(body, t.with_alpha(t.Color.BG_DEEP, 0.5),
                     (0, 0, rect.w, rect.h), border_radius=10)
    pygame.draw.rect(body, t.Color.BORDER_SOFT, (0, 0, rect.w, rect.h),
                     width=1, border_radius=10)

    note_s = fonts.render(t.Type.META_SM, note, t.Color.TEXT_MUTED)
    body.blit(note_s, (12, (rect.h - note_s.get_height()) // 2))

    # segmented pill (right)
    seg_pad = 2
    opt_h = 22
    opt_surfs = []
    for i, opt in enumerate(options):
        col = t.Color.ACCENT_CYAN if i == selected else t.Color.TEXT_MUTED
        opt_surfs.append((opt, fonts.render(
            t.TextStyle(t.JETBRAINS_MONO, 10, "Regular", 0.0), opt, col)))
    opt_ws = [g.get_width() + 20 for _, g in opt_surfs]
    seg_w = sum(opt_ws) + seg_pad * 2
    seg_h = opt_h + seg_pad * 2
    seg = pygame.Surface((seg_w, seg_h), pygame.SRCALPHA)
    pygame.draw.rect(seg, t.Color.BG_MID, (0, 0, seg_w, seg_h),
                     border_radius=999)
    pygame.draw.rect(seg, t.Color.BORDER_SOFT, (0, 0, seg_w, seg_h),
                     width=1, border_radius=999)
    ox = seg_pad
    for i, (opt, g) in enumerate(opt_surfs):
        ow = opt_ws[i]
        if i == selected:
            pygame.draw.rect(seg, t.Color.SURFACE_ELEVATED,
                             (ox, seg_pad, ow, opt_h), border_radius=999)
        seg.blit(g, (ox + (ow - g.get_width()) // 2,
                     seg_pad + (opt_h - g.get_height()) // 2))
        ox += ow
    body.blit(seg, (rect.w - 12 - seg_w, (rect.h - seg_h) // 2))

    surf.blit(body, rect.topleft)


# ═════════════════════════════════════════════════════════════════════
# U1 — Updates available
# ═════════════════════════════════════════════════════════════════════

_HERO_Y = 56
_HERO_H = 70
_AVAIL_Y = _HERO_Y + _HERO_H + 8        # 134
_AVAIL_H = 66
_CHANGES_Y = _AVAIL_Y + _AVAIL_H + 12   # 212
_STRIP_H = 38
_STRIP_Y = FOOTER_Y - 8 - _STRIP_H      # 402


class U1Updates:
    """
    OTA home. Shows the installed version, the one available update (focused,
    A installs), and a NEW/FIX/IMP changelog. The auto-check preference is a
    segmented control at the bottom (default "auto" — PortMaster users hate
    manual chores).

    A installs the available update (coming-soon toast); X drills into the
    full changelog; Y switches release channel; B goes back.
    """
    name = "updates"

    def __init__(self, app):
        self.app = app
        self.installed = "1.3.0"
        self.available = "1.4.0"
        self.size = "28 MB"
        self.focus = 0          # the avail card is the only focusable element
        self.auto = 2           # 0=off 1=manual 2=auto

        # changelog — grouped NEW / FIX / IMP, consumer language.
        self.changes = [
            ("NEW", "**Hidden network** support — connect to networks that "
                    "don't broadcast a name."),
            ("NEW", "**Per-game power profile** auto-switching with 12 "
                    "default rules."),
            ("FIX", "Bluetooth audio crackle after sitting idle for a while."),
            ("FIX", "Stick drift fix wasn't applied at startup."),
            ("IMP", "Pairing range roughly doubled — about 25 m clear "
                    "line-of-sight."),
            ("IMP", "Battery health now charts a 30-day rolling trend."),
        ]

    # ── input ──────────────────────────────────────────────────────────
    def on_action(self, action):
        if action == "a":
            self.app.toast(f"Install {self.available} — coming soon")
        elif action == "x":
            self.app.toast("Full changelog — coming soon")
        elif action == "y":
            self.auto = (self.auto + 1) % 3
            self.app.toast(f"Auto-check: {['off', 'manual', 'auto'][self.auto]}")
        elif action == "b":
            self.app.pop()

    def update(self, dt_ms):
        pass

    # ── draw ───────────────────────────────────────────────────────────
    def draw(self, surf):
        w.status_bar(surf, title="Updates", back=True)
        self._draw_hero(surf)
        self._draw_avail(surf)
        self._draw_changes(surf)
        self._draw_strip(surf)
        w.footer_hints(surf, [("A", "install"), ("X", "changelog"),
                              ("Y", "channel"), ("B", "back")])

    def _draw_hero(self, surf):
        badge = _version_badge(self.installed, "STABLE")
        surf.blit(badge, (SAFE, _HERO_Y))

        mx = SAFE + badge.get_width() + 14
        ttl = fonts.render(t.TextStyle(t.SPACE_GROTESK_BOLD, 22, "Bold", -0.5),
                           f"AAhead Control · {self.installed}",
                           t.Color.TEXT_PRIMARY)
        surf.blit(ttl, (mx, _HERO_Y + 6))

        # meta line: ● installed · [stable] chip   (NO build hash here)
        my = _HERO_Y + 6 + ttl.get_height() + 6
        x = _status_dot_line(surf, mx, my, dot_color=t.Color.SUCCESS,
                             segments=[("installed", t.Color.SUCCESS)])
        chip = _pill_tag("stable", t.Color.TEXT_SECONDARY)
        surf.blit(chip, (x + 10, my + (12 - chip.get_height() // 2) - 6))

        # right column: "checked 2 min ago" + "● online"
        rx = STAGE_W - SAFE
        c1 = fonts.render(t.Type.META_SM, "checked 2 min ago", t.Color.TEXT_MUTED)
        surf.blit(c1, (rx - c1.get_width(), _HERO_Y + 6))
        on_lbl = fonts.render(t.Type.META_SM, "online", t.Color.SUCCESS)
        oy = _HERO_Y + 6 + c1.get_height() + 4
        surf.blit(on_lbl, (rx - on_lbl.get_width(), oy))
        dr = 3
        pygame.draw.circle(surf, t.Color.SUCCESS,
                           (rx - on_lbl.get_width() - 6 - dr, oy + on_lbl.get_height() // 2), dr)

        pygame.draw.line(surf, t.Color.BORDER_SOFT,
                         (SAFE, _HERO_Y + _HERO_H), (STAGE_W - SAFE, _HERO_Y + _HERO_H), 1)

    def _draw_avail(self, surf):
        rect = pygame.Rect(SAFE, _AVAIL_Y, BODY_W, _AVAIL_H)
        _avail_card(
            surf, rect,
            title=f"{self.available} is ready to install",
            sub_segments=[(self.size, t.Color.TEXT_SECONDARY),
                          ("no reboot needed", t.Color.ACCENT_CYAN),
                          ("signed by AAhead", t.Color.TEXT_SECONDARY)],
            cta_label="Install · A",
            focused=(self.focus == 0))

    def _draw_changes(self, surf):
        y = _section_rule(surf, _CHANGES_Y,
                          f"What's new in {self.available}",
                          trailing=f"· {len(self.changes)} changes")
        # 2-column grid
        col_gap = 14
        col_w = (BODY_W - col_gap) // 2
        col_x = [SAFE, SAFE + col_w + col_gap]
        row_h = 38
        for i, (tag, text) in enumerate(self.changes):
            r, c = divmod(i, 2)
            iy = y + r * row_h
            _change_item(surf, col_x[c], iy, col_w, tag=tag, text=text)

    def _draw_strip(self, surf):
        rect = pygame.Rect(SAFE, _STRIP_Y, BODY_W, _STRIP_H)
        _auto_strip(surf, rect, note="auto-check · every 6 h on Wi-Fi",
                    options=["off", "manual", "auto"], selected=self.auto)
