use crate::ir::ModuleStructIR;
use crate::utils;

const MAX_FIELDS: usize = 255;

#[derive(syn_derive::ToTokens)]
pub struct ModuleDefItem {
    item_struct: syn::ItemStruct
}

impl TryFrom<&'_ ModuleStructIR> for ModuleDefItem {
    type Error = syn::Error;

    fn try_from(ir: &'_ ModuleStructIR) -> Result<Self, Self::Error> {
        let mut item_struct = ir.self_code().clone();
        let env_field: syn::Field = utils::misc::field(
            &utils::ident::underscored_env(),
            &utils::ty::rc_contract_env()
        );

        if item_struct.fields.len() > MAX_FIELDS {
            return Err(syn::Error::new_spanned(
                item_struct.fields,
                format!(
                    "The number of fields in a module definition must be less than or equal to {}",
                    MAX_FIELDS
                )
            ));
        }

        let fields = item_struct
            .fields
            .into_iter()
            .chain(vec![env_field])
            .collect::<syn::punctuated::Punctuated<_, _>>();

        item_struct.fields = syn::Fields::Named(syn::FieldsNamed {
            brace_token: Default::default(),
            named: fields
        });

        Ok(Self { item_struct })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_utils::{assert_eq, mock};

    #[test]
    fn test_module_def_item() {
        let ir = mock::module_definition();
        let def = ModuleDefItem::try_from(&ir).unwrap();
        let expected = quote::quote! {
            pub struct CounterPack {
                counter0: SubModule<Counter>,
                counter1: SubModule<Counter>,
                counter2: SubModule<Counter>,
                counters: Var<u32>,
                counters_map: Mapping<u8, Counter>,
                __env: odra::prelude::Rc<odra::ContractEnv>
            }
        };

        assert_eq(def, expected);
    }

    #[test]
    fn empty_module() {
        let ir = mock::empty_module_definition();
        let def = ModuleDefItem::try_from(&ir).unwrap();
        let expected = quote::quote! {
            pub struct CounterPack {
                __env: odra::prelude::Rc<odra::ContractEnv>
            }
        };
        assert_eq(def, expected);
    }

    #[test]
    fn test_invalid_module_definition() {
        let ir = mock::invalid_module_definition();
        let def = ModuleDefItem::try_from(&ir);
        assert!(def.is_err());
    }
}
