//! Floating error toast at bottom-right.

use ftui::core::geometry::Rect;
use ftui::render::cell::{Cell, PackedRgba};
use ftui::render::drawing::Draw;
use ftui::render::frame::Frame;

/// Render a floating error toast at the bottom-right of the area.
///
/// The toast has a colored background and truncates long messages.
pub fn render_error_toast(
    frame: &mut Frame<'_>,
    area: Rect,
    msg: &str,
    error_bg: PackedRgba,
    error_fg: PackedRgba,
) {
    if area.height < 3 || area.width < 10 || msg.is_empty() {
        return;
    }

    // Toast dimensions: message + padding, max 60 chars or half screen.
    let max_toast_width = (area.width / 2).clamp(20, 60);
    let toast_text = if msg.len() as u16 > max_toast_width.saturating_sub(4) {
        let trunc_len = max_toast_width.saturating_sub(7) as usize;
        // Find a char boundary at or before trunc_len to avoid panicking
        // on multi-byte UTF-8 (e.g., emoji or CJK in error messages).
        let safe_end = msg
            .char_indices()
            .take_while(|&(i, _)| i <= trunc_len)
            .last()
            .map_or(0, |(i, c)| i + c.len_utf8())
            .min(msg.len());
        format!(" {}... ", &msg[..safe_end])
    } else {
        format!(" {msg} ")
    };
    let toast_width = toast_text.len() as u16;
    let toast_height: u16 = 1;

    // Position: bottom-right with 1-cell margin.
    let x = area.right().saturating_sub(toast_width + 1);
    let y = area.bottom().saturating_sub(toast_height + 1);

    let toast_rect = Rect::new(x, y, toast_width, toast_height);

    // Fill background.
    let bg_cell = Cell {
        bg: error_bg,
        ..Cell::default()
    };
    frame.draw_rect_filled(toast_rect, bg_cell);

    // Render text.
    let text_cell = Cell {
        fg: error_fg,
        bg: error_bg,
        ..Cell::default()
    };
    frame.print_text_clipped(x, y, &toast_text, text_cell, area.right());
}

#[cfg(test)]
mod tests {
    use super::*;
    use ftui::render::grapheme_pool::GraphemePool;

    macro_rules! with_frame {
        ($width:expr, $height:expr, |$frame:ident| $body:block) => {{
            let mut pool = GraphemePool::new();
            let mut $frame = Frame::new($width, $height, &mut pool);
            $body
        }};
    }

    fn white() -> PackedRgba {
        PackedRgba::rgb(0xFF, 0xFF, 0xFF)
    }

    fn red_bg() -> PackedRgba {
        PackedRgba::rgb(0xFF, 0x00, 0x00)
    }

    #[test]
    fn test_error_toast_renders() {
        with_frame!(80, 24, |frame| {
            render_error_toast(
                &mut frame,
                Rect::new(0, 0, 80, 24),
                "Database is busy",
                red_bg(),
                white(),
            );

            let y = 22u16;
            let has_content = (40..80u16).any(|x| {
                let cell = frame.buffer.get(x, y).unwrap();
                !cell.is_empty()
            });
            assert!(has_content, "Expected error toast at bottom-right");
        });
    }

    #[test]
    fn test_error_toast_empty_message_noop() {
        with_frame!(80, 24, |frame| {
            render_error_toast(&mut frame, Rect::new(0, 0, 80, 24), "", red_bg(), white());

            let has_content = (0..80u16).any(|x| {
                (0..24u16).any(|y| {
                    let cell = frame.buffer.get(x, y).unwrap();
                    !cell.is_empty()
                })
            });
            assert!(!has_content, "Empty message should render nothing");
        });
    }

    #[test]
    fn test_error_toast_truncates_long_message() {
        with_frame!(80, 24, |frame| {
            let long_msg = "A".repeat(200);
            render_error_toast(
                &mut frame,
                Rect::new(0, 0, 80, 24),
                &long_msg,
                red_bg(),
                white(),
            );
        });
    }
}
