use nu_protocol::{Record, Spanned};

pub use arch::Arch;
pub use cargo::Cargo;
pub use flatpak::Flatpak;
pub use python::Uv;
pub use rustup::Rustup;

pub use crate::error::Error;
use crate::parser::Engine;
use crate::{CleanCacheCommand, CleanCommand, SyncCommand, UpdateCommand};

mod arch;
mod cargo;
mod flatpak;
mod python;
mod rustup;

#[derive(Debug)]
pub enum Backends {
    Arch(Arch),
    Flatpak(Flatpak),
    Cargo(Cargo),
    Rustup(Rustup),
    Uv(Uv),
}

pub type Result<T> = std::result::Result<T, Error>;

pub trait Backend {
    fn clean_cache(&self, opts: &CleanCacheCommand) -> Result<()>;
    fn install(&self, engine: &mut Engine, opts: &SyncCommand) -> Result<()>;
    fn new(value: Spanned<&Record>, config: Spanned<&Record>) -> Result<Self>
    where
        Self: Sized;
    fn remove(&self, opts: &CleanCommand) -> Result<()>;
    fn update(&self, opts: &UpdateCommand) -> Result<()>;
}

impl Backends {
    pub fn install(&self, engine: &mut Engine, opts: &SyncCommand) -> Result<()> {
        match self {
            Backends::Arch(arch) => arch.install(engine, opts),
            Backends::Flatpak(flatpak) => flatpak.install(engine, opts),
            Backends::Cargo(cargo) => cargo.install(engine, opts),
            Backends::Rustup(rustup) => rustup.install(engine, opts),
            Backends::Uv(uv) => uv.install(engine, opts),
        }
    }

    pub fn remove(&self, opts: &CleanCommand) -> Result<()> {
        match self {
            Backends::Arch(arch) => arch.remove(opts),
            Backends::Flatpak(flatpak) => flatpak.remove(opts),
            Backends::Cargo(cargo) => cargo.remove(opts),
            Backends::Rustup(rustup) => rustup.remove(opts),
            Backends::Uv(uv) => uv.remove(opts),
        }
    }

    pub fn clean_cache(&self, opts: &CleanCacheCommand) -> Result<()> {
        match self {
            Backends::Arch(arch) => arch.clean_cache(opts),
            Backends::Flatpak(flatpak) => flatpak.clean_cache(opts),
            Backends::Cargo(cargo) => cargo.clean_cache(opts),
            Backends::Rustup(rustup) => rustup.clean_cache(opts),
            Backends::Uv(uv) => uv.clean_cache(opts),
        }
    }

    pub fn update(&self, opts: &UpdateCommand) -> Result<()> {
        match self {
            Backends::Arch(arch) => arch.update(opts),
            Backends::Flatpak(flatpak) => flatpak.update(opts),
            Backends::Cargo(cargo) => cargo.update(opts),
            Backends::Rustup(rustup) => rustup.update(opts),
            Backends::Uv(uv) => uv.update(opts),
        }
    }
}

#[macro_export]
macro_rules! backend_parse {
    ($packages:ident, $config:ident, $($backend:ident),*) => {
        [$(
            {
                use nu_protocol::IntoSpanned;
                let packages = $packages.item.get(stringify!($backend));
                let pkgspan = $packages.span;

                match packages {
                    Some(package_struct) => {
                        let packages = package_struct
                        .as_record()
                        .map_err(|e| mod_err!(pkgspan, e))
                        .and_then(|packages| $backend::new(
                            packages.into_spanned(pkgspan),
                            $config.item.as_ref().into_spanned($config.span)
                        ))
                        .map(|backend| Backends::$backend(backend));
                    Some(packages)
                    }
                    None => None,
                }
            },

        )*]
    };
}

#[macro_export]
macro_rules! parse_all_backends {
    ($packages:ident, $config:ident) => {
        backend_parse!($packages, $config, Arch, Flatpak, Cargo, Rustup, Uv)
    };
}
