Documentation
use crate::error::{CliError, Result};
use crossterm::{
    cursor::{Hide, MoveTo, Show},
    event::{poll, read, Event, KeyCode, KeyModifiers},
    execute,
    style::ResetColor,
    terminal::{
        self, disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen,
        LeaveAlternateScreen,
    },
};
use glyph_core::Glyph;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};

struct TerminalGuard {
    stdout: std::io::Stdout,
}

impl TerminalGuard {
    fn new() -> Result<Self> {
        let mut stdout = std::io::stdout();
        enable_raw_mode().map_err(|e| CliError::Terminal(e.to_string()))?;
        execute!(
            stdout,
            EnterAlternateScreen,
            Hide,
            Clear(ClearType::All),
            ResetColor,
        )
        .map_err(|e| CliError::Terminal(e.to_string()))?;
        Ok(Self { stdout })
    }
}

impl Drop for TerminalGuard {
    fn drop(&mut self) {
        let _ = execute!(
            self.stdout,
            Show,
            ResetColor,
            Clear(ClearType::All),
            LeaveAlternateScreen,
        );
        let _ = disable_raw_mode();
    }
}

pub fn play_glyph(input: PathBuf, loops: u32) -> Result<()> {
    let mut file = File::open(&input)
        .map_err(|e| CliError::Other(format!("Failed to open {}: {}", input.display(), e)))?;
    let glyph = Glyph::read(&mut file)?;

    let _guard = TerminalGuard::new()?;
    let mut stdout = std::io::stdout();

    // Check if terminal is large enough
    let (term_width, term_height) =
        terminal::size().map_err(|e| CliError::Terminal(e.to_string()))?;
    if term_width < glyph.width as u16 || term_height < glyph.height as u16 {
        return Err(CliError::Other(format!(
            "Terminal size too small. Need at least {}x{}, but got {}x{}",
            glyph.width, glyph.height, term_width, term_height
        )));
    }

    play_animation(&mut stdout, &glyph, loops)
}

#[derive(Debug)]
enum PlayerCommand {
    Exit,
    TogglePause,
    PrevFrame,
    NextFrame,
    None,
}

fn play_animation(stdout: &mut std::io::Stdout, glyph: &Glyph, loops: u32) -> Result<()> {
    let mut current_loop = 0;
    let mut frame_index = 0;
    let mut paused = false;
    let mut last_frame_time = Instant::now();

    loop {
        // Check terminal size on each iteration
        let (term_width, term_height) =
            terminal::size().map_err(|e| CliError::Terminal(e.to_string()))?;
        if term_width < glyph.width as u16 || term_height < glyph.height as u16 {
            return Err(CliError::Other(format!(
                "Terminal size too small. Need at least {}x{}, but got {}x{}",
                glyph.width, glyph.height, term_width, term_height
            )));
        }

        if !paused {
            let frame = &glyph.frames[frame_index];
            let frame_duration = frame.duration_ms.unwrap_or(glyph.default_duration_ms);

            // Clear and draw frame
            execute!(stdout, Clear(ClearType::All), MoveTo(0, 0), ResetColor)
                .map_err(|e| CliError::Terminal(e.to_string()))?;

            // Write frame content line by line
            for line in frame.content.lines() {
                stdout
                    .write_all(line.as_bytes())
                    .map_err(|e| CliError::Terminal(e.to_string()))?;
                stdout
                    .write_all(b"\r\n")
                    .map_err(|e| CliError::Terminal(e.to_string()))?;
            }
            stdout
                .flush()
                .map_err(|e| CliError::Terminal(e.to_string()))?;

            // Calculate sleep time based on actual frame duration
            let elapsed = last_frame_time.elapsed();
            if elapsed < Duration::from_millis(frame_duration as u64) {
                let sleep_duration = Duration::from_millis(frame_duration as u64) - elapsed;
                // Break sleep into small intervals to check for input
                let start = Instant::now();
                while start.elapsed() < sleep_duration {
                    if poll(Duration::from_millis(1))
                        .map_err(|e| CliError::Terminal(e.to_string()))?
                    {
                        match handle_input()? {
                            PlayerCommand::Exit => return Ok(()),
                            PlayerCommand::TogglePause => {
                                paused = true;
                                break;
                            }
                            PlayerCommand::PrevFrame => {
                                frame_index = if frame_index == 0 {
                                    glyph.frames.len() - 1
                                } else {
                                    frame_index - 1
                                };
                                last_frame_time = Instant::now();
                                break;
                            }
                            PlayerCommand::NextFrame => {
                                frame_index = (frame_index + 1) % glyph.frames.len();
                                last_frame_time = Instant::now();
                                break;
                            }
                            PlayerCommand::None => {}
                        }
                    }
                    thread::sleep(Duration::from_millis(10));
                }
            }

            if !paused {
                last_frame_time = Instant::now();
                // Move to next frame
                frame_index = (frame_index + 1) % glyph.frames.len();
                if frame_index == 0 {
                    current_loop += 1;
                    if loops > 0 && current_loop >= loops {
                        break;
                    }
                }
            }
        } else {
            // When paused, still respond to input but don't advance frames
            if poll(Duration::from_millis(50)).map_err(|e| CliError::Terminal(e.to_string()))? {
                match handle_input()? {
                    PlayerCommand::Exit => return Ok(()),
                    PlayerCommand::TogglePause => {
                        paused = false;
                        last_frame_time = Instant::now();
                    }
                    PlayerCommand::PrevFrame => {
                        frame_index = if frame_index == 0 {
                            glyph.frames.len() - 1
                        } else {
                            frame_index - 1
                        };
                        // Redraw frame immediately when manually navigating
                        let frame = &glyph.frames[frame_index];
                        execute!(stdout, Clear(ClearType::All), MoveTo(0, 0))
                            .map_err(|e| CliError::Terminal(e.to_string()))?;
                        for line in frame.content.lines() {
                            stdout
                                .write_all(line.as_bytes())
                                .map_err(|e| CliError::Terminal(e.to_string()))?;
                            stdout
                                .write_all(b"\r\n")
                                .map_err(|e| CliError::Terminal(e.to_string()))?;
                        }
                        stdout
                            .flush()
                            .map_err(|e| CliError::Terminal(e.to_string()))?;
                    }
                    PlayerCommand::NextFrame => {
                        frame_index = (frame_index + 1) % glyph.frames.len();
                        // Redraw frame immediately when manually navigating
                        let frame = &glyph.frames[frame_index];
                        execute!(stdout, Clear(ClearType::All), MoveTo(0, 0))
                            .map_err(|e| CliError::Terminal(e.to_string()))?;
                        for line in frame.content.lines() {
                            stdout
                                .write_all(line.as_bytes())
                                .map_err(|e| CliError::Terminal(e.to_string()))?;
                            stdout
                                .write_all(b"\r\n")
                                .map_err(|e| CliError::Terminal(e.to_string()))?;
                        }
                        stdout
                            .flush()
                            .map_err(|e| CliError::Terminal(e.to_string()))?;
                    }
                    PlayerCommand::None => {}
                }
            }
        }
    }

    Ok(())
}

fn handle_input() -> Result<PlayerCommand> {
    if let Event::Key(key) = read().map_err(|e| CliError::Terminal(e.to_string()))? {
        match key.code {
            KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                return Ok(PlayerCommand::Exit)
            }
            KeyCode::Char('q') | KeyCode::Esc => return Ok(PlayerCommand::Exit),
            KeyCode::Char(' ') => return Ok(PlayerCommand::TogglePause),
            KeyCode::Left => return Ok(PlayerCommand::PrevFrame),
            KeyCode::Right => return Ok(PlayerCommand::NextFrame),
            _ => {}
        }
    }
    Ok(PlayerCommand::None)
}