use std::{
    io,
    path::PathBuf,
    process::{Command, ExitStatus, ExitStatusError},
};

use crate::{build::ports::BuildStep, env::Board};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("{0}")]
    SystemError(#[from] io::Error),
    #[error("Cargo command failed")]
    CargoFailed,
    #[error("Could not generate a processed kernel image")]
    GentablesError,
    #[error("Could not build userspace programs")]
    UserspaceBuildFailed,
    #[error("External command failed")]
    ExternalCommandFailed,
    #[error("Missing tools: {0:?}")]
    MissingTools(Vec<(&'static str, &'static str)>),
    #[error("git error: {0}")]
    GitError(#[from] git2::Error),
    #[error("Could not build toolchain")]
    ToolchainBuildFailed,
    #[error("Could not configure toolchain: {0}")]
    ToolchainConfigError(#[from] toml::ser::Error),
    #[error("Could not read {0}: {1}")]
    TomlParseError(PathBuf, toml::de::Error),
    #[error("Could not copy directory")]
    CopyDirectoryError(#[from] walkdir::Error),
    #[error("Could not copy file {1:?} -> {2:?}: {0}")]
    CopyFileError(io::Error, PathBuf, PathBuf),
    #[error("Invalid path: {0:?}")]
    InvalidPath(PathBuf),
    #[error("Could not resolve modules")]
    UnresolvedModuleDependencies,

    #[error("LLVM config failed: {0}")]
    LlvmConfigFailed(CommandFailed),
    #[error("LLVM build failed: {0}")]
    LlvmBuildFailed(CommandFailed),
    #[error("C/C++ runtime config failed: {0}")]
    CRuntimeConfigFailed(CommandFailed),
    #[error("C/C++ runtime build failed: {0}")]
    CRuntimeBuildFailed(CommandFailed),
    #[error("compiler-rt config failed: {0}")]
    CompilerRtConfigFailed(CommandFailed),
    #[error("compiler-rt build failed: {0}")]
    CompilerRtBuildFailed(CommandFailed),

    #[error("No qemu support for board {0:?}")]
    UnsupportedEmulation(Board),

    #[error("Unresolved ports/dependencies")]
    PortUnresolvedDependencies,
    #[error("Port {0:?} build error: {1}")]
    PortError(String, PortError),
}

#[derive(Debug, thiserror::Error)]
pub enum PortError {
    #[error("Could not load port manifest: {0}")]
    ManifestLoadError(io::Error),
    #[error("Invalid port manifest: {0}")]
    ManifestParseError(toml::de::Error),
    #[error("Step script {0:?} not found")]
    ScriptNotFound(PathBuf),
    #[error("Step {0:?} script execute error: {1}")]
    ScriptExecuteError(BuildStep, io::Error),
    #[error("Step {0:?} failed: {1:?}")]
    ScriptFailed(BuildStep, ExitStatus),
    #[error("ygglibc step {0:?} failed: {1}")]
    LibcBuildError(BuildStep, Box<Error>),
}

#[derive(Debug, thiserror::Error)]
pub enum CommandFailed {
    #[error("{0}")]
    Io(#[from] io::Error),
    #[error("exit status {0:?}")]
    ExitCode(#[from] ExitStatusError),
}

pub trait CommandExt {
    fn success(self) -> Result<(), CommandFailed>;
}

impl CommandExt for Command {
    fn success(mut self) -> Result<(), CommandFailed> {
        self.status()?.exit_ok()?;
        Ok(())
    }
}
