pub mod local;
pub mod null;

use anyhow::Result;

#[derive(Debug, Clone)]
pub struct SubstrateCommand {
    pub program: String,
    pub args: Vec<String>,
    pub working_dir: Option<String>,
    pub timeout_secs: u64,
    pub capture_stderr: bool,
    pub env: Vec<(String, String)>,
}

impl SubstrateCommand {
    pub fn new(program: impl Into<String>) -> Self {
        Self {
            program: program.into(),
            args: Vec::new(),
            working_dir: None,
            timeout_secs: 30,
            capture_stderr: true,
            env: Vec::new(),
        }
    }

    pub fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
        self.args = args.into_iter().map(Into::into).collect();
        self
    }

    pub fn cwd(mut self, dir: impl Into<String>) -> Self {
        self.working_dir = Some(dir.into());
        self
    }

    pub fn timeout(mut self, secs: u64) -> Self {
        self.timeout_secs = secs;
        self
    }
}

#[derive(Debug, Clone)]
pub struct SubstrateResult {
    pub stdout: Vec<u8>,
    pub stderr: Vec<u8>,
    pub exit_code: i32,
    pub duration_ms: u64,
}

impl SubstrateResult {
    pub fn stdout_str(&self) -> String {
        String::from_utf8_lossy(&self.stdout).into_owned()
    }

    pub fn stderr_str(&self) -> String {
        String::from_utf8_lossy(&self.stderr).into_owned()
    }

    pub fn succeeded(&self) -> bool {
        self.exit_code == 0
    }
}

#[derive(Debug, thiserror::Error)]
pub enum SubstrateError {
    #[error("command not found: {0}")]
    NotFound(String),
    #[error("execution timed out after {0}s")]
    Timeout(u64),
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),
    #[error("spawn failed: {0}")]
    Spawn(String),
}

pub trait Substrate: Send + Sync {
    fn execute(&self, cmd: SubstrateCommand) -> Result<SubstrateResult, SubstrateError>;
    fn is_available(&self) -> bool;
}
