use crate::{Displayish, DisplayishResult, Seal};

use core::fmt::{Debug, Display, Formatter, Result as FmtResult};

pub trait ContentIntoDisplayExt<D: Display>: Into<D> {
    fn into_dis(self) -> Displayish<D>;

    /// Param `f` is a closure/function that returns an implementation of [Display]. This method
    /// returns a new [Displayish] with a [Displayish::display]
    /// - starting with [Display] returned from `f()`, then
    /// - a space appended, and
    /// - ONLY THEN [Display] generated by `self.into()`.
    fn into_dis_with<FD: Display, F: Fn() -> FD>(self, f: F) -> Displayish<impl Display>;

    fn into_dis_and<EX>(self, extra: EX) -> Displayish<D, EX>;

    fn into_dis_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> Displayish<impl Display, EX>;

    // Sealing is not really necessary, because we have a blanket impl that covers any and
    // all eligible types, so no other types can implement this trait.
    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal);
}
struct DisplayFromFn<F>(F)
where
    F: Fn(&mut Formatter) -> FmtResult;
impl<F: Fn(&mut Formatter) -> FmtResult> Display for DisplayFromFn<F> {
    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
        self.0(fmt)
    }
}
impl<D: Display, T: Into<D>> ContentIntoDisplayExt<D> for T {
    fn into_dis(self) -> Displayish<D> {
        Displayish::new_from_display(self.into())
    }

    fn into_dis_with<FD: Display, F: Fn() -> FD>(self, f: F) -> Displayish<impl Display> {
        let s = self.into();
        let display = DisplayFromFn(move |fmt| {
            f().fmt(fmt)?;
            Display::fmt(&' ', fmt)?; // ' '.fmt(fmt) is ambiguous
            s.fmt(fmt)
        });
        Displayish::<DisplayFromFn<_>>::new_from_display(display)
    }

    fn into_dis_and<EX>(self, extra: EX) -> Displayish<D, EX> {
        Displayish::new_from_pair(self.into(), extra)
    }
    fn into_dis_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> Displayish<impl Display, EX> {
        let s = self.into();
        let display = DisplayFromFn(move |fmt| {
            f().fmt(fmt)?;
            Display::fmt(&' ', fmt)?; // ' '.fmt(fmt) is ambiguous
            s.fmt(fmt)
        });
        Displayish::<DisplayFromFn<_>, _>::new_from_pair(display, extra)
    }

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal) {}
}

pub trait ResultErrIntoDisplayExt<D: Display, T> {
    fn map_err_into(self) -> DisplayishResult<T, D>;
    fn map_err_into_with<FD: Display, F: Fn() -> FD>(
        self,
        f: F,
    ) -> DisplayishResult<T, impl Display>;

    fn map_err_into_and<EX>(self, extra: EX) -> DisplayishResult<T, impl Display, EX>;

    fn map_err_into_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> DisplayishResult<T, impl Display, EX>;

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal);
}
impl<D: Display, T, ERR: Into<D>> ResultErrIntoDisplayExt<D, T> for Result<T, ERR> {
    fn map_err_into(self) -> DisplayishResult<T, D> {
        self.map_err(|err| Displayish::new_from_display(err))
    }
    fn map_err_into_with<FD: Display, F: Fn() -> FD>(
        self,
        f: F,
    ) -> DisplayishResult<T, impl Display> {
        self.map_err(|err| {
            let err = err.into();
            let display = DisplayFromFn(move |fmt| {
                f().fmt(fmt)?;
                Display::fmt(&' ', fmt)?; // ' '.fmt(fmt) is ambiguous
                err.fmt(fmt)
            });
            Displayish::<DisplayFromFn<_>>::new_from_display(display)
        })
    }

    fn map_err_into_and<EX>(self, extra: EX) -> DisplayishResult<T, impl Display, EX> {
        self.map_err(|err| Displayish::new_from_into_pair(err, extra))
    }
    fn map_err_into_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> DisplayishResult<T, impl Display, EX> {
        self.map_err(|err| {
            let err = err.into();
            let display = DisplayFromFn(move |fmt| {
                f().fmt(fmt)?;
                Display::fmt(&' ', fmt)?; // ' '.fmt(fmt) is ambiguous
                err.fmt(fmt)
            });
            Displayish::new_from_pair(display, extra)
        })
    }
    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal) {}
}

pub trait OptionOrBoolExt<T> {
    fn ok_or_error_with<FD: Display, F: Fn() -> FD>(self, f: F) -> DisplayishResult<T, FD>;

    fn ok_or_error_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> DisplayishResult<T, FD, EX>;

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal);
}
impl<T> OptionOrBoolExt<T> for Option<T> {
    fn ok_or_error_with<FD: Display, F: Fn() -> FD>(self, f: F) -> DisplayishResult<T, FD> {
        self.ok_or_else(|| Displayish::new_from_display(f()))
    }

    fn ok_or_error_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> DisplayishResult<T, FD, EX> {
        self.ok_or_else(|| Displayish::new_from_pair(f(), extra))
    }

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal) {}
}
impl OptionOrBoolExt<()> for bool {
    fn ok_or_error_with<FD: Display, F: Fn() -> FD>(self, f: F) -> DisplayishResult<(), FD> {
        // bool::ok_or_else is unstable: https://github.com/rust-lang/rust/issues/142748
        if self {
            Ok(())
        } else {
            Err(Displayish::new_from_display(f()))
        }
    }

    fn ok_or_error_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> DisplayishResult<(), FD, EX> {
        if self {
            Ok(())
        } else {
            Err(Displayish::new_from_pair(f(), extra))
        }
    }

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal) {}
}

pub trait DebugExt: Debug {
    fn dbg_error(&self) -> Displayish<impl Display>;

    fn dbg_error_with<FD: Display, F: Fn() -> FD>(self, f: F) -> Displayish<impl Display>;

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal);
}

impl<T: Debug> DebugExt for T {
    fn dbg_error(&self) -> Displayish<impl Display> {
        let display = DisplayFromFn(move |fmt| fmt.write_fmt(format_args!("{self:?}")));
        Displayish::<DisplayFromFn<_>>::new_from_display(display)
    }
    fn dbg_error_with<FD: Display, F: Fn() -> FD>(self, f: F) -> Displayish<impl Display> {
        let display = DisplayFromFn(move |fmt| {
            f().fmt(fmt)?;
            Display::fmt(&' ', fmt)?; // ' '.fmt(fmt) is ambiguous
            fmt.write_fmt(format_args!("{self:?}"))
        });
        Displayish::<DisplayFromFn<_>>::new_from_display(display)
    }

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal) {}
}

pub trait ResultErrDebugExt<T> {
    fn map_err_dbg(self) -> DisplayishResult<T, impl Display>;
    fn map_err_dbg_with<FD: Display, F: Fn() -> FD>(
        self,
        f: F,
    ) -> DisplayishResult<T, impl Display>;

    fn map_err_dbg_and<EX>(self, extra: EX) -> DisplayishResult<T, impl Display, EX>;
    fn map_err_dbg_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> DisplayishResult<T, impl Display, EX>;

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal);
}
impl<T, ERR: Debug> ResultErrDebugExt<T> for Result<T, ERR> {
    fn map_err_dbg(self) -> DisplayishResult<T, impl Display> {
        self.map_err(|e| {
            let display = DisplayFromFn(move |fmt| fmt.write_fmt(format_args!("{e:?}")));
            Displayish::<DisplayFromFn<_>>::new_from_display(display)
        })
    }
    fn map_err_dbg_with<FD: Display, F: Fn() -> FD>(
        self,
        f: F,
    ) -> DisplayishResult<T, impl Display> {
        self.map_err(|e| {
            let display = DisplayFromFn(move |fmt| {
                f().fmt(fmt)?;
                Display::fmt(&' ', fmt)?; // ' '.fmt(fmt) is ambiguous
                fmt.write_fmt(format_args!("{e:?}"))
            });
            Displayish::<DisplayFromFn<_>>::new_from_display(display)
        })
    }

    fn map_err_dbg_and<EX>(self, extra: EX) -> DisplayishResult<T, impl Display, EX> {
        self.map_err(|e| {
            let display = DisplayFromFn(move |fmt| fmt.write_fmt(format_args!("{e:?}")));
            Displayish::<DisplayFromFn<_>, _>::new_from_pair(display, extra)
        })
    }
    fn map_err_dbg_with_and<FD: Display, F: Fn() -> FD, EX>(
        self,
        f: F,
        extra: EX,
    ) -> DisplayishResult<T, impl Display, EX> {
        self.map_err(|e| {
            let display = DisplayFromFn(move |fmt| {
                f().fmt(fmt)?;
                Display::fmt(&' ', fmt)?; // ' '.fmt(fmt) is ambiguous
                fmt.write_fmt(format_args!("{e:?}"))
            });
            Displayish::<DisplayFromFn<_>, _>::new_from_pair(display, extra)
        })
    }

    #[allow(private_interfaces)]
    fn _seal(&self, _: Seal) {}
}
