use syn::{DataStruct, Fields, Ident};

use super::{ClvmOptions, FieldInfo, Repr, parse_named_fields, parse_unnamed_fields};

pub struct StructInfo {
    pub kind: StructKind,
    pub fields: Vec<FieldInfo>,
    pub repr: Repr,
    pub crate_name: Option<Ident>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StructKind {
    Unit,
    Unnamed,
    Named,
}

pub fn parse_struct(options: ClvmOptions, data_struct: &DataStruct) -> StructInfo {
    assert!(!options.untagged, "`untagged` only applies to enums");

    assert!(options.enum_repr.is_none(), "`repr` only applies to enums");

    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 mut repr = Repr::expect(options.repr);

    assert!(
        repr != Repr::Atom,
        "`atom` is not a valid representation for structs"
    );

    let crate_name = options.crate_name;

    let (kind, mut fields) = match &data_struct.fields {
        Fields::Unit => (StructKind::Unit, Vec::new()),
        Fields::Named(fields) => (StructKind::Named, parse_named_fields(fields)),
        Fields::Unnamed(fields) => (StructKind::Unnamed, parse_unnamed_fields(fields)),
    };

    if repr == Repr::Transparent {
        assert!(
            fields.len() == 1,
            "`transparent` structs must have exactly one field"
        );

        fields[0].rest = true;
        repr = Repr::ProperList;
    }

    StructInfo {
        kind,
        fields,
        repr,
        crate_name,
    }
}
