//! UI-Overlay: **state-driven** und **immediate-mode**.
//!
//! [`layout`] ist eine reine Funktion des [`Session`]-Zustands — es hält
//! keinen eigenen mutierbaren Zustand (anders als irl3ds Widget-Bäume, die
//! neben dem Spielzustand desyncen konnten). Jeden Frame wird aus dem
//! aktuellen [`Mode`] neu gebaut: Dialog → Panel + Choice-Buttons, Menü →
//! Menü-Buttons, usw. Klicks erzeugen denselben Eingabe-String, den auch
//! CLI und stdin liefern, und laufen durch dasselbe `Session::exec` — ein
//! einziger Trichter in den State, kein UI-Backdoor.
//!
//! Koordinaten sind interne Pixel (das Overlay wird vor dem Blit gezeichnet,
//! teilt also den Lo-Fi-Look). Gezeichnet wird über [`crate::render::sprite`].

use crate::engine::tga::Image;
use crate::render::font::{self, Font, Fonts};
use crate::render::sprite::{SpriteBatch, SpriteVertex};

/// Feste Textur-Indizes im Sprite-Pass — Reihenfolge = `ui_textures` in
/// render::run. `WHITE` ist ein 1×1-Weiß-Texel für Solid-Fills (Farbe kommt
/// rein aus dem Tint), `FONT_*` die CP437-Glyph-Atlanten, `CURSORS` das
/// 2×2-Raster der 16×16-Cursor, `ORN` der 9-Slice-Ornamentrahmen.
pub(crate) const WHITE:    usize = 0;
pub(crate) const FONT_EGA: usize = 1;
pub(crate) const FONT_CGA: usize = 2;
pub(crate) const CURSORS:  usize = 3;
pub(crate) const ORN:      usize = 4;

/// Cursor-Indizes im 2×2-Raster (Pivot mittig je 16×16-Zelle).
pub(crate) const CUR_MOUSE: usize = 0; // Maus, normal
pub(crate) const CUR_HUD:   usize = 2; // First-Person-Pointer, normal
// Die „interagierbar"-Varianten (1, 3) kommen mit dem Hit-Testing in Stage 4.
#[allow(dead_code)] pub(crate) const CUR_MOUSE_INTERACT: usize = 1;
#[allow(dead_code)] pub(crate) const CUR_HUD_INTERACT:   usize = 3;

const CURSOR_PX: f32 = 16.0;
const ORN_CORNER: f32 = 8.0; // Eckgröße im Ornament-Atlas (und im Panel)

/// 1×1 weißes RGBA8-`Image` für getintete Solid-Flächen.
pub(crate) fn white_pixel() -> Image {
    Image { width: 1, height: 1, rgba: vec![255, 255, 255, 255] }
}

/// Weiß-auf-schwarz-Sprite (Font, Cursor, Ornament) → getintbare Maske:
/// rgb = weiß, alpha = Luminanz. So blendet der Sprite-Pass den schwarzen
/// Hintergrund weg und der Tint setzt die Farbe. Reine CPU-Nachbearbeitung
/// beim Laden.
pub(crate) fn key_luminance(img: &Image) -> Image {
    let mut rgba = img.rgba.clone();
    for px in rgba.chunks_mut(4) {
        let lum = px[0].max(px[1]).max(px[2]);
        px[0] = 255; px[1] = 255; px[2] = 255; px[3] = lum;
    }
    Image { width: img.width, height: img.height, rgba }
}

/// Cursor, wie ihn die App pro Frame an [`layout`] gibt.
pub(crate) struct Cursor {
    /// Position in internen Pixeln (über die Letterbox gemappt).
    pub(crate) pos:     [f32; 2],
    /// Maus gefangen (Flycam aktiv) → First-Person-Pointer mittig statt
    /// Maus-Cursor an `pos`.
    pub(crate) grabbed: bool,
}

/// Akkumulierte Overlay-Geometrie eines Frames, fertig für den Sprite-Pass.
#[derive(Default)]
pub(crate) struct Ui {
    pub(crate) verts:   Vec<SpriteVertex>,
    pub(crate) batches: Vec<SpriteBatch>,
}

impl Ui {
    /// Ein getintetes Quad. `rect` = (x, y, w, h) in internen Pixeln
    /// (oben-links), `uv` = (u0, v0, u1, v1) in 0..1. Aufeinanderfolgende
    /// Quads derselben Textur landen im selben Batch.
    pub(crate) fn quad(&mut self, tex: usize, rect: [f32; 4], uv: [f32; 4], color: [f32; 4]) {
        let [x, y, w, h] = rect;
        let [u0, v0, u1, v1] = uv;
        let base = self.verts.len() as u32;
        // Zwei Dreiecke (TL, BL, BR / TL, BR, TR).
        let tl = SpriteVertex { pos: [x,     y    ], uv: [u0, v0], color };
        let bl = SpriteVertex { pos: [x,     y + h], uv: [u0, v1], color };
        let br = SpriteVertex { pos: [x + w, y + h], uv: [u1, v1], color };
        let tr = SpriteVertex { pos: [x + w, y    ], uv: [u1, v0], color };
        self.verts.extend([tl, bl, br, tl, br, tr]);

        // An letzten Batch anhängen, wenn gleiche Textur, sonst neuen öffnen.
        match self.batches.last_mut() {
            Some(b) if b.texture == tex => b.count += 6,
            _ => self.batches.push(SpriteBatch { texture: tex, start: base, count: 6 }),
        }
    }

    /// Getintetes Solid-Rechteck (weißer Texel).
    pub(crate) fn fill(&mut self, rect: [f32; 4], color: [f32; 4]) {
        self.quad(WHITE, rect, [0.0, 0.0, 1.0, 1.0], color);
    }

    /// Eine Textzeile ab (x, y) oben-links in `font`, proportional gesetzt
    /// (variable Laufweite, leere Glyphen werden übersprungen). `\n` springt
    /// eine Zeile weiter. Gibt die x-Endposition zurück.
    pub(crate) fn text(&mut self, font: &Font, x: f32, y: f32, s: &str, color: [f32; 4]) -> f32 {
        let (mut cx, mut cy) = (x, y);
        for ch in s.chars() {
            if ch == '\n' { cx = x; cy += font.glyph_h; continue; }
            let code = font::cp437(ch);
            if let Some((uv, w)) = font.glyph_quad(code) {
                self.quad(font.tex(), [cx, cy, w, font.glyph_h], uv, color);
            }
            cx += font.advance(code);
        }
        cx
    }

    /// Ein Cursor-Sprite (16×16, Pivot mittig) an `pos`, getintet.
    pub(crate) fn cursor(&mut self, idx: usize, pos: [f32; 2], color: [f32; 4]) {
        let rect = [pos[0] - CURSOR_PX * 0.5, pos[1] - CURSOR_PX * 0.5, CURSOR_PX, CURSOR_PX];
        self.quad(CURSORS, rect, cursor_uv(idx), color);
    }

    /// Opakes Panel mit Ornamentrahmen. `rect` = Außenbounds. Erst der
    /// Hintergrund (Solid, getintet), dann der 9-Slice-Rahmen darüber: feste
    /// 8×8-Ecken, die Kanten dazwischen gekachelt bis zur nächsten Ecke.
    pub(crate) fn panel(&mut self, rect: [f32; 4], bg: [f32; 4], frame: [f32; 4]) {
        self.fill(rect, bg);
        self.ornament_frame(rect, frame);
    }

    fn ornament_frame(&mut self, rect: [f32; 4], color: [f32; 4]) {
        let [x, y, w, h] = rect;
        let c = ORN_CORNER;
        let a = ORN_ATLAS;
        // Vier Ecken (1:1 aus dem Atlas).
        self.quad(ORN, [x,         y,         c, c], orn_uv(0.0,    0.0,    c, c), color);
        self.quad(ORN, [x + w - c, y,         c, c], orn_uv(a - c,  0.0,    c, c), color);
        self.quad(ORN, [x,         y + h - c, c, c], orn_uv(0.0,    a - c,  c, c), color);
        self.quad(ORN, [x + w - c, y + h - c, c, c], orn_uv(a - c,  a - c,  c, c), color);
        // Kanten zwischen den Ecken kacheln.
        self.tile_h(x + c, x + w - c, y,         c, [c, 0.0],   color); // oben
        self.tile_h(x + c, x + w - c, y + h - c, c, [c, a - c], color); // unten
        self.tile_v(y + c, y + h - c, x,         c, [0.0, c],   color); // links
        self.tile_v(y + c, y + h - c, x + w - c, c, [a - c, c], color); // rechts
    }

    /// Horizontale Kante von `x0` bis `x1` bei `y` (Höhe `c`) mit dem
    /// `c`-breiten Atlas-Sample ab `src` (Pixel) kacheln; letzte Kachel wird
    /// passend beschnitten.
    fn tile_h(&mut self, x0: f32, x1: f32, y: f32, c: f32, src: [f32; 2], color: [f32; 4]) {
        let mut cx = x0;
        while cx < x1 - 0.5 {
            let seg = (x1 - cx).min(ORN_EDGE);
            self.quad(ORN, [cx, y, seg, c], orn_uv(src[0], src[1], seg, c), color);
            cx += seg;
        }
    }

    fn tile_v(&mut self, y0: f32, y1: f32, x: f32, c: f32, src: [f32; 2], color: [f32; 4]) {
        let mut cy = y0;
        while cy < y1 - 0.5 {
            let seg = (y1 - cy).min(ORN_EDGE);
            self.quad(ORN, [x, cy, c, seg], orn_uv(src[0], src[1], c, seg), color);
            cy += seg;
        }
    }
}

const ORN_ATLAS: f32 = 32.0; // Ornament-Atlasgröße (32×32)
const ORN_EDGE:  f32 = 16.0; // Kanten-Sample-Länge (zwischen den 8px-Ecken)

/// Atlas-Pixel-Rechteck → UV (0..1) im 32×32-Ornamentatlas.
fn orn_uv(x: f32, y: f32, w: f32, h: f32) -> [f32; 4] {
    [x / ORN_ATLAS, y / ORN_ATLAS, (x + w) / ORN_ATLAS, (y + h) / ORN_ATLAS]
}

/// UV-Rechteck einer Cursor-Zelle im 2×2-Raster (16×16 je Zelle, Atlas 32×32).
fn cursor_uv(idx: usize) -> [f32; 4] {
    let col = (idx % 2) as f32;
    let row = (idx / 2) as f32;
    let (x0, y0) = (col * CURSOR_PX, row * CURSOR_PX);
    [x0 / 32.0, y0 / 32.0, (x0 + CURSOR_PX) / 32.0, (y0 + CURSOR_PX) / 32.0]
}

/// Das Overlay für den aktuellen Frame bauen.
//
// Stage 3: Demo-Panel mit Ornamentrahmen + Text, plus der Cursor. Ab Stage 4
// verzweigt das hier nach `session.mode` (Dialog-Panel, Menü, HUD).
pub(crate) fn layout(internal: [f32; 2], fonts: &Fonts, cur: &Cursor) -> Ui {
    let mut ui = Ui::default();

    // Demo-Panel (opak) mit Ornamentrahmen + beide Fonts zum Vergleich.
    ui.panel([8.0, 8.0, 216.0, 58.0], [0.06, 0.05, 0.10, 1.0], [0.85, 0.75, 0.95, 1.0]);
    ui.text(&fonts.ega, 16.0, 16.0, "EGA 8x14 — Schöne Grüße!", [0.9, 0.9, 1.0, 1.0]);
    ui.text(&fonts.cga, 16.0, 34.0, "CGA 8x8 — abcABC 0123 äöüß", [0.6, 1.0, 0.7, 1.0]);

    // Cursor zuletzt → liegt über allem. Gefangen (Flycam) → HUD-Pointer
    // mittig; sonst Maus-Cursor an der Mausposition.
    if cur.grabbed {
        ui.cursor(CUR_HUD, [internal[0] * 0.5, internal[1] * 0.5], [1.0; 4]);
    } else {
        ui.cursor(CUR_MOUSE, cur.pos, [1.0; 4]);
    }

    ui
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn same_texture_quads_merge_into_one_batch() {
        let mut ui = Ui::default();
        ui.fill([0.0, 0.0, 10.0, 10.0], [1.0; 4]);
        ui.fill([20.0, 0.0, 10.0, 10.0], [1.0; 4]);
        assert_eq!(ui.verts.len(), 12);          // 2 Quads · 6 Verts
        assert_eq!(ui.batches.len(), 1);          // gleiche Textur → ein Batch
        assert_eq!(ui.batches[0].count, 12);
    }

    #[test]
    fn different_textures_split_batches() {
        let mut ui = Ui::default();
        ui.fill([0.0, 0.0, 10.0, 10.0], [1.0; 4]);          // WHITE
        ui.quad(1, [0.0, 0.0, 10.0, 10.0], [0.0, 0.0, 1.0, 1.0], [1.0; 4]); // Textur 1
        ui.fill([0.0, 0.0, 10.0, 10.0], [1.0; 4]);          // WHITE wieder
        assert_eq!(ui.batches.len(), 3);
        assert_eq!((ui.batches[0].start, ui.batches[1].start, ui.batches[2].start), (0, 6, 12));
    }

    #[test]
    fn key_luminance_white_opaque_black_transparent() {
        let img = Image { width: 2, height: 1, rgba: vec![255, 255, 255, 255,  0, 0, 0, 255] };
        let k = key_luminance(&img);
        assert_eq!(&k.rgba[0..4], &[255, 255, 255, 255]); // weiß → opak
        assert_eq!(&k.rgba[4..8], &[255, 255, 255, 0]);   // schwarz → transparent
    }

    #[test]
    fn cursor_uv_picks_quadrant() {
        // idx 3 = (col 1, row 1) → rechte untere 16×16-Zelle.
        assert_eq!(cursor_uv(3), [0.5, 0.5, 1.0, 1.0]);
        assert_eq!(cursor_uv(0), [0.0, 0.0, 0.5, 0.5]);
    }

    #[test]
    fn ornament_frame_tiles_edges_and_places_corners() {
        let mut ui = Ui::default();
        // 48×48-Panel: Kanten je 48-16=32 px → 32/16 = 2 Kacheln pro Seite.
        // Erwartung: 4 Ecken + 4·2 Kanten = 12 Ornament-Quads (nach dem fill).
        ui.panel([0.0, 0.0, 48.0, 48.0], [0.0; 4], [1.0; 4]);
        let orn_quads = ui.verts.len() / 6 - 1; // minus das Hintergrund-fill
        assert_eq!(orn_quads, 12);
    }
}
