//! Snapshot des Bildschirms als vergleichbarer Text.
//!
//! Grundlage des Kompatibilitäts-Korpus (Entscheidung 2026-09-03, D3):
//! ein Byte-Strom könnte `LOCATE`/`COLOR` nicht abbilden, ein voller
//! 80×25-Abzug wäre für ein `PRINT "hi"` fast nur Rauschen. Der Snapshot
//! ist deshalb auf die belegte Fläche getrimmt — nachgestellte Leerzeichen,
//! die `PRINT` selbst ausgibt, gehören dazu.
//!
//! Die Attributebene entsteht nur, wenn das Programm überhaupt Farben
//! gesetzt hat; sonst wäre sie eine Fläche identischer Werte.

use crate::screen::{Cell, TextScreen};

/// Trennzeile vor der Attributebene.
pub const ATTRIBUT_MARKE: &str = "--- Attribute (Vordergrund/Hintergrund je Zelle, hex) ---";

/// Standardattribut eines unberührten Bildschirms.
fn ist_standard(c: &Cell) -> bool {
    c.fg == Cell::default().fg && c.bg == Cell::default().bg
}

/// Enthält die belegte Fläche eine vom Standard abweichende Farbe?
pub fn hat_farben(screen: &TextScreen) -> bool {
    (1..=screen.belegte_zeilen())
        .any(|r| (1..=screen.belegte_breite(r)).any(|c| !ist_standard(&screen.cell(r, c))))
}

/// Textbild der belegten Fläche, je Zeile bis zu ihrer eigenen belegten
/// Breite. Leerer String, wenn nichts ausgegeben wurde.
pub fn text(screen: &TextScreen) -> String {
    let mut out = String::new();
    for r in 1..=screen.belegte_zeilen() {
        for c in 1..=screen.belegte_breite(r) {
            // Fortsetzungszellen gehören zum Zeichen davor und werden nicht
            // eigens ausgegeben — sonst stünde hinter jedem breiten Zeichen
            // ein Leerzeichen und die Spalten der Datei liefen auseinander.
            let z = screen.cell(r, c);
            if !z.fortsetzung {
                out.push(z.ch);
            }
        }
        out.push('\n');
    }
    out
}

/// Attributebene: je Zelle zwei Hexziffern (Vordergrund, Hintergrund),
/// zeilenweise gleich breit wie das Textbild.
pub fn attribute(screen: &TextScreen) -> String {
    let mut out = String::new();
    for r in 1..=screen.belegte_zeilen() {
        for c in 1..=screen.belegte_breite(r) {
            let cell = screen.cell(r, c);
            if cell.fortsetzung {
                continue; // gehört zum Zeichen davor (siehe `text`)
            }
            out.push(char::from_digit(cell.fg as u32, 16).unwrap_or('?'));
            out.push(char::from_digit(cell.bg as u32, 16).unwrap_or('?'));
        }
        out.push('\n');
    }
    out
}

/// Vollständiger Snapshot: Textbild, bei gesetzten Farben gefolgt von
/// Marke und Attributebene.
pub fn snapshot(screen: &TextScreen) -> String {
    let mut s = text(screen);
    if hat_farben(screen) {
        s.push_str(ATTRIBUT_MARKE);
        s.push('\n');
        s.push_str(&attribute(screen));
    }
    s
}

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

    #[test]
    fn getrimmt_auf_die_belegte_flaeche() {
        let mut s = TextScreen::new();
        s.print("hi\n");
        // Zwei Zeichen in Zeile 1 — kein 80×25-Abzug.
        assert_eq!(snapshot(&s), "hi\n");
    }

    #[test]
    fn nachgestellte_leerzeichen_bleiben_erhalten() {
        let mut s = TextScreen::new();
        s.print(" 1  2  3 "); // wie PRINT 1; 2; 3
        assert_eq!(snapshot(&s), " 1  2  3 \n");
    }

    #[test]
    fn ohne_farben_keine_attributebene() {
        let mut s = TextScreen::new();
        s.print("abc");
        assert!(!snapshot(&s).contains(ATTRIBUT_MARKE));
    }

    #[test]
    fn mit_farben_kommt_die_attributebene() {
        let mut s = TextScreen::new();
        s.print("a");
        s.set_color(14, 1);
        s.print("b");
        let snap = snapshot(&s);
        assert!(snap.starts_with("ab\n"));
        assert!(snap.contains(ATTRIBUT_MARKE));
        // Zelle 1 Standard (70), Zelle 2 gelb auf blau (e1)
        assert!(snap.ends_with("70e1\n"), "{snap:?}");
    }

    #[test]
    fn locate_erweitert_die_flaeche() {
        let mut s = TextScreen::new();
        s.locate(3, 5).unwrap();
        s.print("x");
        let snap = snapshot(&s);
        let zeilen: Vec<&str> = snap.lines().collect();
        assert_eq!(zeilen.len(), 3);
        assert_eq!(zeilen[2], "    x");
        // Unberührte Zeilen bleiben leer statt auf die Breite aufgefüllt.
        assert_eq!(zeilen[0], "");
    }

    #[test]
    fn breites_zeichen_erscheint_einmal() {
        let mut s = TextScreen::new();
        s.print("中a");
        // Ein Zeichen, kein nachgestelltes Leerzeichen für die Fortsetzung.
        assert_eq!(snapshot(&s), "中a\n");
    }

    #[test]
    fn leerer_bildschirm_ergibt_leeren_snapshot() {
        assert_eq!(snapshot(&TextScreen::new()), "");
    }
}
