use super::{Field, FieldAttrs, MacroArgs, Struct, StructAttrs};
use crate::common::{Result, error, parse_attr_value, parse_meta_list};
use darling::FromMeta;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Attribute, LitStr, braced};

const STRUCT_ATTR_ERROR: &str = "Only `#[cfg]` and `#[doc]` are allowed on structs";

const DUPLICATE_SERDE_ATTR_ERROR: &str = "Duplicate `#[serde]` attribute";
const DUPLICATE_SERDE_AS_ATTR_ERROR: &str = "Duplicate `#[serde_as]` attribute";

const ARG_ATTR_ERROR: &str = "Only `#[serde]` and `#[serde_as]` are allowed on struct fields";

impl Parse for MacroArgs {
    fn parse(input: ParseStream) -> Result<Self> {
        if input.is_empty() {
            return Ok(Default::default());
        }

        let meta_list = parse_meta_list(&input)?;

        Ok(Self::from_list(&meta_list)?)
    }
}

impl Parse for Struct {
    fn parse(input: ParseStream) -> Result<Self> {
        fn parse_attrs(attrs: Vec<Attribute>) -> Result<StructAttrs> {
            let mut cfg = vec![];
            let mut doc = "".to_owned();

            for attr in attrs {
                let path = attr.path();

                if path.is_ident("cfg") {
                    cfg.push(attr);
                } else if path.is_ident("doc") {
                    doc.push_str(&parse_attr_value::<LitStr>(attr)?.value());
                } else {
                    return error(&attr, STRUCT_ATTR_ERROR);
                }
            }

            Ok(StructAttrs { cfg, doc })
        }

        let attrs = parse_attrs(input.call(Attribute::parse_outer)?)?;
        let vis = input.parse()?;
        let struct_token = input.parse()?;
        let ident = input.parse()?;
        let fields_content;
        let _brace_token = braced!(fields_content in input);
        let mut fields = Punctuated::new();

        while !fields_content.is_empty() {
            fields.push_value(fields_content.parse()?);

            if fields_content.is_empty() {
                break;
            }

            fields.push_punct(fields_content.parse()?);
        }

        Ok(Self {
            attrs,
            vis,
            struct_token,
            ident,
            fields,
        })
    }
}

impl Parse for Field {
    fn parse(input: ParseStream) -> Result<Self> {
        fn parse_attrs(raw_attrs: Vec<Attribute>) -> Result<FieldAttrs> {
            let mut attrs = FieldAttrs::default();

            for attr in raw_attrs {
                let path = attr.path();

                if path.is_ident("serde") {
                    if attrs.serde.is_some() {
                        return error(&attr, DUPLICATE_SERDE_ATTR_ERROR);
                    }

                    attrs.serde = Some(attr);
                } else if path.is_ident("serde_as") {
                    if attrs.serde_as.is_some() {
                        return error(&attr, DUPLICATE_SERDE_AS_ATTR_ERROR);
                    }

                    attrs.serde_as = Some(attr);
                } else {
                    return error(&attr, ARG_ATTR_ERROR);
                }
            }

            Ok(attrs)
        }

        let attrs = parse_attrs(input.call(Attribute::parse_outer)?)?;
        let vis = input.parse()?;
        let ident = input.parse()?;
        let colon_token = input.parse()?;
        let ty = input.parse()?;

        Ok(Self {
            attrs,
            vis,
            ident,
            colon_token,
            ty,
        })
    }
}
