use crate::KorokTrait;
use codama_attributes::{Attributes, NameDirective, TryFromFilter};
use codama_errors::{CodamaResult, IteratorCombineErrors};
use codama_nodes::{CamelCaseString, Node, RegisteredTypeNode, StructFieldTypeNode, TypeNode};

#[derive(Debug, PartialEq)]
pub struct FieldKorok<'a> {
    pub ast: &'a syn::Field,
    pub attributes: Attributes<'a>,
    pub node: Option<Node>,
}

impl<'a> FieldKorok<'a> {
    pub fn parse(ast: &'a syn::Field) -> CodamaResult<Self> {
        let attributes = Attributes::parse(&ast.attrs, ast.into())?;
        Ok(Self {
            ast,
            attributes,
            node: None,
        })
    }

    pub fn parse_all(ast: &'a syn::Fields) -> CodamaResult<Vec<Self>> {
        match ast {
            syn::Fields::Named(f) => f.named.iter().map(Self::parse).collect_and_combine_errors(),
            syn::Fields::Unnamed(f) => f
                .unnamed
                .iter()
                .map(Self::parse)
                .collect_and_combine_errors(),
            syn::Fields::Unit => Ok(vec![]),
        }
    }

    pub fn name(&self) -> Option<CamelCaseString> {
        self.attributes
            .get_last(NameDirective::filter)
            .map(|n| n.name.clone())
            .or_else(|| self.ast.ident.as_ref().map(|i| i.to_string().into()))
    }

    pub fn get_updated_type_node(&self, node: TypeNode) -> Option<Node> {
        match &self.node {
            Some(Node::Type(RegisteredTypeNode::StructField(field))) => Some(
                StructFieldTypeNode {
                    r#type: node,
                    ..field.clone()
                }
                .into(),
            ),
            // Only wrap in StructFieldTypeNode for named fields (those with an AST ident).
            // Tuple fields store the raw TypeNode; callers use field.name() to get custom
            // names from #[codama(name = "...")] when needed.
            _ => match self.ast.ident.as_ref() {
                Some(_) => Some(StructFieldTypeNode::new(self.name().unwrap(), node).into()),
                None => Some(node.into()),
            },
        }
    }

    pub fn set_type_node(&mut self, node: TypeNode) {
        self.node = self.get_updated_type_node(node);
    }
}

impl KorokTrait for FieldKorok<'_> {
    fn node(&self) -> &Option<Node> {
        &self.node
    }

    fn set_node(&mut self, node: Option<Node>) {
        self.node = node;
    }

    fn attributes(&self) -> Option<&Attributes<'_>> {
        Some(&self.attributes)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use codama_nodes::{
        NumberFormat::{U32, U64},
        NumberTypeNode,
    };

    #[test]
    fn get_updated_type_node_with_named_none() {
        let korok = FieldKorok {
            ast: &syn::parse_quote! { pub my_field: u32 },
            attributes: Attributes(vec![]),
            node: None,
        };
        assert_eq!(
            korok.get_updated_type_node(NumberTypeNode::le(U32).into()),
            Some(StructFieldTypeNode::new("my_field", NumberTypeNode::le(U32)).into())
        );
    }

    #[test]
    fn get_updated_type_node_with_unnamed_none() {
        let korok = FieldKorok {
            ast: &syn::parse_quote! { u32 },
            attributes: Attributes(vec![]),
            node: None,
        };
        assert_eq!(
            korok.get_updated_type_node(NumberTypeNode::le(U32).into()),
            Some(NumberTypeNode::le(U32).into())
        );
    }

    #[test]
    fn get_updated_type_node_with_some() {
        let korok = FieldKorok {
            ast: &syn::parse_quote! { pub my_field: u32 },
            attributes: Attributes(vec![]),
            node: Some(StructFieldTypeNode::new("my_node_name", NumberTypeNode::le(U32)).into()),
        };
        assert_eq!(
            korok.get_updated_type_node(NumberTypeNode::le(U64).into()),
            Some(StructFieldTypeNode::new("my_node_name", NumberTypeNode::le(U64)).into())
        );
    }
}
