"""
S1/S2 — Search. QWERTY on-screen keyboard + live results.

A gamepad keyboard (D-pad to move, A to type) filters the catalog live. Below
the keyboard a 3:4 box strip shows the matches (or the Clerk's picks for an
empty query, or the closest-in-spirit redirect when nothing matches — S2).

Layout (v3): query field on top, big legible QWERTY keyboard, result strip
under it. Focus area = keyboard (default) or results strip.
"""

from __future__ import annotations

import pygame

from ..design import tokens as t
from ..shared import fonts, render, icons
from ..widgets import chrome, components as c, images, nav

BODY_TOP = t.Space.STATUS_BAR_HEIGHT
BODY_BOT = t.VIEWPORT_HEIGHT - t.Space.FOOTER_HINT_HEIGHT
SAFE = t.Space.SAFE_AREA

QUERY = pygame.Rect(14, 34, 612, 38)
KB_X, KB_TOP, KB_W, KB_H = 14, 84, 612, 200
ROW_GAP, KEY_GAP = 4, 4
ROW_H = (KB_H - 3 * ROW_GAP) // 4                # 47
STRIP_HEAD_Y = 294
STRIP_Y = STRIP_HEAD_Y + 22
TILE_W, TILE_H = 72, 96                          # 3:4

KB_ALPHA = [
    list("QWERTYUIOP"),
    list("ASDFGHJKL'"),
    list("ZXCVBNM,.-"),
    ["123", "SPACE", "DEL", "ENTER"],
]
KB_NUM = [
    list("1234567890"),
    list("@#&_-+()/:"),
    list("!?.,'\";*~="),
    ["ABC", "SPACE", "DEL", "ENTER"],
]
WEIGHT = {"SPACE": 5.0, "123": 1.4, "ABC": 1.4, "DEL": 1.6, "ENTER": 1.6}
KEY_STYLE = t.TextStyle(t.SPACE_GROTESK_SEMIBOLD, 18, "SemiBold", -0.2)
KEY_SP_STYLE = t.TextStyle(t.INTER_MEDIUM, 12, "Medium", 0.15)
NAME_STYLE = t.TextStyle(t.INTER_SEMIBOLD, 11, "SemiBold", -0.05)


class SearchScreen:
    name = "search"

    def __init__(self, app):
        self.app = app
        self.nav = nav.NavBar("search")
        self.query = ""
        self.layer = "abc"
        self.area = "kb"        # 'kb' | 'results'
        self.committed = False  # S2: settled no-results redirect view
        self.kr = 0
        self.kc = 0
        self.rc = 0
        self._recompute()

    def _rows(self):
        return KB_ALPHA if self.layer == "abc" else KB_NUM

    def _recompute(self):
        cat = self.app.catalog
        if self.query.strip():
            self.results = cat.search(self.query)
            self.suggesting = not self.results
            self.redirect = (cat.collection_games("essentials")[:6]
                             or cat.games[:6]) if self.suggesting else []
        else:
            self.results = cat.collection_games("essentials")[:12] or cat.games[:12]
            self.suggesting = False
            self.redirect = []
        self.rc = 0
        self.committed = False

    def _strip(self):
        return self.redirect if self.suggesting else self.results

    # ── input ────────────────────────────────────────────────────────
    def on_action(self, action):
        if self.nav.handle(self.app, action):
            return
        if self.committed:
            self._s2_input(action)
            return
        if action == "b":
            self.app.go_tab("home")
        elif action == "x":
            self.query = ""; self._recompute()
        elif action == "y":
            self.app.open_addgames()
        elif action in ("azprev", "aznext"):       # right stick → fast scroll the results
            strip = self._strip()
            if strip:
                self.area = "results"
                step = 5 if action == "aznext" else -5
                self.rc = max(0, min(len(strip) - 1, self.rc + step))
        elif self.area == "results":
            self._results_input(action)
        else:
            self._kb_input(action)

    def _s2_input(self, action):
        picks = self.redirect[:3]
        if action == "up":
            self.nav.focus()
        elif action == "left":
            self.rc = max(0, self.rc - 1)
        elif action == "right":
            self.rc = min(len(picks) - 1, self.rc + 1)
        elif action == "a" and picks:
            self.app.open_game(picks[self.rc]["id"])
        elif action == "b":                 # clear & try again
            self.query = ""; self._recompute()
        elif action == "y":                 # type more (keep query)
            self.committed = False

    def _results_input(self, action):
        strip = self._strip()
        if action == "up":
            self.area = "kb"; self.kr = len(self._rows()) - 1
        elif action == "left":
            self.rc = max(0, self.rc - 1)
        elif action == "right":
            self.rc = min(len(strip) - 1, self.rc + 1)
        elif action == "a" and strip:
            self.app.open_game(strip[self.rc]["id"])

    def _kb_input(self, action):
        rows = self._rows()
        if action == "up":
            if self.kr == 0:
                self.nav.focus()
            else:
                self.kr -= 1
                self.kc = min(self.kc, len(rows[self.kr]) - 1)
        elif action == "down":
            if self.kr < len(rows) - 1:
                self.kr += 1
                self.kc = min(self.kc, len(rows[self.kr]) - 1)
            elif self.results:
                self.area = "results"; self.rc = 0
            elif self.suggesting:
                self.committed = True; self.rc = 0
        elif action == "left":
            self.kc = max(0, self.kc - 1)
        elif action == "right":
            self.kc = min(len(rows[self.kr]) - 1, self.kc + 1)
        elif action == "a":
            self._press(rows[self.kr][self.kc])

    def _press(self, key):
        if key == "SPACE":
            self.query += " "
        elif key == "DEL":
            self.query = self.query[:-1]
        elif key == "ENTER":
            if self.results:
                self.area = "results"; self.rc = 0
            elif self.suggesting:
                self.committed = True; self.rc = 0
            return
        elif key == "123":
            self.layer = "num"; return
        elif key == "ABC":
            self.layer = "abc"; return
        else:
            self.query += key.lower() if key.isalpha() else key
        self._recompute()

    def update(self, dt_ms):
        pass

    # ── draw ─────────────────────────────────────────────────────────
    def draw(self, surf):
        self.nav.draw(surf, app=self.app)
        navf = self.nav.focused
        if self.committed:
            self._draw_s2(surf, navf)
            chrome.footer_hints(surf, [("A", "Open"), ("B", "Clear"), ("Y", "Type more")])
            return
        self._draw_field(surf)
        self._draw_keyboard(surf, navf)
        self._draw_strip(surf, navf)
        chrome.footer_hints(surf, [("A", "Type"), ("B", "Back"), ("X", "Clear")])

    def _draw_field(self, surf, right_text=None):
        body = pygame.Surface((QUERY.w, QUERY.h), pygame.SRCALPHA)
        pygame.draw.rect(body, t.with_alpha(t.Color.SURFACE_BASE, 0.55),
                         (0, 0, QUERY.w, QUERY.h), border_radius=9)
        pygame.draw.rect(body, t.Color.BORDER_SOFT, (0, 0, QUERY.w, QUERY.h),
                         width=1, border_radius=9)
        gi = icons.icon("ph-magnifying-glass", 18, t.Color.TEXT_MUTED)
        body.blit(gi, (14, (QUERY.h - gi.get_height()) // 2))
        tx = 14 + gi.get_width() + 12
        rt = right_text or f"across {len(self.app.catalog.games)} games"
        src = fonts.render(t.Type.TILE_SUB, rt, t.Color.TEXT_MUTED)
        body.blit(src, (QUERY.w - 14 - src.get_width(), (QUERY.h - src.get_height()) // 2))
        # query must not run under the right-side count → clip to the tail
        avail = QUERY.w - tx - src.get_width() - 12 - 14
        if self.query:
            disp = self.query + ("" if self.committed else "▏")
            col = t.Color.TEXT_PRIMARY
            while len(disp) > 1 and fonts.measure(t.Type.BODY_STRONG, disp)[0] > avail:
                disp = disp[1:]                       # drop from the head, keep what you type
            if disp != self.query + ("" if self.committed else "▏"):
                disp = "…" + disp[1:]
        else:
            disp, col = "Start typing…▏", t.Color.TEXT_MUTED
        ts = fonts.render(t.Type.BODY_STRONG, disp, col)
        body.blit(ts, (tx, (QUERY.h - ts.get_height()) // 2))
        surf.blit(body, QUERY.topleft)

    # ── S2 — settled no-results redirect ──
    def _draw_s2(self, surf, navf):
        self._draw_field(surf, "B · clear")
        x, y = 14, 90
        c.clerk_eyebrow(surf, x, y, "The Clerk")
        y += 22
        lead = (f"“{self.query.strip()}” isn't on this device — "
                "it's not part of this retro shelf.")
        y = c.draw_paragraph(surf, t.Type.ROW_TITLE, lead, x, y, t.VIEWPORT_WIDTH - 28,
                             t.Color.TEXT_PRIMARY, line_h=22, max_lines=2)
        y += 6
        sub = "But if you wanted something with that kind of weight, I have a few."
        y = c.draw_paragraph(surf, t.Type.CLERK, sub, x, y, t.VIEWPORT_WIDTH - 28,
                             t.Color.TEXT_SECONDARY, line_h=18, max_lines=2)
        y += 14
        c.rail_header(surf, x, y, "Closest in spirit on this device",
                      "A · open the first", max_w=t.VIEWPORT_WIDTH - 28)
        y += 26
        picks = self.redirect[:3]
        active = not navf
        for i, g in enumerate(picks):
            self._pick_row(surf, x, y + i * 64, g, focused=(active and i == self.rc))

    def _pick_row(self, surf, x, y, g, *, focused):
        cat = self.app.catalog
        AW, AH = 96, 54
        img = images.cover(cat.asset_path(g, "hero"), AW, AH, radius=6, mode="cover")
        ar = pygame.Rect(x, y, AW, AH)
        if focused:
            glow, gpos = render.focus_glow(ar, 6); surf.blit(glow, gpos)
        surf.blit(img, ar.topleft)
        if focused:
            surf.blit(render.gradient_border((AW, AH), 6), ar.topleft)
        tx = x + AW + 12
        nm = fonts.render(t.Type.ROW_TITLE, c._ellipsize(t.Type.ROW_TITLE, g["title"], 330),
                          t.Color.TEXT_PRIMARY if focused else t.Color.TEXT_SECONDARY)
        surf.blit(nm, (tx, y))
        tag = fonts.render(t.Type.TILE_SUB, f"{g['system']} · {g.get('year', '')}",
                           t.Color.TEXT_MUTED)
        surf.blit(tag, (tx + nm.get_width() + 10, y + 4))
        why = c.first_sentence(g.get("description", ""), cap=96)
        c.draw_paragraph(surf, t.Type.CLERK, why, tx, y + 22,
                         t.VIEWPORT_WIDTH - 28 - (tx - x), t.Color.TEXT_SECONDARY,
                         line_h=17, max_lines=2)

    def _row_rects(self, row):
        total = sum(WEIGHT.get(k, 1.0) for k in row)
        avail = KB_W - KEY_GAP * (len(row) - 1)
        out, x = [], KB_X
        for k in row:
            w = int(avail * WEIGHT.get(k, 1.0) / total)
            out.append((k, x, w))
            x += w + KEY_GAP
        return out

    def _draw_keyboard(self, surf, navf):
        active = (self.area == "kb" and not navf)
        for r, row in enumerate(self._rows()):
            y = KB_TOP + r * (ROW_H + ROW_GAP)
            for ci, (k, x, w) in enumerate(self._row_rects(row)):
                self._key(surf, pygame.Rect(x, y, w, ROW_H), k,
                          focused=(active and r == self.kr and ci == self.kc))

    def _key(self, surf, rect, key, focused):
        special = key in WEIGHT or key in ("123", "ABC")
        body = pygame.Surface((rect.w, rect.h), pygame.SRCALPHA)
        fill = t.with_alpha(t.Color.TEXT_PRIMARY, 0.025 if special else 0.045)
        pygame.draw.rect(body, fill, (0, 0, rect.w, rect.h), border_radius=7)
        if not focused:
            pygame.draw.rect(body, t.Color.BORDER_SOFT, (0, 0, rect.w, rect.h),
                             width=1, border_radius=7)
        label = {"SPACE": "space", "DEL": "DEL", "ENTER": "done"}.get(key, key)
        if special:
            ks = fonts.render(KEY_SP_STYLE, label, t.Color.TEXT_SECONDARY)
        else:
            ks = fonts.render(KEY_STYLE, label,
                              t.Color.TEXT_PRIMARY if focused else t.Color.TEXT_SECONDARY)
        body.blit(ks, ((rect.w - ks.get_width()) // 2, (rect.h - ks.get_height()) // 2))
        if focused:
            glow, gpos = render.focus_glow(rect, 7); surf.blit(glow, gpos)
        surf.blit(body, rect.topleft)
        if focused:
            surf.blit(render.gradient_border((rect.w, rect.h), 7), rect.topleft)

    def _draw_strip(self, surf, navf):
        strip = self._strip()
        if self.suggesting:
            head, aside = "Not on this device — closest in spirit", "Y · open source"
        elif self.query.strip():
            n = len(self.results)
            head = f"{n} result" + ("" if n == 1 else "s")
            aside = "Y · open source"
        else:
            head, aside = "Try one of these tonight", "Y · open source"
        c.rail_header(surf, 14, STRIP_HEAD_Y, head, aside,
                      max_w=t.VIEWPORT_WIDTH - 28)
        active = (self.area == "results" and not navf)
        view = (t.VIEWPORT_WIDTH - 14) // (TILE_W + 8) + 1
        start = max(0, min(self.rc - view + 2, max(0, len(strip) - view))) if active else 0
        for k in range(start, min(start + view + 1, len(strip))):
            g = strip[k]
            x = 14 + (k - start) * (TILE_W + 8)
            focused = (active and k == self.rc)
            c.poster_tile(surf, pygame.Rect(x, STRIP_Y, TILE_W, TILE_H),
                          path=self.app.catalog.asset_path(g, "box"), name=g["title"],
                          focused=focused, dim=(active and not focused))
        if not strip:
            ns = fonts.render(t.Type.BODY, "No matches — edit your search above",
                              t.Color.TEXT_MUTED)
            surf.blit(ns, (14, STRIP_Y + 20))
