use crate::IartErr;
use crate::events::AutoRequestType::{
    ToResult, ToResultFail, TryDownCastFail, TryDownCastUsed, TryUsed,
};
use crate::events::IartEvent;
use crate::types::{DummyErr, ErrorDetail, Iart};
use crate::utils::{cold_path, unlikely};
#[cfg(not(feature = "no-alloc"))]
use alloc::boxed::Box;
#[cfg(not(feature = "no-alloc"))]
use alloc::collections::VecDeque;
use core::fmt::Debug;
use core::panic::Location;

#[cfg(feature = "no-alloc")]
use crate::BACK_TRACE_MAX;

impl Default for ErrorDetail {
    #[inline]
    #[cfg(not(feature = "no-alloc"))]
    fn default() -> Self {
        Self {
            ty: Some(Box::new(DummyErr {})),
            desc: None,
            trans_fns: jen_fns!(DummyErr),
        }
    }

    #[inline]
    #[cfg(feature = "no-alloc")]
    fn default() -> Self {
        Self {
            ty: Some(&DummyErr {}),
            desc: None,
            trans_fns: jen_fns!(&DummyErr),
        }
    }
}

impl ErrorDetail {
    #[must_use]
    #[doc = include_str!("../../doc/fn/ErrorDetail/try_cast_err.md")]
    #[cfg(not(feature = "no-alloc"))]
    pub unsafe fn try_cast_err<T: 'static>(&mut self) -> Option<Box<T>> {
        let data = self.ty.take()?;
        let res = unsafe { (self.trans_fns.0)(data) };
        match res.downcast::<T>() {
            Ok(t) => Some(t),
            Err(item) => {
                self.ty = Some(unsafe { (self.trans_fns.1)(item) });
                None
            }
        }
    }

    #[must_use]
    #[doc = include_str!("../../doc/fn/ErrorDetail/try_cast_err.md")]
    #[cfg(feature = "no-alloc")]
    pub unsafe fn try_cast_err<T: 'static>(&mut self) -> Option<&'static T> {
        let data = self.ty.take()?;
        let res = unsafe { (self.trans_fns.0)(data) };
        res.downcast_ref::<T>()
    }
}

impl<Item: core::fmt::Debug> Iart<Item> {
    #[inline]
    #[must_use]
    #[doc = include_str!("../../doc/fn/Iart/is_ok.md")]
    pub const fn is_ok(&self) -> Option<bool> {
        if let Some(data) = self.data.as_ref() {
            Some(data.is_ok())
        } else {
            cold_path();
            debug_assert!(false, "Iart: is_ok called after consumption");
            None
        }
    }

    #[inline]
    #[must_use]
    #[track_caller]
    #[doc = include_str!("../../doc/fn/Iart/peak_err.md")]
    pub const fn peak_err(&self) -> Option<&ErrorDetail> {
        match self.data.as_ref() {
            Some(data) => match data {
                Ok(_) => None,
                Err(item) => Some(item),
            },
            None => {
                cold_path();
                debug_assert!(false, "Iart: have_warn called after consumption");
                None
            }
        }
    }

    #[track_caller]
    #[doc = include_str!("../../doc/fn/Iart/to_result.md")]
    #[cfg(not(feature = "no-alloc"))]
    pub unsafe fn to_result<T: 'static>(
        mut self,
    ) -> Result<
        (
            Result<(), (T, Box<ErrorDetail>)>,
            Option<Item>,
            Option<VecDeque<&'static Location<'static>>>,
        ),
        Self,
    >
    where
        Item: Debug,
    {
        self.send_log_to_handler::<true>(IartEvent::FunctionHook(ToResult))
            .unwrap();

        #[cfg(feature = "allow-backtrace-logging")]
        let log = self.log.take();
        #[cfg(not(feature = "allow-backtrace-logging"))]
        let log = None;

        #[cfg(feature = "error-can-have-item")]
        let err_item = self.err_item.take();

        #[cfg(not(feature = "error-can-have-item"))]
        let err_item = None;

        self.handled = true;

        match self.is_ok() {
            Some(false) => match unsafe { self.try_downcast::<T>() } {
                Err(mut me) => {
                    cold_path();
                    #[cfg(feature = "allow-backtrace-logging")]
                    {
                        me.log = log;
                    }
                    #[cfg(feature = "error-can-have-item")]
                    {
                        me.err_item = err_item;
                    }

                    me.send_log_to_handler::<true>(IartEvent::FunctionHook(ToResultFail))
                        .unwrap();

                    me.handled = false;

                    Err(me)
                }
                Ok(item) => Ok((Err(item), err_item, log)),
            },
            Some(true) => {
                let res = unsafe { self.data.take().unwrap_unchecked().unwrap_unchecked() };
                Ok((Ok(()), Some(res), log))
            }
            None => {
                cold_path();

                #[cfg(feature = "allow-backtrace-logging")]
                {
                    self.log = log;
                }

                #[cfg(feature = "error-can-have-item")]
                {
                    self.err_item = err_item;
                }

                self.handled = false;

                self.send_log_to_handler::<true>(IartEvent::FunctionHook(ToResultFail))
                    .unwrap();

                Err(self)
            }
        }
    }

    #[track_caller]
    #[doc = include_str!("../../doc/fn/Iart/to_result.md")]
    #[cfg(feature = "no-alloc")]
    pub unsafe fn to_result<T: 'static>(
        mut self,
    ) -> Result<
        (
            Result<(), (&'static T, ErrorDetail)>,
            Option<Item>,
            Option<[Option<&'static Location<'static>>; BACK_TRACE_MAX]>,
        ),
        Self,
    >
    where
        Item: Debug,
    {
        self.send_log_to_handler::<true>(IartEvent::FunctionHook(ToResult))
            .unwrap();

        #[cfg(feature = "allow-backtrace-logging")]
        let log = self.log.take();
        #[cfg(not(feature = "allow-backtrace-logging"))]
        let log = None;

        #[cfg(feature = "error-can-have-item")]
        let err_item = self.err_item.take();

        #[cfg(not(feature = "error-can-have-item"))]
        let err_item = None;

        self.handled = true;

        match self.is_ok() {
            Some(false) => match unsafe { self.try_downcast::<T>() } {
                Err(mut me) => {
                    cold_path();
                    #[cfg(feature = "allow-backtrace-logging")]
                    {
                        me.log = log;
                    }
                    #[cfg(feature = "error-can-have-item")]
                    {
                        me.err_item = err_item;
                    }

                    me.send_log_to_handler::<true>(IartEvent::FunctionHook(ToResultFail))
                        .unwrap();

                    me.handled = false;

                    Err(me)
                }
                Ok(item) => Ok((Err(item), err_item, log)),
            },
            Some(true) => {
                let res = unsafe { self.data.take().unwrap_unchecked().unwrap_unchecked() };
                Ok((Ok(()), Some(res), log))
            }
            None => {
                cold_path();

                #[cfg(feature = "allow-backtrace-logging")]
                {
                    self.log = log;
                }

                #[cfg(feature = "error-can-have-item")]
                {
                    self.err_item = err_item;
                }

                self.handled = false;

                self.send_log_to_handler::<true>(IartEvent::FunctionHook(ToResultFail))
                    .unwrap();

                Err(self)
            }
        }
    }

    #[track_caller]
    #[doc = include_str!("../../doc/fn/Iart/try_downcast.md")]
    #[cfg(not(feature = "no-alloc"))]
    pub unsafe fn try_downcast<T: 'static>(mut self) -> Result<(T, Box<ErrorDetail>), Self>
    where
        Item: Debug,
    {
        if unlikely(self.is_ok().unwrap_or(false)) {
            return Err(self);
        }

        self.send_log();

        let _ = self.send_log_to_handler::<true>(IartEvent::FunctionHook(TryDownCastUsed));

        let data = self.data.take();

        if unlikely(data.is_none()) {
            debug_assert!(false, "Iart: try_downcast called after consumption");
            let _ = self.send_log_to_handler::<true>(IartEvent::FunctionHook(TryDownCastFail));
            return Err(self);
        }

        let mut detail = unsafe { data.unwrap().unwrap_err_unchecked() };
        let ty = detail.ty.take().unwrap();

        let data = unsafe { (detail.trans_fns.0)(ty) };

        match data.downcast::<T>() {
            Err(item) => {
                cold_path();
                detail.ty = Some(unsafe { (detail.trans_fns.1)(item) });
                self.data = Some(Err(detail));
                let _ = self.send_log_to_handler::<true>(IartEvent::FunctionHook(TryDownCastFail));
                Err(self)
            }
            Ok(item) => {
                self.handled = true;

                let value: T = *item;

                Ok((value, detail))
            }
        }
    }

    #[track_caller]
    #[doc = include_str!("../../doc/fn/Iart/try_downcast.md")]
    #[cfg(feature = "no-alloc")]
    pub unsafe fn try_downcast<T: 'static>(mut self) -> Result<(&'static T, ErrorDetail), Self>
    where
        Item: Debug,
    {
        if unlikely(self.is_ok().unwrap_or(false)) {
            return Err(self);
        }

        self.send_log();

        let _ = self.send_log_to_handler::<true>(IartEvent::FunctionHook(TryDownCastUsed));

        let data = self.data.take();

        if unlikely(data.is_none()) {
            debug_assert!(false, "Iart: try_downcast called after consumption");
            let _ = self.send_log_to_handler::<true>(IartEvent::FunctionHook(TryDownCastFail));
            return Err(self);
        }

        let mut detail = unsafe { data.unwrap().unwrap_err_unchecked() };
        let ty = detail.ty.take().unwrap();

        let data = unsafe { (detail.trans_fns.0)(ty) };

        match data.downcast_ref::<T>() {
            None => {
                cold_path();
                self.data = Some(Err(detail));
                let _ = self.send_log_to_handler::<true>(IartEvent::FunctionHook(TryDownCastFail));
                Err(self)
            }
            Some(item) => {
                self.handled = true;

                Ok((item, detail))
            }
        }
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_send_try_used.md")]
    pub unsafe fn __internal_send_try_used(&self) -> core::fmt::Result {
        self.send_log_to_handler::<false>(IartEvent::FunctionHook(TryUsed))
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_take_data.md")]
    #[cfg(not(feature = "no-alloc"))]
    pub unsafe fn __internal_take_data(&mut self) -> Option<Result<Item, Box<ErrorDetail>>> {
        self.data.take()
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_take_data.md")]
    #[cfg(feature = "no-alloc")]
    pub unsafe fn __internal_take_data(&mut self) -> Option<Result<Item, ErrorDetail>> {
        self.data.take()
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_take_log.md")]
    #[cfg(not(feature = "no-alloc"))]
    pub unsafe fn __internal_take_log(&mut self) -> Option<VecDeque<&'static Location<'static>>> {
        #[cfg(feature = "allow-backtrace-logging")]
        let res = self.log.take();
        #[cfg(not(feature = "allow-backtrace-logging"))]
        let res = None;
        res
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_take_log.md")]
    #[cfg(feature = "no-alloc")]
    pub unsafe fn __internal_take_log(
        &mut self,
    ) -> Option<[Option<&'static Location<'static>>; BACK_TRACE_MAX]> {
        #[cfg(feature = "allow-backtrace-logging")]
        let res = self.log.take();
        #[cfg(not(feature = "allow-backtrace-logging"))]
        let res = None;
        res
    }

    #[doc(hidden)]
    #[inline]
    #[doc = include_str!("../../doc/fn/Iart/__internal_get_trans_fns.md")]
    #[cfg(not(feature = "no-alloc"))]
    pub unsafe fn __internal_get_trans_fns(
        &mut self,
    ) -> Option<(
        unsafe fn(Box<dyn IartErr + Send + Sync>) -> Box<dyn core::any::Any + Send + Sync>,
        unsafe fn(Box<dyn core::any::Any + Send + Sync>) -> Box<dyn IartErr + Send + Sync>,
    )> {
        self.trans_fns.clone()
    }

    #[doc(hidden)]
    #[inline]
    #[doc = include_str!("../../doc/fn/Iart/__internal_get_trans_fns.md")]
    #[cfg(feature = "no-alloc")]
    pub unsafe fn __internal_get_trans_fns(
        &mut self,
    ) -> Option<(
        unsafe fn(
            &'static (dyn IartErr + Send + Sync),
        ) -> &'static (dyn core::any::Any + Send + Sync),
        unsafe fn(
            &'static (dyn core::any::Any + Send + Sync),
        ) -> &'static (dyn IartErr + Send + Sync),
    )> {
        self.trans_fns.clone()
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_mark_handled.md")]
    pub unsafe fn __internal_mark_handled(&mut self) {
        self.handled = true;
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_take_err_item.md")]
    pub unsafe fn __internal_take_err_item(&mut self) -> Option<Item> {
        #[cfg(feature = "error-can-have-item")]
        let res = self.err_item.take();
        #[cfg(not(feature = "error-can-have-item"))]
        let res = None;

        res
    }

    #[doc(hidden)]
    #[inline(always)]
    #[cfg(feature = "for-nightly-allocator-api-support")]
    #[doc = include_str!("../../doc/fn/Iart/__internal_get_allocator.md")]
    pub unsafe fn __internal_get_allocator(&self) -> Option<alloc::alloc::Global> {
        Some(self.allocator)
    }

    #[doc(hidden)]
    #[inline(always)]
    #[cfg(not(feature = "for-nightly-allocator-api-support"))]
    #[doc = include_str!("../../doc/fn/Iart/__internal_get_allocator.md")]
    pub unsafe fn __internal_get_allocator(&self) -> Option<u32> {
        None
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_rebuild_err.md")]
    #[cfg(not(feature = "no-alloc"))]
    pub unsafe fn __internal_rebuild_err(
        err: Box<ErrorDetail>,
        #[allow(unused)] log: Option<VecDeque<&'static Location<'static>>>,
        trans_fns: Option<(
            unsafe fn(Box<dyn IartErr + Send + Sync>) -> Box<dyn core::any::Any + Send + Sync>,
            unsafe fn(Box<dyn core::any::Any + Send + Sync>) -> Box<dyn IartErr + Send + Sync>,
        )>,
        #[allow(unused)] err_item: Option<Item>,
        #[cfg(feature = "for-nightly-allocator-api-support")] alloc: Option<alloc::alloc::Global>,
        #[cfg(not(feature = "for-nightly-allocator-api-support"))] _alloc: Option<u32>,
    ) -> Self {
        Self {
            handled: false,
            data: Some(Err(err)),
            #[cfg(feature = "error-can-have-item")]
            err_item,
            #[cfg(feature = "allow-backtrace-logging")]
            log,
            trans_fns,
            #[cfg(feature = "for-nightly-allocator-api-support")]
            allocator: alloc.unwrap(),
        }
    }

    #[doc(hidden)]
    #[inline(always)]
    #[doc = include_str!("../../doc/fn/Iart/__internal_rebuild_err.md")]
    #[cfg(feature = "no-alloc")]
    pub unsafe fn __internal_rebuild_err(
        err: ErrorDetail,
        #[allow(unused)] log: Option<[Option<&'static Location<'static>>; BACK_TRACE_MAX]>,
        trans_fns: Option<(
            unsafe fn(
                &'static (dyn IartErr + Send + Sync),
            ) -> &'static (dyn core::any::Any + Send + Sync),
            unsafe fn(
                &'static (dyn core::any::Any + Send + Sync),
            ) -> &'static (dyn IartErr + Send + Sync),
        )>,
        #[allow(unused)] err_item: Option<Item>,
        #[cfg(feature = "for-nightly-allocator-api-support")] alloc: Option<alloc::alloc::Global>,
        #[cfg(not(feature = "for-nightly-allocator-api-support"))] _alloc: Option<u32>,
    ) -> Self {
        Self {
            handled: false,
            data: Some(Err(err)),
            #[cfg(feature = "error-can-have-item")]
            err_item,
            #[cfg(feature = "allow-backtrace-logging")]
            log,
            trans_fns,
            #[cfg(feature = "for-nightly-allocator-api-support")]
            allocator: alloc.unwrap(),
        }
    }
}

unsafe impl<T: Send> Send for Iart<T> {}
unsafe impl<T: Sync> Sync for Iart<T> {}

#[cfg(feature = "core_error-support")]
impl core_error::Error for Iart {}
