"""
Display preset engine — the data + emit-side of manifesto §12
("игра как задумана — но лучше"). One base preset per system, auto-applied at
launch time via a RetroArch override config that the launch hook (slice P1.B)
appends with `--appendconfig`.

The R36S has ONE built-in 640×480 panel and NO video output, so there is no
"output target" to choose (an earlier model had an LCD/TV axis — removed; the
TV column emitted byte-identical config to LCD anyway). What the user picks
instead is a single global **look** — how much "old screen" character to apply
— and the engine maps it onto each system's base preset:

  LOOK_CRISP  — no shader anywhere; clean sharp integer pixels ("pixel-correct").
  LOOK_RETRO  — the authentic per-system treatment (default): CRT/composite for
                home consoles, the real LCD grid for handhelds, off for PSP.

§12.1 — the invariants that hold regardless of look:
  - Only integer scale (no 2xSaI / hq-x / xBR interpolation).
  - Right PAR/AR per system (no stretch-to-fill) — aspect is ALWAYS emitted.
  - Handhelds get THEIR LCD model under Retro, not CRT (DMG dot, GBC, GBA).
  - One forced preset per launch — no in-game shader picker on the gameplay path.

Shader paths reference the RetroArch common-shaders pack (`shaders_glsl/...`)
that dArkOS-RE ships. If a particular shader file is missing on a given build,
RetroArch logs a load failure and runs unshaded — the preset still wins on
aspect + integer. We never fail-loud over a missing shader on the launcher
path; "render unshaded" is better than "won't launch".

This module is the PURE data + emit layer. The Display screen UI reads
`preset_for(...)` / `LOOKS` for display; the launch hook (slice P1.B) calls
`cfg_for(system, look)` to get the override text. Neither talks to disk here.
"""

from __future__ import annotations

from dataclasses import dataclass


# ── looks (global presentation style) ────────────────────────────────
LOOK_CRISP = "crisp"      # no shader — sharp, pixel-correct integer scaling
LOOK_RETRO = "retro"      # authentic per-system treatment (§12) — the default
LOOKS = (LOOK_CRISP, LOOK_RETRO)
LOOK_DEFAULT = LOOK_RETRO


# ── RetroArch aspect_ratio_index values ──────────────────────────────
# From the canonical `enum aspect_ratio` in RA's gfx/video_defines.h.
# (Verified via RA master on 2026-05-27 — the enum is stable, but several
# online guides quote OLDER value tables where indices were off by 3 or 4
# because new ratios were inserted in front. Always cross-check against
# video_defines.h before bumping.)
#
#   0  = 4:3       1  = 16:9       2  = 16:10
#   3  = 16:15     4  = 21:9       5  = 1:1
#   6  = 2:1       7  = 3:2        8  = 3:4
#   9  = 4:1       10 = 4:4        11 = 5:4
#   12 = 6:5       13 = 7:9        14 = 8:3
#   15 = 8:7       16 = 19:12      17 = 19:14
#   18 = 30:17     19 = 32:9       20 = Config
#   21 = 1:1 PAR (SQUARE)          22 = Core Provided  ← what we want
#   23 = Custom    24 = Full
#
# Previous code had ASPECT_CORE = 19, which is ULTRAWIDE 32:9 — the
# on-device symptom was "странный аспект ратио типа 21:9". The correct
# value is 22 (ASPECT_RATIO_CORE in the enum).
ASPECT_4_3 = 0
ASPECT_16_9 = 1
ASPECT_1_1 = 5
ASPECT_3_2 = 7
ASPECT_8_7 = 15
ASPECT_CORE = 22                              # fallback for unmapped systems


# ── shader paths (relative to RetroArch shader root) ─────────────────
# Calibrated against the on-card `_SETUP_INSTRUCTIONS.txt` (dArkOS-RE / R36S,
# G80CA-MB V1.2-20250422). These are shaders the user has verified ship on
# their device; safer than guessing from common-shaders pack names.
#
#   crt-pi          — pi-class CRT, light enough for Mali-400. User has it
#                     set as global default (SETUP step 1). Used here for
#                     every CRT system under the Retro look.
#   zfast_composite — SETUP step 4 recommends for MD/32X: emulates the
#                     composite blur that Sonic's waterfall dithering needs.
#   lcd3x           — SETUP step 4: GB/GBC DMG-style grid.
#   gba-color       — SETUP step 4: GBA LCD + gamma.
SHADER_CRT_PI = "shaders_glsl/crt/crt-pi.glslp"
SHADER_CRT_COMPOSITE = "shaders_glsl/crt/zfast_crt_composite.glslp"
SHADER_HANDHELD_LCD3X = "shaders_glsl/handheld/lcd3x.glslp"
SHADER_HANDHELD_GBA = "shaders_glsl/handheld/gba-color.glslp"


@dataclass(frozen=True)
class Preset:
    """One system's BASE presentation preset (the authentic / Retro look).

    Honest fields only — what we actually emit to RetroArch:
        shader_path     → video_shader / video_shader_enable=true (Retro)
        disable_shader  → this is a native-LCD system (PSP): no CRT in ANY
                          look; under Retro/Crisp it always emits
                          video_shader_enable=false
        aspect_index    → aspect_ratio_index (ALWAYS emitted, every look)
        integer_scale   → video_scale_integer (ALWAYS emitted, every look)
    `notes` is for the Display screen and debug — never written to RetroArch.

    The look transform in `cfg_for()` decides the final shader:
      Retro → this preset's shader_path (or disable for native-LCD)
      Crisp → shaders off everywhere
    """
    system: str
    shader_path: str | None           # the authentic (Retro) shader, or None
    aspect_index: int = ASPECT_CORE
    integer_scale: bool = True
    disable_shader: bool = False      # True → native-LCD (PSP): never a CRT
    notes: str = ""


# ── the matrix ───────────────────────────────────────────────────────
# Keyed by system for O(1) lookup. One base preset each — the authentic look.
_PRESETS: dict[str, Preset] = {}


def _add(p: Preset) -> None:
    _PRESETS[p.system] = p


# Home consoles — CRT systems. All use 4:3 (their authentic TV display AR)
# instead of Core Provided, because some cores emit weird stretched 16:9 for
# certain games (especially PS1 widescreen-hack). 4:3 + integer scale + the
# right per-system shader = pixel-perfect output.
_add(Preset("NES", SHADER_CRT_PI, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + crt-pi (NTSC TV authentic)"))
_add(Preset("SNES", SHADER_CRT_PI, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + crt-pi"))
_add(Preset("Genesis", SHADER_CRT_COMPOSITE, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + zfast_crt_composite (Sonic waterfall dithering)"))

# Sega add-ons share Genesis's display profile — same 320×224 framebuffer
# family (Sega CD adds CD audio + larger ROM; 32X adds an extra Hitachi SH-2
# framebuffer but still outputs to the Genesis's video pipeline). Composite
# shader matches the "watch this on a 1994 TV through RF" aesthetic.
_add(Preset("Sega CD", SHADER_CRT_COMPOSITE, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + zfast_crt_composite (matches Genesis pipeline)"))
_add(Preset("Sega 32X", SHADER_CRT_COMPOSITE, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + zfast_crt_composite (32X uses Genesis video out)"))

_add(Preset("Master System", SHADER_CRT_PI, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + crt-pi"))
_add(Preset("PC Engine", SHADER_CRT_PI, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + crt-pi"))
_add(Preset("N64", SHADER_CRT_PI, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + crt-pi (VI-blur emulated in-core)"))

# Neo Geo / arcade — most arcade boards output 4:3. Vertical shooters need
# per-game AR (P1.E follow-up — likely 3:4 rotated).
_add(Preset("Neo Geo", SHADER_CRT_PI, aspect_index=ASPECT_4_3,
            notes="4:3 + integer + crt-pi (arcade CRT)"))

# PS1 — also 4:3 native. A few games (Gran Turismo 2, etc) emit 16:9
# widescreen mode flag, but the safe default is 4:3 — the override here pins
# it so a widescreen-hack ROM doesn't accidentally stretch.
_add(Preset("PS1", SHADER_CRT_PI, aspect_index=ASPECT_4_3,
            notes="4:3 + integer/sharp + crt-pi"))

# PSP — native 480×272 LCD ≈ 16:9 (1.76). Set 16:9 explicitly + mark as a
# native-LCD system: shader is disabled in EVERY look (§12.2: native LCD, no
# CRT — a scanline shader on PSP looks wrong).
_add(Preset("PSP", None, aspect_index=ASPECT_16_9, disable_shader=True,
            notes="16:9 native (480×272), shader OFF in every look (native LCD)"))

# Handhelds — per §12.3: GB/GBC/GBA keep their native LCD model under Retro.
# The aspect ratio is the LCD's actual shape (square pixels = 1:1 PAR, NOT
# stretched to 4:3). On the device's 4:3 panel this letterboxes the content
# top/bottom and preserves the original aspect.
#   GB / GBC: 160×144 = 10:9 native, square pixels  → ASPECT_1_1
#   GBA:      240×160 = 3:2 native, square pixels   → ASPECT_3_2
# SETUP step 4 shader names already verified on the card.
_add(Preset("Game Boy", SHADER_HANDHELD_LCD3X, aspect_index=ASPECT_1_1,
            notes="1:1 PAR + lcd3x (DMG dot-matrix on 10:9 native LCD)"))
_add(Preset("Game Boy Color", SHADER_HANDHELD_LCD3X, aspect_index=ASPECT_1_1,
            notes="1:1 PAR + lcd3x (GBC LCD grid)"))
_add(Preset("GBA", SHADER_HANDHELD_GBA, aspect_index=ASPECT_3_2,
            notes="3:2 native (240×160) + gba-color LCD + gamma"))

# Game Gear — handheld with a 160×144 backlit LCD, same shape as GB so 1:1 PAR
# is right (NOT stretched to 4:3 like the original Master System it ports games
# from). Uses crt-pi as its Retro shader since SETUP didn't provide a Game
# Gear-specific entry.
_add(Preset("Game Gear", SHADER_CRT_PI, aspect_index=ASPECT_1_1,
            notes="1:1 PAR + crt-pi (handheld LCD; verified shader fallback)"))


# ── public API ───────────────────────────────────────────────────────
def preset_for(system: str) -> Preset | None:
    """Return the system's base preset, or None if it isn't in the matrix.
    The launch hook treats None as 'apply the safe default' rather than
    refusing to launch — see `cfg_for()`."""
    return _PRESETS.get(system)


def _shader_lines(p: Preset | None, look: str) -> list[str]:
    """The shader keys for a (preset, look) pair — the heart of the look model.

    Three-state RetroArch shader contract:
      ['video_shader_enable = "true"', 'video_shader = "<path>"']  → this wins
      ['video_shader_enable = "false"']                            → suppress
                                                  any global shader for this core
      []                                          → no shader keys (user's global
                                                    preset wins — Retro/unknown)
    """
    if look == LOOK_CRISP:
        # Sharp everywhere: actively suppress any shader (incl. user's global).
        return ['video_shader_enable = "false"']

    # LOOK_RETRO — the authentic per-system base (the original 3-state model).
    if p is None:
        return []                                  # unknown → user's global wins
    if p.shader_path:
        return ['video_shader_enable = "true"', f'video_shader = "{p.shader_path}"']
    if p.disable_shader:
        return ['video_shader_enable = "false"']
    return []


def cfg_for(system: str, look: str = LOOK_DEFAULT) -> str:
    """Return a RetroArch override-config snippet for (system, look).

    Appended to RetroArch's main config via `--appendconfig` at launch.
    ALWAYS sets `aspect_ratio_index` and `video_scale_integer` (manifesto
    §12.1 baseline) — regardless of look, games never stretch/blur. The
    shader keys are decided by `_shader_lines()` per the look (see its docstring).

    Unknown system (not in matrix) → safe default: integer + core-provided AR.
    Under Retro it adds no shader override (the user's global wins); under
    Crisp it suppresses shaders (sharp). Launcher always proceeds.
    """
    if look not in LOOKS:
        look = LOOK_DEFAULT
    p = preset_for(system)
    integer = "true" if (p.integer_scale if p else True) else "false"
    aspect = p.aspect_index if p else ASPECT_CORE
    lines = [
        "# AAhead preset overlay — generated, do not hand-edit",
        f"# system: {system}",
        f"# look: {look}",
        f'aspect_ratio_index = "{aspect}"',
        f'video_scale_integer = "{integer}"',
    ]
    lines += _shader_lines(p, look)
    return "\n".join(lines) + "\n"
