use std::{borrow::Cow, sync::Arc, time::Duration};

use crate::config::Config;

mod private {
    pub trait HasProgress {}
}

impl private::HasProgress for () {}
impl private::HasProgress for MultiProgress {}
impl private::HasProgress for ProgressBar {}

pub trait HasProgress: private::HasProgress {}
impl<P: private::HasProgress> HasProgress for P {}

#[derive(Clone)]
pub struct Progress<T: HasProgress>(ProgressImpl<T>);

#[derive(Clone)]
enum ProgressImpl<T: HasProgress> {
    NoProgress,
    Progress(T),
}

impl<T> Progress<T>
where
    T: HasProgress,
{
    pub fn new(progress: T, config: &Config) -> Progress<T> {
        Progress(if config.no_progress() {
            ProgressImpl::NoProgress
        } else {
            ProgressImpl::Progress(progress)
        })
    }

    pub fn no_progress() -> Progress<T> {
        Progress(ProgressImpl::NoProgress)
    }

    pub fn map<F, R>(&self, callback: F) -> Progress<R>
    where
        F: FnOnce(&T) -> R,
        R: HasProgress,
    {
        Progress(if let ProgressImpl::Progress(progress) = &self.0 {
            ProgressImpl::Progress(callback(progress))
        } else {
            ProgressImpl::NoProgress
        })
    }
}

// WARNING: Don't implement `Clone` for this.
pub struct MultiProgress(indicatif::MultiProgress);
pub struct ProgressBar(indicatif::ProgressBar);

impl MultiProgress {
    pub fn new(config: &Config) -> Progress<Self> {
        Progress::new(Self(indicatif::MultiProgress::new()), config)
    }

    pub fn new_arc(config: &Config) -> Arc<Progress<MultiProgress>> {
        Arc::new(MultiProgress::new(config))
    }

    pub fn add(&self, bar: ProgressBar) -> ProgressBar {
        ProgressBar(self.0.insert_from_back(0, bar.0))
    }

    pub fn new_bar(&self) -> ProgressBar {
        self.add(ProgressBar::new())
    }

    pub fn suspend<F, R>(&self, callback: F) -> R
    where
        F: FnOnce() -> R,
    {
        self.0.suspend(callback)
    }
}

impl ProgressBar {
    pub fn new() -> Self {
        let bar =
            indicatif::ProgressBar::new_spinner().with_finish(indicatif::ProgressFinish::AndClear);
        bar.enable_steady_tick(Duration::from_millis(100));

        Self(bar)
    }

    pub fn into_raw(self) -> indicatif::ProgressBar {
        self.0
    }

    pub fn set_message<M>(&self, message: M)
    where
        M: Into<Cow<'static, str>>,
    {
        self.0.set_message(message)
    }

    pub fn set_position(&self, position: u64) {
        self.0.set_position(position)
    }

    pub fn position(&self) -> u64 {
        self.0.position()
    }

    pub fn println<M>(&self, message: M)
    where
        M: AsRef<str>,
    {
        self.0.println(message)
    }

    pub fn finish_with_message<M>(&self, message: M)
    where
        M: Into<Cow<'static, str>>,
    {
        self.0.finish_with_message(message)
    }

    pub fn finish_and_clear(&self) {
        self.0.finish_and_clear()
    }
}

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

impl From<String> for ProgressBar {
    fn from(message: String) -> Self {
        Self(Self::new().0.with_message(message))
    }
}

impl From<u64> for ProgressBar {
    fn from(position: u64) -> Self {
        let new = Self::new();
        new.set_position(position);

        new
    }
}
