use std::{
    collections::HashMap,
    hash::{DefaultHasher, Hasher},
    sync::Mutex,
};

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};

static REGISTRY: Mutex<Option<HashMap<String, u16>>> = Mutex::new(None);

fn get_registry() -> std::sync::MutexGuard<'static, Option<HashMap<String, u16>>> {
    REGISTRY.lock().unwrap()
}

pub fn new_protocol_info(input: TokenStream) -> TokenStream {
    // Get protocol name
    let name_str = parse_macro_input!(input as LitStr).value();

    // Generate new protocol tag, conditioned to the package major version
    let tag: u16 = {
        let mut hasher = DefaultHasher::new();
        hasher.write(name_str.as_bytes());
        hasher.write(env!("CARGO_PKG_VERSION_MAJOR").as_bytes());
        hasher.finish() as u16
    };

    // Check if the name or the tag already exist. If so, throw a compile time error
    let mut registry_guard = get_registry();
    let registry = registry_guard.get_or_insert_with(HashMap::new);
    if registry.contains_key(&name_str) {
        let error = format!("Protocol name '{name_str}' was already used. Use a different name.");
        return quote! { compile_error!(#error); }.into();
    }
    if registry.values().any(|&v| v == tag) {
        let error = format!("Tag collision detected for protocol name '{name_str}' (tag: {tag}). Use a (slightly) different name.");
        return quote! { compile_error!(#error); }.into();
    }

    quote! { ProtocolInfo::new(#name_str, #tag) }.into()
}

/// Macro to check registry state at compile time (for debugging)
pub fn dump_tags(_input: TokenStream) -> TokenStream {
    let registry_guard = get_registry();
    if let Some(registry) = registry_guard.as_ref() {
        let entries: Vec<_> = registry.iter().collect();
        eprintln!("Tag Registry:");
        for (name, tag) in entries {
            eprintln!("  {name} -> {tag}");
        }
    }
    quote! { () }.into()
}
