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

#![allow(clippy::result_large_err)]

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

use clingwrap::runner::CommandRunner;

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

mod cargo;
pub use cargo::*;

mod custom;
pub use custom::*;

pub mod debian;
pub use debian::*;

pub mod dns;
pub use dns::*;

mod dput;
pub use dput::*;

mod dummy;
pub use dummy::*;

mod git;
pub use git::*;

mod http_get;
pub use http_get::*;

mod mkdir;
pub use mkdir::*;

mod netdiag;
pub use netdiag::*;

mod npm;
pub use npm::*;

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::*;

mod rustup;
pub use rustup::*;

mod file;
pub use file::*;

mod pip;
pub use pip::*;

mod gomod;
pub use gomod::*;

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

    /// A set of environment variables a pre-plan action can specify for
    /// executing the actual plan in its own VM. These are set in the
    /// [`RunnablePlan`] generated from the actual plan.
    const CARRY_OVER_ENVS: &[(&'static str, &'static str)] = &[];
}

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(RunLogSource::Plan, &cmd);

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