use {
    anstream::{print, println},
    owo_colors::*,
    problemo::*,
    std::{error::Error, fmt},
};

// In this example we will attach a formatting function.

// See examples/supertrait.rs for a different approach.

fn read_file(path: &str) -> Result<String, Problem> {
    std::fs::read_to_string(path)
        .via(ReadError::new(path.into()))
        .with(ReadError::pretty())
}

fn main() {
    if let Err(problem) = read_file("non-existing.txt") {
        for cause in &problem {
            print!("• ");
            match cause.attachment_of_type::<PrettyPrint>() {
                // Handle pretty errors
                Some(pretty_print) => pretty_print(&cause.error),

                // Handle non-pretty errors
                None => println!("{}", cause.error),
            }
        }
    }
}

//
// ReadError
//

#[derive(Debug)]
pub struct ReadError {
    pub path: String,
}

impl ReadError {
    pub fn new(path: String) -> Self {
        Self { path }
    }

    pub fn pretty() -> PrettyPrint {
        Box::new(Self::pretty_print)
    }

    // This particular function expects to be attached to a ReadError only;
    // But it's possible to have more general pretty-printing functions, too
    pub fn pretty_print(error: &CapturedError) {
        let error: &Self = error.downcast_ref().expect("ReadError");
        println!("could not read from: {}", error.path.red().underline());
    }
}

impl fmt::Display for ReadError {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "could not read from: {}'", self.path)
    }
}

impl Error for ReadError {}

//
// PrettyPrint
//

pub type PrettyPrint = Box<dyn Fn(&CapturedError) + Send + Sync>;
