//! Streaming output strategy and thinking animation.

use super::OutputStrategy;
use crate::ui::Icons;
use colored::Colorize;
use std::io::{self, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use tokio::runtime::Handle as TokioHandle;
use tokio::task::JoinHandle as TokioJoinHandle;
use tokio::time::sleep;

/// Streaming output - displays content in real-time as it arrives
///
/// This provides the best user experience for interactive use,
/// showing each token as it's generated by the LLM.
pub struct StreamingOutput {
    has_started: AtomicBool,
    /// Handle to the thinking animation thread
    thinking_handle: Mutex<Option<ThinkingAnimation>>,
}

/// Thinking animation that runs in a background thread
struct ThinkingAnimation {
    running: Arc<AtomicBool>,
    handle: Option<AnimationHandle>,
}

enum AnimationHandle {
    Thread(JoinHandle<()>),
    Tokio(TokioJoinHandle<()>),
}

impl ThinkingAnimation {
    fn start(message: &str) -> Self {
        let running = Arc::new(AtomicBool::new(true));
        let running_clone = running.clone();
        let message = message.to_string();

        let handle = if let Ok(rt_handle) = TokioHandle::try_current() {
            let join = rt_handle.spawn(async move {
                let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
                let mut i = 0;

                while running_clone.load(Ordering::Relaxed) {
                    print!("\r\x1b[K\x1b[2m{} {}\x1b[0m", frames[i], message);
                    let _ = io::stdout().flush();
                    i = (i + 1) % frames.len();
                    sleep(Duration::from_millis(80)).await;
                }

                print!("\r\x1b[K");
                let _ = io::stdout().flush();
            });
            AnimationHandle::Tokio(join)
        } else {
            let join = thread::spawn(move || {
                let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
                let mut i = 0;

                while running_clone.load(Ordering::Relaxed) {
                    print!("\r\x1b[K\x1b[2m{} {}\x1b[0m", frames[i], message);
                    let _ = io::stdout().flush();
                    i = (i + 1) % frames.len();
                    thread::sleep(Duration::from_millis(80));
                }

                print!("\r\x1b[K");
                let _ = io::stdout().flush();
            });
            AnimationHandle::Thread(join)
        };

        Self {
            running,
            handle: Some(handle),
        }
    }

    fn stop(&mut self) {
        self.running.store(false, Ordering::Relaxed);
        print!("\r\x1b[K");
        let _ = io::stdout().flush();
        if let Some(handle) = self.handle.take() {
            match handle {
                AnimationHandle::Thread(handle) => {
                    let _ = handle.join();
                }
                AnimationHandle::Tokio(handle) => {
                    handle.abort();
                }
            }
        }
    }
}

impl Drop for ThinkingAnimation {
    fn drop(&mut self) {
        self.running.store(false, Ordering::Relaxed);
    }
}

impl StreamingOutput {
    pub fn new() -> Self {
        Self {
            has_started: AtomicBool::new(false),
            thinking_handle: Mutex::new(None),
        }
    }
}

impl Default for StreamingOutput {
    fn default() -> Self {
        Self::new()
    }
}

impl OutputStrategy for StreamingOutput {
    fn on_content_start(&self) {
        print!("\r\x1b[K");
        print!("{} ", Icons::message().bright_white());
        let _ = io::stdout().flush();
        self.has_started.store(true, Ordering::SeqCst);
    }

    fn on_content_chunk(&self, chunk: &str) {
        let indented = chunk.replace('\n', "\n  ");
        print!("{}", indented);
        let _ = io::stdout().flush();
    }

    fn on_content_end(&self) {
        if self.has_started.load(Ordering::SeqCst) {
            println!();
        }
        self.has_started.store(false, Ordering::SeqCst);
    }

    fn on_tool_start(&self, name: &str, params: &str) {
        print!("\r\x1b[K");
        println!();
        print!(
            "{} {}",
            Icons::message().bright_blue(),
            name.bright_white().bold()
        );
        if !params.is_empty() {
            println!("({})", params.dimmed());
        } else {
            println!();
        }
    }

    fn on_tool_result(&self, success: bool, output: Option<&str>, error: Option<&str>) {
        if success {
            if let Some(out) = output {
                let preview: String = out.lines().take(3).collect::<Vec<_>>().join("\n    ");
                if !preview.trim().is_empty() {
                    let truncated = crate::utils::truncate_with_ellipsis(&preview, 200);
                    println!("  {} {}", Icons::result().dimmed(), truncated.dimmed());
                }
            }
        } else {
            let err_msg = error.unwrap_or("Unknown error");
            let first_line = err_msg.lines().next().unwrap_or(err_msg);
            let truncated = crate::utils::truncate_with_ellipsis(first_line, 80);
            println!("  {} {}", Icons::result().red(), truncated.red());
        }
    }

    fn on_thinking(&self, message: &str) {
        if let Ok(mut handle) = self.thinking_handle.lock() {
            if let Some(ref mut anim) = *handle {
                anim.stop();
            }
            *handle = Some(ThinkingAnimation::start(message));
        }
    }

    fn on_thinking_stop(&self) {
        if let Ok(mut handle) = self.thinking_handle.lock() {
            if let Some(ref mut anim) = *handle {
                anim.stop();
            }
            *handle = None;
        }
    }
}
