use derive_try_from_ref::TryFromRef;
use syn::parse_quote;

use crate::{ir::ModuleImplIR, utils};

use super::parts_utils::UseSuperItem;

#[derive(syn_derive::ToTokens, TryFromRef)]
#[source(ModuleImplIR)]
#[err(syn::Error)]
pub struct BlueprintItem {
    #[expr(utils::attr::odra_module(&input.module_str()?))]
    attr: syn::Attribute,
    mod_item: BlueprintModItem
}

#[derive(syn_derive::ToTokens, TryFromRef)]
#[source(ModuleImplIR)]
#[err(syn::Error)]
struct BlueprintModItem {
    #[default]
    mod_token: syn::token::Mod,
    #[expr(input.schema_mod_ident()?)]
    ident: syn::Ident,
    #[syn(braced)]
    #[default]
    brace_token: syn::token::Brace,
    #[default]
    #[syn(in = brace_token)]
    use_super: UseSuperItem,
    #[syn(in = brace_token)]
    schema_fn: SchemaFnItem
}

#[derive(syn_derive::ToTokens)]
struct SchemaFnItem {
    no_mangle_attr: syn::Attribute,
    not_wasm32_attr: syn::Attribute,
    sig: syn::Signature,
    #[syn(braced)]
    brace_token: syn::token::Brace,
    #[syn(in = brace_token)]
    expr: syn::Expr
}

impl TryFrom<&'_ ModuleImplIR> for SchemaFnItem {
    type Error = syn::Error;

    fn try_from(module: &'_ ModuleImplIR) -> Result<Self, Self::Error> {
        let ty_blueprint = utils::ty::contract_blueprint();
        let ident_module_schema = utils::ident::module_schema();

        Ok(Self {
            no_mangle_attr: utils::attr::no_mangle(),
            not_wasm32_attr: utils::attr::not_wasm32(),
            sig: parse_quote!(fn #ident_module_schema() -> #ty_blueprint),
            brace_token: Default::default(),
            expr: utils::expr::new_blueprint(&module.module_ident()?)
        })
    }
}

#[cfg(test)]
mod test {
    use super::BlueprintItem;
    use crate::test_utils;
    use quote::quote;

    #[test]
    fn blueprint_item() {
        let module = test_utils::mock::module_impl();
        let item = BlueprintItem::try_from(&module).unwrap();
        let expected = quote!(
            #[cfg(odra_module = "Erc20")]
            mod __erc20_schema {
                use super::*;

                #[no_mangle]
                #[cfg(not(target_arch = "wasm32"))]
                fn module_schema() -> odra::contract_def::ContractBlueprint {
                    odra::contract_def::ContractBlueprint::new::<Erc20>()
                }
            }
        );
        test_utils::assert_eq(item, &expected);
    }
}
