use std::{
    fmt,
    path::{Path, PathBuf},
};

#[derive(Debug, Clone)]
pub enum BuildError {
    IoError(String),
    Generic(String),
}

impl From<std::io::Error> for BuildError {
    fn from(err: std::io::Error) -> Self {
        Self::IoError(err.to_string())
    }
}

impl From<Box<dyn std::error::Error>> for BuildError {
    fn from(err: Box<dyn std::error::Error>) -> Self {
        Self::Generic(err.to_string())
    }
}

impl fmt::Display for BuildError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::IoError(err) => write!(f, "IO Error: {err}"),
            Self::Generic(err) => write!(f, "Generic Error: {err}"),
        }
    }
}

pub trait Builder {
    type Output: Clone + std::convert::AsRef<[u8]>;
    type Args: Clone;

    fn new(src_path: impl Into<PathBuf>) -> Self;

    // starts compilation
    fn start(&mut self, args: Self::Args);

    // non-blocking function, returns output if completed
    fn poll(&mut self) -> Option<Result<Self::Output, BuildError>>;

    // blocking function, returns output when completed.
    fn output(&mut self) -> Result<Self::Output, BuildError>;

    fn write_result(&mut self, path: impl AsRef<Path>) -> Result<(), BuildError> {
        let output = self.output()?;
        std::fs::write(path.as_ref(), output)
            .map_err(|e| BuildError::IoError(e.to_string()))
    }

    fn logs(&self) -> Vec<String>;
}
