use syn::{Expr, FieldsNamed, FieldsUnnamed, Ident, Type, spanned::Spanned};

use super::{ClvmOptions, parse_clvm_options};

pub struct FieldInfo {
    pub ident: Ident,
    pub ty: Type,
    pub constant: Option<Expr>,
    pub optional_with_default: Option<Option<Expr>>,
    pub rest: bool,
}

pub fn parse_named_fields(fields: &FieldsNamed) -> Vec<FieldInfo> {
    let mut items = Vec::new();

    let mut rest = false;
    let mut optional = false;

    for field in &fields.named {
        let ident = field.ident.clone().unwrap();
        let ty = field.ty.clone();

        let options = parse_clvm_options(&field.attrs);
        check_field_options(&options);

        assert!(
            !rest,
            "nothing can come after the `rest` field, since it consumes all arguments"
        );

        assert!(
            !optional,
            "only the last field can be optional, to prevent ambiguity"
        );

        rest = options.rest;
        optional = options.default.is_some();

        items.push(FieldInfo {
            ident,
            ty,
            constant: options.constant,
            optional_with_default: options.default,
            rest: options.rest,
        });
    }

    items
}

pub fn parse_unnamed_fields(fields: &FieldsUnnamed) -> Vec<FieldInfo> {
    let mut items = Vec::new();

    let mut rest = false;
    let mut optional = false;

    for (i, field) in fields.unnamed.iter().enumerate() {
        let ident = Ident::new(&format!("field_{i}"), field.span());
        let ty = field.ty.clone();

        let options = parse_clvm_options(&field.attrs);
        check_field_options(&options);

        assert!(
            !rest,
            "nothing can come after the `rest` field, since it consumes all arguments"
        );

        assert!(
            !optional,
            "only the last field can be optional, to prevent ambiguity"
        );

        rest = options.rest;
        optional = options.default.is_some();

        items.push(FieldInfo {
            ident,
            ty,
            constant: options.constant,
            optional_with_default: options.default,
            rest: options.rest,
        });
    }

    items
}

fn check_field_options(options: &ClvmOptions) {
    assert!(!options.untagged, "`untagged` only applies to enums");

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

    if let Some(repr) = options.repr {
        panic!("`{repr}` can't be set on individual fields");
    }

    assert!(
        options.crate_name.is_none(),
        "`crate_name` can't be set on individual fields"
    );

    assert!(
        !(options.default.is_some() && options.constant.is_some()),
        "`default` can't be used with `constant` set"
    );

    assert!(
        !(options.default.is_some() && options.rest),
        "`default` can't be used with `rest` option set"
    );
}
