use darling::util::Flag;
use darling::FromMeta;
use syn::ExprPath;

#[derive(Default)]
pub struct FieldAttributes {
    pub skip_mode: SkipMode,
    pub debug_format: DebugFormat,
}

impl FieldAttributes {
    fn new(internal: InternalFieldAttributes) -> darling::Result<Self> {
        let mut skip_mode = SkipMode::Default;
        let mut debug_format = DebugFormat::Default;

        if internal.skip.is_present() {
            skip_mode = skip_mode.try_combine(SkipMode::Always)?;
        }

        if let Some(skip_if) = internal.skip_if {
            skip_mode = skip_mode.try_combine(SkipMode::Condition(skip_if))?;
        }

        if let Some(format) = internal.format {
            debug_format = debug_format.try_combine(DebugFormat::Format(format))?;
        }

        if let Some(with) = internal.with {
            debug_format = debug_format.try_combine(DebugFormat::With(with))?;
        }

        Ok(Self {
            skip_mode,
            debug_format,
        })
    }

    pub fn try_combine(self, other: Self) -> darling::Result<Self> {
        let skip_mode = self.skip_mode.try_combine(other.skip_mode)?;
        let debug_format = self.debug_format.try_combine(other.debug_format)?;

        Ok(Self {
            skip_mode,
            debug_format,
        })
    }
}

impl FromMeta for FieldAttributes {
    fn from_nested_meta(item: &darling::ast::NestedMeta) -> darling::Result<Self> {
        InternalFieldAttributes::from_nested_meta(item).and_then(FieldAttributes::new)
    }

    fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
        InternalFieldAttributes::from_meta(item).and_then(FieldAttributes::new)
    }

    fn from_none() -> Option<Self> {
        InternalFieldAttributes::from_none().and_then(|attrs| FieldAttributes::new(attrs).ok())
    }

    fn from_word() -> darling::Result<Self> {
        InternalFieldAttributes::from_word().and_then(FieldAttributes::new)
    }

    fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
        InternalFieldAttributes::from_list(items).and_then(FieldAttributes::new)
    }
}

#[derive(FromMeta, Debug, PartialEq, Eq, Default)]
pub enum DebugFormat {
    #[default]
    Default,
    Format(String),
    With(ExprPath),
}

impl DebugFormat {
    fn try_combine(self, other: Self) -> darling::Result<Self> {
        match (&self, &other) {
            (DebugFormat::Default, _) => Ok(other),
            (_, DebugFormat::Default) => Ok(self),
            _ => Err(conflicting_format_options_error()),
        }
    }
}

#[derive(Default, PartialEq, Eq)]
pub enum SkipMode {
    #[default]
    Default,
    Condition(ExprPath),
    Always,
}

impl SkipMode {
    fn try_combine(self, other: Self) -> darling::Result<Self> {
        match (&self, &other) {
            (SkipMode::Default, _) => Ok(other),
            (_, SkipMode::Default) => Ok(self),
            _ => Err(conflicting_skip_options_error()),
        }
    }
}

#[derive(FromMeta)]
struct InternalFieldAttributes {
    skip: Flag,
    skip_if: Option<ExprPath>,
    format: Option<String>,
    with: Option<ExprPath>,
}

fn conflicting_skip_options_error() -> darling::Error {
    darling::Error::custom("Conflicting skip options")
}

fn conflicting_format_options_error() -> darling::Error {
    darling::Error::custom("Conflicting format options")
}
