use crate::gitui::config::Config;
use itertools::Itertools;
use ratatui::style::Style;
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::text::Text;
use std::borrow::Cow;
use std::iter;
use std::process::Command;
use std::sync::Arc;
use std::sync::RwLock;

pub struct CmdLog {
    pub entries: Vec<Arc<RwLock<CmdLogEntry>>>,
}

impl CmdLog {
    pub fn new() -> Self {
        Self { entries: vec![] }
    }

    pub fn push_cmd(&mut self, cmd: &Command) -> Arc<RwLock<CmdLogEntry>> {
        let value = Arc::new(RwLock::new(CmdLogEntry::Cmd {
            args: command_args(cmd),
            out: None,
        }));

        self.entries.push(Arc::clone(&value));
        value
    }

    pub fn push_cmd_with_output(
        &mut self,
        cmd: &Command,
        out: Cow<'static, str>,
    ) -> Arc<RwLock<CmdLogEntry>> {
        let value = Arc::new(RwLock::new(CmdLogEntry::Cmd {
            args: command_args(cmd),
            out: Some(out),
        }));

        self.entries.push(Arc::clone(&value));
        value
    }

    pub fn push(&mut self, entry: CmdLogEntry) {
        self.entries.push(Arc::new(RwLock::new(entry)));
    }

    pub fn clear(&mut self) {
        self.entries.clear();
    }

    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    pub fn format_log(&self, config: &Config) -> Text<'static> {
        Text::from(
            self.entries
                .iter()
                .flat_map(|cmd| format_log_entry(config, cmd))
                .collect::<Vec<_>>(),
        )
    }
}

pub fn command_args(cmd: &Command) -> Cow<'static, str> {
    iter::once(cmd.get_program().to_string_lossy())
        .chain(cmd.get_args().map(|arg| arg.to_string_lossy()))
        .join(" ")
        .into()
}

pub fn format_log_entry<'a>(
    config: &Config,
    log: &Arc<RwLock<CmdLogEntry>>,
) -> Vec<Line<'a>> {
    match &*log.read().unwrap() {
        CmdLogEntry::Cmd { args, out } => [Line::styled(
            format!("{}{}", if out.is_some() { "$ " } else { "Running: " }, args),
            &config.style.command,
        )]
        .into_iter()
        .chain(out.iter().flat_map(|out| {
            if out.is_empty() {
                vec![]
            } else {
                Text::raw(out.to_string()).lines
            }
        }))
        .collect::<Vec<_>>(),
        CmdLogEntry::Error(err) => {
            vec![Line::styled(
                format!("! {}", err),
                Style::new().red().bold(),
            )]
        }
        CmdLogEntry::Info(msg) => {
            vec![Line::styled(
                format!("> {}", msg),
                Style::new().green().bold(),
            )]
        }
    }
}

pub enum CmdLogEntry {
    Cmd {
        args: Cow<'static, str>,
        out: Option<Cow<'static, str>>,
    },
    Error(String),
    Info(String),
}
