use std::io;

use slt::{frame, AppState, Backend, Buffer, Context, Event, Rect, RunConfig};

pub struct RecordingBackend {
    buffer: Buffer,
    width: u32,
    height: u32,
    last_rendered: String,
}

impl RecordingBackend {
    pub fn new(width: u32, height: u32) -> Self {
        Self {
            buffer: Buffer::empty(Rect::new(0, 0, width, height)),
            width,
            height,
            last_rendered: String::new(),
        }
    }

    pub fn to_string_trimmed(&self) -> String {
        self.last_rendered.clone()
    }

    fn snapshot_current_buffer(&self) -> String {
        let mut lines = Vec::with_capacity(self.height as usize);
        for y in 0..self.height {
            let mut line = String::new();
            for x in 0..self.width {
                line.push_str(&self.buffer.get(x, y).symbol);
            }
            lines.push(line.trim_end().to_string());
        }
        while lines.last().is_some_and(|line| line.is_empty()) {
            lines.pop();
        }
        lines.join("\n")
    }
}

impl Backend for RecordingBackend {
    fn size(&self) -> (u32, u32) {
        (self.width, self.height)
    }

    fn buffer_mut(&mut self) -> &mut Buffer {
        &mut self.buffer
    }

    fn flush(&mut self) -> io::Result<()> {
        self.last_rendered = self.snapshot_current_buffer();
        self.buffer.reset();
        Ok(())
    }
}

pub fn render_with_frame_backend(
    backend: &mut RecordingBackend,
    state: &mut AppState,
    events: &[Event],
    f: &mut impl FnMut(&mut Context),
) -> io::Result<String> {
    let keep_running = frame(backend, state, &RunConfig::default(), events, f)?;
    assert!(keep_running, "test frame unexpectedly requested quit");
    Ok(backend.to_string_trimmed())
}
