use crate::{
    kw, utils::DebugPunctuated, ParameterList, SolIdent, Spanned, Type, VariableDeclaration,
};
use proc_macro2::Span;
use std::fmt;
use syn::{
    parenthesized,
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
    token::Paren,
    Attribute, Error, Result, Token,
};
#[derive(Clone)]
pub struct ItemEvent {
    pub attrs: Vec<Attribute>,
    pub event_token: kw::event,
    pub name: SolIdent,
    pub paren_token: Paren,
    pub parameters: Punctuated<EventParameter, Token![,]>,
    pub anonymous: Option<kw::anonymous>,
    pub semi_token: Token![;],
}
impl fmt::Display for ItemEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "event {}(", self.name)?;
        for (i, param) in self.parameters.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }
            param.fmt(f)?;
        }
        f.write_str(")")?;
        if self.is_anonymous() {
            f.write_str(" anonymous")?;
        }
        f.write_str(";")
    }
}
impl fmt::Debug for ItemEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ItemEvent")
            .field("attrs", &self.attrs)
            .field("name", &self.name)
            .field("arguments", DebugPunctuated::new(&self.parameters))
            .field("anonymous", &self.is_anonymous())
            .finish()
    }
}
impl Parse for ItemEvent {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let content;
        Ok(Self {
            attrs: input.call(Attribute::parse_outer)?,
            event_token: input.parse()?,
            name: input.parse()?,
            paren_token: parenthesized!(content in input),
            parameters: content.parse_terminated(EventParameter::parse, Token![,])?,
            anonymous: input.parse()?,
            semi_token: input.parse()?,
        })
    }
}
impl Spanned for ItemEvent {
    fn span(&self) -> Span {
        self.name.span()
    }
    fn set_span(&mut self, span: Span) {
        self.name.set_span(span);
    }
}
impl ItemEvent {
    #[inline]
    pub const fn is_anonymous(&self) -> bool {
        self.anonymous.is_some()
    }
    #[inline]
    pub fn max_indexed(&self) -> usize {
        if self.is_anonymous() {
            4
        } else {
            3
        }
    }
    #[inline]
    pub fn exceeds_max_indexed(&self) -> bool {
        self.indexed_params().count() > self.max_indexed()
    }
    pub fn assert_valid(&self) -> Result<()> {
        if self.exceeds_max_indexed() {
            let msg = if self.is_anonymous() {
                "more than 4 indexed arguments for anonymous event"
            } else {
                "more than 3 indexed arguments for event"
            };
            Err(Error::new(self.span(), msg))
        } else {
            Ok(())
        }
    }
    pub fn params(&self) -> ParameterList {
        self.parameters.iter().map(EventParameter::as_param).collect()
    }
    pub fn param_types(
        &self,
    ) -> impl ExactSizeIterator<Item = &Type> + DoubleEndedIterator + Clone {
        self.parameters.iter().map(|var| &var.ty)
    }
    pub fn param_types_mut(
        &mut self,
    ) -> impl ExactSizeIterator<Item = &mut Type> + DoubleEndedIterator {
        self.parameters.iter_mut().map(|var| &mut var.ty)
    }
    pub fn param_types_and_names(
        &self,
    ) -> impl ExactSizeIterator<Item = (&Type, Option<&SolIdent>)> + DoubleEndedIterator {
        self.parameters.iter().map(|p| (&p.ty, p.name.as_ref()))
    }
    pub fn param_type_strings(
        &self,
    ) -> impl ExactSizeIterator<Item = String> + DoubleEndedIterator + Clone + '_ {
        self.parameters.iter().map(|var| var.ty.to_string())
    }
    pub fn non_indexed_params(&self) -> impl Iterator<Item = &EventParameter> {
        self.parameters.iter().filter(|p| !p.is_indexed())
    }
    pub fn indexed_params(&self) -> impl Iterator<Item = &EventParameter> {
        self.parameters.iter().filter(|p| p.is_indexed())
    }
    pub fn dynamic_params(&self) -> impl Iterator<Item = &EventParameter> {
        self.parameters.iter().filter(|p| p.is_abi_dynamic())
    }
    pub fn as_type(&self) -> Type {
        let mut ty = Type::Tuple(self.parameters.iter().map(|arg| arg.ty.clone()).collect());
        ty.set_span(self.span());
        ty
    }
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct EventParameter {
    pub attrs: Vec<Attribute>,
    pub ty: Type,
    pub indexed: Option<kw::indexed>,
    pub name: Option<SolIdent>,
}
impl fmt::Display for EventParameter {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.ty.fmt(f)?;
        if self.indexed.is_some() {
            f.write_str(" indexed")?;
        }
        if let Some(name) = &self.name {
            write!(f, " {name}")?;
        }
        Ok(())
    }
}
impl fmt::Debug for EventParameter {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("EventParameter")
            .field("attrs", &self.attrs)
            .field("ty", &self.ty)
            .field("indexed", &self.indexed.is_some())
            .field("name", &self.name)
            .finish()
    }
}
impl Parse for EventParameter {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        Ok(Self {
            attrs: input.call(Attribute::parse_outer)?,
            ty: input.parse()?,
            indexed: input.parse()?,
            name: if SolIdent::peek_any(input) { Some(input.parse()?) } else { None },
        })
    }
}
impl Spanned for EventParameter {
    fn span(&self) -> Span {
        let span = self.ty.span();
        self.name.as_ref().and_then(|name| span.join(name.span())).unwrap_or(span)
    }
    fn set_span(&mut self, span: Span) {
        self.ty.set_span(span);
        if let Some(kw) = &mut self.indexed {
            kw.span = span;
        }
        if let Some(name) = &mut self.name {
            name.set_span(span);
        }
    }
}
impl EventParameter {
    pub fn as_param(&self) -> VariableDeclaration {
        VariableDeclaration {
            attrs: self.attrs.clone(),
            name: self.name.clone(),
            storage: None,
            ty: self.ty.clone(),
        }
    }
    #[inline]
    pub const fn is_indexed(&self) -> bool {
        self.indexed.is_some()
    }
    pub const fn is_non_indexed(&self) -> bool {
        self.indexed.is_none()
    }
    pub fn is_abi_dynamic(&self) -> bool {
        self.ty.is_abi_dynamic()
    }
    pub fn indexed_as_hash(&self) -> bool {
        self.is_indexed() && self.is_abi_dynamic()
    }
}