use ratatui::layout::Rect;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Frame;

use crate::state::AppState;
use crate::theme;

const FRAMES: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

pub fn render(state: &AppState, frame: &mut Frame, area: Rect) {
    if state.in_flight.is_none() {
        return;
    }
    let frame_idx = (std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis()
        / 80) as usize
        % FRAMES.len();
    let label = match state.in_flight.as_ref() {
        Some(crate::state::InFlight::Thinking) => "thinking",
        Some(crate::state::InFlight::StreamingText { .. }) => "streaming",
        Some(crate::state::InFlight::ToolCall { name, .. }) => name.as_str(),
        Some(crate::state::InFlight::ToolRunning { name, .. }) => name.as_str(),
        None => return,
    };
    let spinner_area = Rect {
        x: area.x + 1,
        y: area.y,
        width: 20.min(area.width.saturating_sub(2)),
        height: 1,
    };
    let line = Line::from(vec![
        Span::styled(FRAMES[frame_idx].to_string(), theme::spinner()),
        Span::raw(" "),
        Span::styled(label.to_string(), theme::dim()),
    ]);
    frame.render_widget(Paragraph::new(line), spinner_area);
}

#[cfg(test)]
mod tests {
    // Spinner is time-dependent; snapshot tests are flaky, so we only
    // assert that calling render() with in_flight = None is a no-op, and
    // that with in_flight = Some(Thinking) it panics/writes nothing unusual.
    use super::*;
    use crate::state::{AppState, InFlight};
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;

    #[test]
    fn noop_when_idle() -> Result<(), Box<dyn std::error::Error>> {
        let state = AppState::default();
        let mut term = Terminal::new(TestBackend::new(40, 3))?;
        let before = format!("{:?}", term.backend());
        term.draw(|f| render(&state, f, f.area()))?;
        let after = format!("{:?}", term.backend());
        assert_eq!(before, after, "spinner must not draw when idle");
        Ok(())
    }

    #[test]
    fn renders_something_when_thinking() -> Result<(), Box<dyn std::error::Error>> {
        let state = AppState {
            in_flight: Some(InFlight::Thinking),
            ..AppState::default()
        };
        let mut term = Terminal::new(TestBackend::new(40, 3))?;
        term.draw(|f| render(&state, f, f.area()))?;
        let debug = format!("{:?}", term.backend());
        assert!(debug.contains("thinking"), "spinner label missing: {debug}");
        Ok(())
    }
}
