use std::io;
use std::time::{Duration, Instant};

mod art_data;

use art_data::{load_art, ArtData};
use clap::{Parser, Subcommand};
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use crossterm::execute;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
use rand::RngExt;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::Rect;
use ratatui::widgets::Paragraph;
use ratatui::Terminal;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

const VERSION: &str = "1.1.1";
const MAX_LENGTH: usize = 30;

#[derive(Parser, Debug)]
#[command(name = "mmisay", version = VERSION, about = "Rust rewrite of momoisay")]
struct Cli {
    #[command(subcommand)]
    command: Option<Command>,

    // Backward compatible mode: `mmisay hello world` -> static v1.
    #[arg(hide = true)]
    text: Vec<String>,
}

#[derive(Subcommand, Debug)]
enum Command {
    /// List available static and animated versions.
    List,
    /// Show static Momoi.
    Static {
        #[arg(short, long, default_value_t = 1, value_parser = clap::value_parser!(u8).range(1..=1))]
        version: u8,
        #[arg()]
        text: Vec<String>,
    },
    /// Show animated Momoi.
    Animated {
        #[arg(short, long, default_value_t = 1, value_parser = clap::value_parser!(u8).range(1..=3))]
        version: u8,
        #[arg()]
        text: Vec<String>,
    },
    /// Freestyle random animation loop.
    Freestyle {
        #[arg()]
        text: Vec<String>,
    },
}

struct TerminalGuard;

impl TerminalGuard {
    fn enter() -> io::Result<Self> {
        enable_raw_mode()?;
        execute!(io::stdout(), EnterAlternateScreen)?;
        Ok(Self)
    }
}

impl Drop for TerminalGuard {
    fn drop(&mut self) {
        let _ = disable_raw_mode();
        let _ = execute!(io::stdout(), LeaveAlternateScreen);
    }
}

fn main() -> io::Result<()> {
    let cli = Cli::parse();
    let art = load_art();

    match cli.command {
        Some(Command::List) => {
            println!("static: 1");
            println!("animated: 1 2 3");
            Ok(())
        }
        Some(Command::Static { version, text }) => {
            run_static(&art, version as usize, wrap_text(&text.join(" ")))
        }
        Some(Command::Animated { version, text }) => {
            run_animated(&art, version as usize, wrap_text(&text.join(" ")))
        }
        Some(Command::Freestyle { text }) => run_freestyle(&art, wrap_text(&text.join(" "))),
        None => run_static(&art, 1, wrap_text(&cli.text.join(" "))),
    }
}

fn run_static(art: &ArtData, version: usize, text_lines: Vec<String>) -> io::Result<()> {
    let frames = match version {
        1 => art.static_v1.clone(),
        _ => art.static_v1.clone(),
    };
    render_loop(frames, vec![Duration::from_millis(120)], text_lines, true)
}

fn run_animated(art: &ArtData, version: usize, text_lines: Vec<String>) -> io::Result<()> {
    let (frames, timings) = match version {
        1 => (
            art.animated_v1.clone(),
            vec![150, 75, 150, 150, 75],
        ),
        2 => (
            art.animated_v2.clone(),
            vec![70, 70, 70, 1500, 70, 70, 70],
        ),
        3 => (
            art.animated_v3.clone(),
            vec![70, 70, 70, 70, 70, 70, 70, 70],
        ),
        _ => (
            art.animated_v1.clone(),
            vec![150, 75, 150, 150, 75],
        ),
    };

    let timings = timings.into_iter().map(Duration::from_millis).collect();
    render_loop(frames, timings, text_lines, false)
}

fn run_freestyle(art: &ArtData, text_lines: Vec<String>) -> io::Result<()> {
    let _guard = TerminalGuard::enter()?;
    let backend = CrosstermBackend::new(io::stdout());
    let mut terminal = Terminal::new(backend)?;
    let mut rng = rand::rng();

    loop {
        let pick = rng.random_range(1..=3);
        let (frames, timings, rounds) = match pick {
            1 => (
                art.animated_v1.clone(),
                vec![150, 75, 150, 150, 75],
                rng.random_range(3..=5),
            ),
            2 => (
                art.animated_v2.clone(),
                vec![70, 70, 70, 1500, 70, 70, 70],
                rng.random_range(1..=3),
            ),
            _ => (
                art.animated_v3.clone(),
                vec![70, 70, 70, 70, 70, 70, 70, 70],
                rng.random_range(3..=5),
            ),
        };

        for _ in 0..rounds {
            for (idx, frame_lines) in frames.iter().enumerate() {
                let merged = merge_bubble(frame_lines, &text_lines);
                terminal.draw(|f| {
                    let area = centered_rect(f.area(), &merged);
                    let widget = Paragraph::new(merged.join("\n"));
                    f.render_widget(widget, area);
                })?;

                if should_quit(Duration::from_millis(timings[idx]))? {
                    return Ok(());
                }
            }
        }
    }
}

fn render_loop(
    frames: Vec<Vec<String>>,
    timings: Vec<Duration>,
    text_lines: Vec<String>,
    static_only: bool,
) -> io::Result<()> {
    let _guard = TerminalGuard::enter()?;
    let backend = CrosstermBackend::new(io::stdout());
    let mut terminal = Terminal::new(backend)?;

    let mut idx = 0usize;
    loop {
        let merged = merge_bubble(&frames[idx], &text_lines);
        terminal.draw(|f| {
            let area = centered_rect(f.area(), &merged);
            let widget = Paragraph::new(merged.join("\n"));
            f.render_widget(widget, area);
        })?;

        if should_quit(timings[idx.min(timings.len().saturating_sub(1))])? {
            return Ok(());
        }

        if !static_only {
            idx = (idx + 1) % frames.len();
        }
    }
}

fn should_quit(timeout: Duration) -> io::Result<bool> {
    let end = Instant::now() + timeout;
    while Instant::now() < end {
        let remain = end.saturating_duration_since(Instant::now());
        if event::poll(remain.min(Duration::from_millis(25)))? {
            if let Event::Key(key) = event::read()? {
                if key.kind == KeyEventKind::Press {
                    match key.code {
                        KeyCode::Char('q') | KeyCode::Esc | KeyCode::Char('c') => return Ok(true),
                        _ => {}
                    }
                }
            }
        }
    }
    Ok(false)
}

fn centered_rect(area: Rect, lines: &[String]) -> Rect {
    let width = lines
        .iter()
        .map(|l| display_width(l))
        .max()
        .unwrap_or(1) as u16;
    let height = lines.len().max(1) as u16;

    let x = area.x + area.width.saturating_sub(width) / 2;
    let y = area.y + area.height.saturating_sub(height) / 2;

    Rect::new(x, y, width.min(area.width), height.min(area.height))
}

fn wrap_text(text: &str) -> Vec<String> {
    let cleaned = text.trim();
    if cleaned.is_empty() {
        return Vec::new();
    }

    let mut lines = Vec::new();
    let mut current = String::new();

    for word in cleaned.split_whitespace() {
        let next_len = if current.is_empty() {
            display_width(word)
        } else {
            display_width(&current) + 1 + display_width(word)
        };

        if next_len > MAX_LENGTH {
            if !current.is_empty() {
                lines.push(current.clone());
                current.clear();
            }

            if display_width(word) > MAX_LENGTH {
                let mut chunk = String::new();
                let mut chunk_width = 0usize;
                for ch in word.chars() {
                    let ch_width = UnicodeWidthChar::width(ch).unwrap_or(0);
                    if chunk_width + ch_width > MAX_LENGTH {
                        lines.push(chunk.clone());
                        chunk.clear();
                        chunk_width = 0;
                    }
                    chunk.push(ch);
                    chunk_width += ch_width;
                }
                if !chunk.is_empty() {
                    current = chunk;
                }
            } else {
                current.push_str(word);
            }
        } else {
            if !current.is_empty() {
                current.push(' ');
            }
            current.push_str(word);
        }
    }

    if !current.is_empty() {
        lines.push(current);
    }

    lines
}

fn merge_bubble(frame: &[String], text_lines: &[String]) -> Vec<String> {
    if text_lines.is_empty() {
        return frame.to_vec();
    }

    let bubble_width = text_lines
        .iter()
        .map(|l| display_width(l))
        .max()
        .unwrap_or(0);

    let mut output = Vec::new();
    output.push(format!(" {} ", "_".repeat(bubble_width + 2)));

    for line in text_lines {
        let padding = bubble_width.saturating_sub(display_width(line));
        output.push(format!("| {}{} |", line, " ".repeat(padding)));
    }

    output.push(format!(" {} ", "-".repeat(bubble_width + 2)));
    output.push("      \\".to_string());
    output.push("       \\".to_string());
    output.extend(frame.iter().cloned());
    output
}

fn display_width(s: &str) -> usize {
    UnicodeWidthStr::width(s)
}

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

    #[test]
    fn bubble_width_handles_cjk() {
        let frame = vec!["x".to_string()];
        let lines = vec!["你好ab".to_string()];
        let out = merge_bubble(&frame, &lines);

        assert_eq!(display_width(&lines[0]), 6);
        assert_eq!(out[0], " ________ ");
        assert_eq!(out[2], " -------- ");
        assert_eq!(out[1], "| 你好ab |");
    }
}
