//! `#[mizan::context]` / `#[mizan::context("name")]` — emit `ContextMarker`
//! impl + linkme registration for a unit struct.

use heck::ToSnakeCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Parser, punctuated::Punctuated, ItemStruct, Lit, LitStr, Meta, Token};

/// Attribute args: either nothing, or one string literal that overrides the
/// derived snake_case context name.
pub struct ContextArgs {
    pub explicit_name: Option<String>,
}

impl ContextArgs {
    pub fn parse(attr_tokens: TokenStream) -> syn::Result<Self> {
        if attr_tokens.is_empty() {
            return Ok(Self { explicit_name: None });
        }
        // Support both `#[mizan::context("user")]` (string literal) and
        // `#[mizan::context(name = "user")]` (key=value).
        if let Ok(lit) = syn::parse2::<LitStr>(attr_tokens.clone()) {
            return Ok(Self {
                explicit_name: Some(lit.value()),
            });
        }
        let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
        let metas = parser.parse2(attr_tokens)?;
        for meta in metas {
            if let Meta::NameValue(nv) = meta {
                if nv.path.is_ident("name") {
                    if let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = nv.value {
                        return Ok(Self {
                            explicit_name: Some(s.value()),
                        });
                    }
                }
            }
        }
        Err(syn::Error::new_spanned(
            "",
            "expected `#[mizan::context]` or `#[mizan::context(\"<name>\")]` or `#[mizan::context(name = \"<name>\")]`",
        ))
    }
}

pub fn expand(args: ContextArgs, item: ItemStruct) -> TokenStream {
    if !item.fields.is_empty() {
        return syn::Error::new_spanned(
            &item.fields,
            "#[mizan::context] requires a unit struct — context markers carry no data.",
        )
        .to_compile_error();
    }

    let ident = item.ident.clone();
    let name = args
        .explicit_name
        .unwrap_or_else(|| ident.to_string().to_snake_case());

    let register_static =
        format_ident!("__MIZAN_CTX_REGISTER_{}", ident.to_string().to_uppercase());

    quote! {
        #item

        impl ::mizan_core::ContextMarker for #ident {
            const NAME: &'static str = #name;
        }

        #[::mizan_core::__priv::linkme::distributed_slice(::mizan_core::CONTEXTS)]
        #[linkme(crate = ::mizan_core::__priv::linkme)]
        static #register_static: ::mizan_core::ContextEntry = ::mizan_core::ContextEntry {
            name: #name,
        };
    }
}
