use super::{super::problem::*, into::*};

use std::{any::*, error::Error, io};

//
// ProblemResult
//

/// Problemo extensions for [Result].
pub trait ProblemResult<OkT> {
    /// Adds the error to the top of the causation chain.
    fn via<ErrorT>(self, error: ErrorT) -> Result<OkT, Problem>
    where
        ErrorT: 'static + Error + Send + Sync;

    /// Adds the error to the top of the causation chain.
    fn map_via<ErrorT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        ErrorT: 'static + Error + Send + Sync,
        FromT: FnOnce() -> ErrorT;

    /// Attach to the top cause.
    fn with<AttachmentT>(self, attachment: AttachmentT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync;

    /// Attach to the top cause.
    fn map_with<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> AttachmentT;

    /// Attach to the top cause only if we don't already have one of the same type.
    fn with_once<AttachmentT>(self, attachment: AttachmentT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync;

    /// Attach to the top cause only if we don't already have one of the same type.
    fn map_with_once<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> AttachmentT;

    /// Attach to the top cause if [Some].
    fn maybe_with<AttachmentT>(self, attachment: Option<AttachmentT>) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync;

    /// Attach to the top cause.
    fn maybe_map_with<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> Option<AttachmentT>;

    /// Attach to the top cause if [Some] and we don't already have one of the same type.
    fn maybe_with_once<AttachmentT>(self, attachment: Option<AttachmentT>) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync;

    /// Attach to the top cause if [Some] and we don't already have one of the same type.
    fn maybe_map_with_once<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> Option<AttachmentT>;

    /// Attach a [Location](std::panic::Location) (via
    /// [Location::caller](std::panic::Location::caller)) if we don't already have one.
    fn with_location(self) -> Result<OkT, Problem>;

    /// Attach a backtrace to the top cause if we don't already have one.
    fn with_backtrace(self) -> Result<OkT, Problem>;

    /// Into [io::Error] with [ErrorKind::Other](io::ErrorKind::Other).
    fn into_io_error(self) -> io::Result<OkT>;
}

// Note that we are *not* using map_err() here because a closure cannot be annotated with #[track_caller]

impl<ResultT, OkT> ProblemResult<OkT> for ResultT
where
    ResultT: IntoProblemResult<OkT>,
{
    #[track_caller]
    fn via<ViaErrorT>(self, error: ViaErrorT) -> Result<OkT, Problem>
    where
        ViaErrorT: 'static + Error + Send + Sync,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.via(error)),
        }
    }

    #[track_caller]
    fn map_via<ErrorT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        ErrorT: 'static + Error + Send + Sync,
        FromT: FnOnce() -> ErrorT,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.via(from())),
        }
    }

    #[track_caller]
    fn with<AttachmentT>(self, attachment: AttachmentT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with(attachment)),
        }
    }

    #[track_caller]
    fn map_with<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> AttachmentT,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with(from())),
        }
    }

    #[track_caller]
    fn with_once<AttachmentT>(self, attachment: AttachmentT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with_once(attachment)),
        }
    }

    #[track_caller]
    fn map_with_once<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> AttachmentT,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with_once(from())),
        }
    }

    #[track_caller]
    fn maybe_with<AttachmentT>(self, attachment: Option<AttachmentT>) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.maybe_with(attachment)),
        }
    }

    #[track_caller]
    fn maybe_map_with<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> Option<AttachmentT>,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.maybe_with(from())),
        }
    }

    #[track_caller]
    fn maybe_with_once<AttachmentT>(self, attachment: Option<AttachmentT>) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.maybe_with_once(attachment)),
        }
    }

    #[track_caller]
    fn maybe_map_with_once<AttachmentT, FromT>(self, from: FromT) -> Result<OkT, Problem>
    where
        AttachmentT: Any + Send + Sync,
        FromT: FnOnce() -> Option<AttachmentT>,
    {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.maybe_with_once(from())),
        }
    }

    #[track_caller]
    fn with_location(self) -> Result<OkT, Problem> {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with_location()),
        }
    }

    #[track_caller]
    fn with_backtrace(self) -> Result<OkT, Problem> {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.with_backtrace()),
        }
    }

    #[track_caller]
    fn into_io_error(self) -> io::Result<OkT> {
        match self.into_problem() {
            Ok(ok) => Ok(ok),
            Err(problem) => Err(problem.into()),
        }
    }
}
