use paste::paste;
use std::sync::OnceLock;
use thiserror::Error;

use super::DataLoadError;

#[derive(Debug, Error)]
#[error("template with id {0} not found")]
pub struct TemplateNotFoundError(pub u32);

macro_rules! template_id {
    ($type_name:ident $id_field:ident) => {
        ::paste::paste! {
            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ::serde::Deserialize, ::serde::Serialize)]
            pub struct [<$type_name ID>](u32);

            impl [<$type_name ID>] {
                pub fn new(id: u32) -> Result<Self, super::TemplateNotFoundError> {
                    crate::tables::[<$type_name:snake _template_tb>]::iter()
                        .any(|tmpl| tmpl.$id_field.value() == id)
                        .then_some(Self(id)).ok_or(super::TemplateNotFoundError(id))
                }

                pub const fn new_unchecked(id: u32) -> Self {
                    Self(id)
                }

                pub fn value(&self) -> u32 {
                    self.0
                }

                pub fn template(&self) -> &[<$type_name Template>] {
                    crate::tables::[<$type_name:snake _template_tb>]::iter().find(|tmpl| tmpl.$id_field == *self).unwrap()
                }
            }

            impl ::std::fmt::Display for [<$type_name ID>] {
                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                    f.write_fmt(format_args!("{}", self.0))
                }
            }
        }
    };
}

macro_rules! template_tables {
    ($($template_type:ident;)*) => {
        $(paste! {
            mod [<$template_type:snake>];
            pub use [<$template_type:snake>]::*;
        })*

        $(paste! {
            static [<$template_type:snake:upper _TB>]: OnceLock<Vec<$template_type>> = OnceLock::new();
        })*

        pub(crate) fn load_tables(filecfg_path: &str) -> Result<(), DataLoadError> {
            $(paste! {
                let file_name = concat!(stringify!($template_type), "Tb.json");
                let path = format!("{filecfg_path}/{file_name}");

                let data = std::fs::read_to_string(path)?;
                [<$template_type:snake:upper _TB>].set(serde_json::from_str(&data).map_err(|err| DataLoadError::FromJsonError(String::from(stringify!($template_type)), err))?).unwrap();
            })*

            Ok(())
        }

        $(paste! {
            pub mod [<$template_type:snake _tb>] {
                pub fn iter() -> ::std::slice::Iter<'static, super::$template_type> {
                    super::[<$template_type:snake:upper _TB>].get().unwrap().iter()
                }
            }
        })*
    };
}

template_tables! {
    AvatarBaseTemplate;
    BuddyBaseTemplate;
    UnlockConfigTemplate;
    SectionConfigTemplate;
    ProcedureConfigTemplate;
    PostGirlConfigTemplate;
    TrainingQuestTemplate;
    WeaponTemplate;
    MainCityObjectTemplate;
    MainCityDefaultObjectTemplate;
    MainCityBgmConfigTemplate;
    ArchiveFileQuestTemplate;
    ArchiveBattleQuestTemplate;
    HollowQuestTemplate;
    HollowConfigTemplate;
    BattleEventConfigTemplate;
    BattleGroupConfigTemplate;
    SubAreaDataTemplate;
    VariableDataTemplate;
    OnceRewardTemplate;
    QuickAccessTemplate;
    QuickFuncTemplate;
    TeleportConfigTemplate;
    ItemTemplate;
    RobotConfigTemplate;
    RobotBuddyConfigTemplate;
}
