"""Minimal LANDR Live UI."""
from __future__ import annotations

import math
from dataclasses import dataclass, field

import numpy as np
import pygame

C_BG = (8, 6, 18)
C_PANEL = (18, 14, 36)
C_LINE = (40, 32, 64)
C_TEXT = (236, 232, 255)
C_DIM = (140, 132, 168)
C_IN = (86, 180, 233)
C_OUT = (230, 159, 0)
C_OK = (0, 158, 115)
C_WAIT = (213, 94, 0)
C_MASTER = (0, 114, 178)


@dataclass
class Frame:
    in_name: str
    out_name: str
    master_label: str
    bypass: bool
    engine_on: bool
    signal: bool
    in_db: float
    out_db: float
    pair: int
    err: str
    wave_in: np.ndarray = field(default_factory=lambda: np.zeros(0, np.float32))
    wave_out: np.ndarray = field(default_factory=lambda: np.zeros(0, np.float32))


class UI:
    def __init__(self) -> None:
        pygame.init()
        pygame.display.set_caption("LANDR Live")
        self.screen = pygame.display.set_mode((960, 580))
        self.clock = pygame.time.Clock()
        self.font = pygame.font.Font(None, 28)
        self.small = pygame.font.Font(None, 20)
        self.title = pygame.font.Font(None, 40)

    def _wave(
        self,
        rect: pygame.Rect,
        wave: np.ndarray,
        accent: tuple[int, int, int],
        title: str,
    ) -> None:
        s = self.screen
        pygame.draw.rect(s, C_PANEL, rect, border_radius=12)
        pygame.draw.rect(s, accent, (rect.x + 4, rect.y + 10, 4, rect.h - 20), border_radius=2)
        s.blit(self.small.render(title, True, accent), (rect.x + 16, rect.y + 8))
        left = rect.x + 16
        right = rect.x + rect.w - 16
        mid = rect.y + 28 + (rect.h - 36) // 2
        pygame.draw.line(s, C_LINE, (left, mid), (right, mid), 1)
        w = np.nan_to_num(np.asarray(wave, dtype=np.float32), copy=False)
        if w.size < 8:
            return
        n = min(160, w.size)
        idx = np.linspace(0, w.size - 1, n).astype(np.int32)
        pts = np.clip(w[idx], -1.0, 1.0)
        amp = float(rect.h - 44) * 0.46
        xs = np.linspace(left, right, n)
        points = [(int(xs[i]), int(mid + float(pts[i]) * amp)) for i in range(n)]
        if len(points) > 1:
            try:
                pygame.draw.lines(s, accent, False, points, 2)
            except Exception:
                pass

    def draw(self, f: Frame) -> None:
        s = self.screen
        s.fill(C_BG)
        s.blit(self.title.render("LANDR Live", True, C_TEXT), (24, 20))

        status = "Signal lock" if f.signal else "No signal"
        col = C_OK if f.signal else C_WAIT
        pygame.draw.rect(s, col, (720, 22, 216, 40), border_radius=8)
        s.blit(self.font.render(status, True, (12, 10, 20)), (736, 30))

        def card(x: int, num: str, head: str, sub: str, accent: tuple[int, int, int]) -> None:
            pygame.draw.rect(s, C_PANEL, (x, 80, 296, 88), border_radius=12)
            pygame.draw.rect(s, accent, (x + 4, 92, 4, 64), border_radius=2)
            s.blit(self.small.render(num, True, accent), (x + 16, 88))
            s.blit(self.font.render(head, True, C_TEXT), (x + 16, 108))
            s.blit(self.small.render(sub[:38], True, C_DIM), (x + 16, 136))

        pair = f" · USB {f.pair + 1}–{f.pair + 2}" if f.pair else ""
        card(24, "17", "Input", f.in_name + pair, C_IN)
        proc = "Passthrough" if f.bypass else f.master_label
        card(332, "19", "Process", proc, C_MASTER)
        card(640, "18", "Output", f.out_name, C_OUT)

        eng = "25 Engine on" if f.engine_on else "25 Engine off"
        s.blit(self.small.render(eng, True, C_OK if f.engine_on else C_DIM), (24, 188))
        s.blit(self.small.render(f"In {f.in_db:+.1f} dBFS   Out {f.out_db:+.1f} dBFS", True, C_TEXT), (24, 212))

        self._wave(pygame.Rect(24, 240, 912, 118), f.wave_in, C_IN, "21 IN WAVE")
        self._wave(pygame.Rect(24, 368, 912, 118), f.wave_out, C_OUT, "22 OUT WAVE")

        s.blit(
            self.small.render("B bypass   Q quit   djay output = DDJ-FLX", True, C_DIM),
            (24, 548),
        )
        if f.err:
            s.blit(self.small.render(f.err[:90], True, (244, 114, 182)), (24, 524))

        pygame.display.flip()
        self.clock.tick(40)

    def poll(self) -> str | None:
        for ev in pygame.event.get():
            if ev.type == pygame.QUIT:
                return "quit"
            if ev.type == pygame.KEYDOWN:
                if ev.key in (pygame.K_q, pygame.K_ESCAPE):
                    return "quit"
                if ev.key == pygame.K_b:
                    return "bypass"
        return None

    def close(self) -> None:
        pygame.quit()


def dbfs(x: float) -> float:
    return 20.0 * math.log10(max(x, 1e-12))
