//! Action implementations.
//!
//! These types implement all the actual actions supported by Ambient.

#![allow(clippy::result_large_err)]

use std::{
    path::PathBuf,
    process::{Command, Stdio},
};

use clingwrap::runner::CommandRunner;

use crate::action::{ActionError, Context};

mod cargo;
pub use cargo::*;

mod custom;
pub use custom::*;

mod deb;
pub use deb::*;

mod dput;
pub use dput::*;

mod dummy;
pub use dummy::*;

mod http_get;
pub use http_get::*;

mod mkdir;
pub use mkdir::*;

mod pwd;
pub use pwd::*;

mod rsync;
pub use rsync::*;

mod shell;
pub use shell::*;

mod tar;
pub use tar::*;

mod setenv;
pub use setenv::*;

/// Implement an action.
pub trait ActionImpl: std::fmt::Debug {
    /// Execute the action.
    fn execute(&self, context: &mut Context) -> Result<(), ActionError>;
}

fn rust_toolchain_versions(context: &mut Context) -> Result<(), ActionError> {
    spawn(context, &["cargo", "--version"])?;
    spawn(context, &["cargo", "clippy", "--version"])?;
    spawn(context, &["rustc", "--version"])?;
    Ok(())
}

fn spawn(context: &mut Context, argv: &[&str]) -> Result<(), ActionError> {
    spawn_in(context, argv, context.source_dir().to_path_buf())
}

fn spawn_in(context: &mut Context, argv: &[&str], cwd: PathBuf) -> Result<(), ActionError> {
    let argv0 = if let Some(argv0) = argv.first() {
        argv0
    } else {
        return Err(ActionError::SpawnNoArgv0);
    };

    let mut cmd = Command::new(argv0);
    cmd.args(&argv[1..])
        .envs(context.env())
        .current_dir(&cwd)
        .stdin(Stdio::null())
        .current_dir(&cwd);

    context.runlog().start_program(&cmd);

    let mut runner = CommandRunner::new(cmd);
    runner.capture_stdout();
    runner.capture_stderr();
    match runner.execute() {
        Ok(output) => {
            context.runlog().program_succeeded(&output);
            Ok(())
        }
        Err(err) => {
            context.runlog().program_failed(&err);
            Err(ActionError::Execute(argv0.to_string(), err))
        }
    }
}
