use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
use thiserror::Error;

pub mod auth;
pub mod import;
pub mod install;
pub mod instance;
pub mod jre;
pub mod logs;
pub mod metadata;
pub mod minecraft_skins;
pub mod mr_auth;
pub mod onboarding_checklist;
pub mod process;
pub mod reports;
pub mod settings;
pub mod shortcuts;
pub mod tags;
pub mod users;
pub mod utils;

pub mod ads;
#[cfg(target_os = "macos")]
mod ads_occlusion_macos;
#[cfg(windows)]
mod ads_occlusion_windows;
pub mod cache;
pub mod files;
pub mod friends;
pub mod worlds;

mod oauth_utils;

pub type Result<T> = std::result::Result<T, TheseusSerializableError>;

// // Main returnable Theseus GUI error
// // Needs to be Serializable to be returned to the JavaScript side
// #[derive(Error, Debug, Serialize)]
// pub enum TheseusGuiError {
//     #[error(transparent)]
//     Serializable(),
// }

// Serializable error intermediary, so TheseusGuiError can be Serializable (eg: so that we can return theseus::Errors in Tauri directly)
#[derive(Error, Debug)]
pub enum TheseusSerializableError {
    #[error("{0}")]
    Theseus(#[from] theseus::Error),

    #[error("IO error: {0}")]
    IO(#[from] std::io::Error),

    #[error("Tauri error: {0}")]
    Tauri(#[from] tauri::Error),

    #[cfg(feature = "updater")]
    #[error("Updater error: {0}")]
    Updater(#[from] tauri_plugin_updater::Error),

    #[cfg(feature = "updater")]
    #[error("HTTP error: {0}")]
    Http(#[from] tauri_plugin_http::reqwest::Error),
}

// Generic implementation of From<T> for ErrorTypeA
// impl<T> From<T> for TheseusGuiError
// where
//     TheseusSerializableError: From<T>,
// {
//     fn from(error: T) -> Self {
//         TheseusGuiError::Serializable(TheseusSerializableError::from(error))
//     }
// }

// This is a very simple macro that implements a very basic Serializable for each variant of TheseusSerializableError,
// where the field is the string. (This allows easy extension to errors without many match arms)
macro_rules! impl_serialize {
    ($($variant:ident),* $(,)?) => {
        impl Serialize for TheseusSerializableError {
            fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                match self {
                    // For the Theseus variant, we add a special display for the error,
                    // to view the spans if subscribed to them (which is information that is lost when serializing)
                    TheseusSerializableError::Theseus(theseus_error) => {
                        $crate::error::display_tracing_error(theseus_error);

                        let unavailable_reason = match theseus_error.raw.as_ref() {
                            theseus::ErrorKind::SharedInstanceUnavailable(reason) => Some(reason),
                            _ => None,
                        };
                        let code = match theseus_error.raw.as_ref() {
                            theseus::ErrorKind::SharedInstanceUnavailable(_) => {
                                Some("shared_instance_unavailable")
                            }
                            theseus::ErrorKind::SharedInstancesApiError(_) => {
                                Some("shared_instances_api_error")
                            }
                            _ => None,
                        };
                        let mut state = serializer.serialize_struct(
                            "Theseus",
                            2 + usize::from(code.is_some()) + usize::from(unavailable_reason.is_some()),
                        )?;
                        state.serialize_field("field_name", "Theseus")?;
                        state.serialize_field("message", &theseus_error.to_string())?;
                        if let Some(code) = code {
                            state.serialize_field("code", code)?;
                        }
                        if let Some(reason) = unavailable_reason {
                            state.serialize_field("reason", reason)?;
                        }
                        state.end()
                    }
                    $(
                        TheseusSerializableError::$variant(message) => {
                            let mut state = serializer.serialize_struct(stringify!($variant), 2)?;
                            state.serialize_field("field_name", stringify!($variant))?;
                            state.serialize_field("message", &message.to_string())?;
                            state.end()
                        },
                    )*
                }
            }
        }
    };
}

// Use the macro to implement Serialize for TheseusSerializableError
#[cfg(not(feature = "updater"))]
impl_serialize! {
    IO,
    Tauri,
}

#[cfg(feature = "updater")]
impl_serialize! {
    IO,
    Tauri,
    Updater,
    Http,
}
