use syn::{DataEnum, Ident};

use super::{ClvmOptions, Repr, VariantInfo, parse_clvm_options, parse_variant};

pub struct EnumInfo {
    pub variants: Vec<VariantInfo>,
    pub discriminant_type: Option<Ident>,
    pub is_untagged: bool,
    pub default_repr: Repr,
    pub crate_name: Option<Ident>,
}

pub fn parse_enum(mut options: ClvmOptions, data_enum: &DataEnum) -> EnumInfo {
    assert!(
        options.constant.is_none(),
        "`constant` only applies to fields"
    );

    assert!(
        options.default.is_none(),
        "`default` only applies to fields"
    );

    assert!(!options.rest, "`rest` only applies to fields");

    let repr = Repr::expect(options.repr);

    if repr == Repr::Transparent {
        assert!(
            !options.untagged,
            "`transparent` enums are implicitly untagged"
        );

        options.untagged = true;
    }

    let mut variants = Vec::new();

    for variant in &data_enum.variants {
        let variant_options = parse_clvm_options(&variant.attrs);
        let variant_repr = variant_options.repr;

        assert!(
            !(repr == Repr::Atom && variant_repr.is_some()),
            "cannot override `atom` representation for individual enum variants"
        );

        assert!(
            repr != Repr::Atom || variant.fields.is_empty(),
            "cannot have fields in an `atom` enum variant"
        );

        assert!(
            options.untagged || variant_repr.is_none(),
            "cannot specify representation for individual enum variants in a tagged enum"
        );

        let mut variant_info = parse_variant(&variant_options, variant);

        if (repr == Repr::Transparent && variant_repr.is_none())
            || variant_repr == Some(Repr::Transparent)
        {
            assert!(
                variant_info.fields.len() == 1,
                "`transparent` enum variants must have exactly one field"
            );

            variant_info.fields[0].rest = true;
            variant_info.repr = Some(Repr::ProperList);
        }

        variants.push(variant_info);
    }

    EnumInfo {
        variants,
        discriminant_type: options.enum_repr,
        is_untagged: options.untagged,
        default_repr: repr,
        crate_name: options.crate_name,
    }
}
