//! Sparse node metadata store for macro boundary analysis.
//!
//! This module provides [`NodeMetadataStore`], a sparse metadata store keyed by
//! full [`NodeId`] (index + generation) to prevent stale metadata when the
//! generational arena reuses a slot index with a new generation.
//!
//! Only nodes with macro-relevant metadata get entries, keeping memory overhead
//! proportional to the number of macro-annotated symbols rather than total node count.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use super::super::node::id::NodeId;

/// Optional metadata for nodes that participate in macro boundary analysis.
///
/// Stored separately from [`NodeEntry`] to avoid bloating the arena for the
/// majority of nodes that don't need macro metadata.
///
/// Each field is `Option` (or `Vec` with empty default) so only relevant
/// metadata consumes space in the serialized representation.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct MacroNodeMetadata {
    /// Whether this symbol was generated by macro expansion.
    pub macro_generated: Option<bool>,

    /// Qualified name of the macro that generated this symbol.
    pub macro_source: Option<String>,

    /// The cfg predicate string (e.g., `"test"`, `"feature = \"serde\""`)
    pub cfg_condition: Option<String>,

    /// Whether this cfg is active (`None` = unknown, requires external config).
    pub cfg_active: Option<bool>,

    /// Proc-macro kind for proc-macro function nodes.
    pub proc_macro_kind: Option<ProcMacroFunctionKind>,

    /// Whether expansion data came from cache vs live `cargo expand`.
    pub expansion_cached: Option<bool>,

    /// Unresolved attribute paths that could not be positively identified
    /// as proc-macro attributes. Stored for potential future resolution.
    pub unresolved_attributes: Vec<String>,
}

/// Classification of proc-macro function types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProcMacroFunctionKind {
    /// `#[proc_macro_derive(Name)]` — generates impls for structs/enums.
    Derive,
    /// `#[proc_macro_attribute]` — transforms annotated items.
    Attribute,
    /// `#[proc_macro]` — function-like `my_macro!(...)` invocation.
    FunctionLike,
}

/// Sparse metadata store keyed by full `NodeId` (index + generation).
///
/// Uses `(u32, u64)` tuple key to prevent stale metadata when the
/// generational arena reuses a slot index with a new generation.
/// A lookup with `NodeId { index: 5, generation: 3 }` will NOT match metadata
/// stored for `NodeId { index: 5, generation: 2 }`.
///
/// # Memory characteristics
///
/// For a typical large codebase (100K nodes), only ~5-10% of nodes have macro
/// metadata. A store with 10K entries at ~200 bytes each = ~2MB, which is
/// acceptable given snapshots are already 10-50MB.
///
/// # Serialization
///
/// The in-memory representation uses `HashMap` for O(1) lookups. For postcard
/// serialization (which doesn't support tuple keys natively), we serialize as
/// a `Vec` of `NodeMetadataEntry` structs with explicit `index` and `generation`
/// fields, then reconstruct the `HashMap` on deserialization.
#[derive(Debug, Clone, Default)]
pub struct NodeMetadataStore {
    /// Metadata entries keyed by `(NodeId::index(), NodeId::generation())`.
    entries: HashMap<(u32, u64), MacroNodeMetadata>,
}

/// Serialization wrapper for a single metadata entry.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct NodeMetadataEntry {
    index: u32,
    generation: u64,
    metadata: MacroNodeMetadata,
}

impl Serialize for NodeMetadataStore {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let entries: Vec<NodeMetadataEntry> = self
            .entries
            .iter()
            .map(|(&(index, generation), metadata)| NodeMetadataEntry {
                index,
                generation,
                metadata: metadata.clone(),
            })
            .collect();
        entries.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for NodeMetadataStore {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let entries: Vec<NodeMetadataEntry> = Vec::deserialize(deserializer)?;
        let map = entries
            .into_iter()
            .map(|e| ((e.index, e.generation), e.metadata))
            .collect();
        Ok(Self { entries: map })
    }
}

impl NodeMetadataStore {
    /// Create a new empty metadata store.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Get metadata for a node, if any exists.
    ///
    /// Returns `None` if no metadata is stored for this node, or if the
    /// generation doesn't match (indicating a stale reference).
    #[must_use]
    pub fn get(&self, node_id: NodeId) -> Option<&MacroNodeMetadata> {
        self.entries.get(&(node_id.index(), node_id.generation()))
    }

    /// Get mutable metadata for a node, if any exists.
    #[must_use]
    pub fn get_mut(&mut self, node_id: NodeId) -> Option<&mut MacroNodeMetadata> {
        self.entries
            .get_mut(&(node_id.index(), node_id.generation()))
    }

    /// Insert metadata for a node, replacing any existing entry.
    pub fn insert(&mut self, node_id: NodeId, metadata: MacroNodeMetadata) {
        self.entries
            .insert((node_id.index(), node_id.generation()), metadata);
    }

    /// Get or insert default metadata for a node.
    pub fn get_or_insert_default(&mut self, node_id: NodeId) -> &mut MacroNodeMetadata {
        self.entries
            .entry((node_id.index(), node_id.generation()))
            .or_default()
    }

    /// Remove metadata for a node.
    pub fn remove(&mut self, node_id: NodeId) -> Option<MacroNodeMetadata> {
        self.entries
            .remove(&(node_id.index(), node_id.generation()))
    }

    /// Returns the number of nodes with metadata.
    #[must_use]
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Returns true if no nodes have metadata.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// Iterate over all metadata entries.
    pub fn iter(&self) -> impl Iterator<Item = ((u32, u64), &MacroNodeMetadata)> {
        self.entries.iter().map(|(&k, v)| (k, v))
    }

    /// Merge another metadata store into this one.
    ///
    /// Entries from `other` overwrite existing entries with the same key.
    pub fn merge(&mut self, other: &NodeMetadataStore) {
        for (&key, value) in &other.entries {
            self.entries.insert(key, value.clone());
        }
    }
}

impl PartialEq for NodeMetadataStore {
    fn eq(&self, other: &Self) -> bool {
        self.entries == other.entries
    }
}

impl Eq for NodeMetadataStore {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_metadata_store_basic_operations() {
        let mut store = NodeMetadataStore::new();
        assert!(store.is_empty());
        assert_eq!(store.len(), 0);

        let node = NodeId::new(5, 1);
        let metadata = MacroNodeMetadata {
            macro_generated: Some(true),
            macro_source: Some("derive_Debug".to_string()),
            ..Default::default()
        };

        store.insert(node, metadata.clone());
        assert_eq!(store.len(), 1);
        assert!(!store.is_empty());

        let retrieved = store.get(node).unwrap();
        assert_eq!(retrieved.macro_generated, Some(true));
        assert_eq!(retrieved.macro_source.as_deref(), Some("derive_Debug"));
    }

    #[test]
    fn test_metadata_full_nodeid_key() {
        let mut store = NodeMetadataStore::new();

        let node_gen1 = NodeId::new(5, 1);
        let node_gen2 = NodeId::new(5, 2);

        store.insert(
            node_gen1,
            MacroNodeMetadata {
                macro_generated: Some(true),
                ..Default::default()
            },
        );

        // Same index, different generation → should NOT match
        assert!(store.get(node_gen2).is_none());

        // Same index, same generation → should match
        assert!(store.get(node_gen1).is_some());
    }

    #[test]
    fn test_metadata_slot_reuse_no_stale_data() {
        let mut store = NodeMetadataStore::new();

        // Simulate: node at index 5 gen 1 has metadata
        let old_node = NodeId::new(5, 1);
        store.insert(
            old_node,
            MacroNodeMetadata {
                cfg_condition: Some("test".to_string()),
                ..Default::default()
            },
        );

        // Simulate: slot 5 is reused with generation 2 (new node)
        let new_node = NodeId::new(5, 2);

        // New node should NOT see old metadata
        assert!(store.get(new_node).is_none());

        // Old node still accessible
        assert_eq!(
            store.get(old_node).unwrap().cfg_condition.as_deref(),
            Some("test")
        );
    }

    #[test]
    fn test_metadata_store_postcard_roundtrip() {
        let mut store = NodeMetadataStore::new();

        store.insert(
            NodeId::new(1, 0),
            MacroNodeMetadata {
                macro_generated: Some(true),
                macro_source: Some("derive_Debug".to_string()),
                cfg_condition: Some("test".to_string()),
                cfg_active: Some(true),
                proc_macro_kind: Some(ProcMacroFunctionKind::Derive),
                expansion_cached: Some(false),
                unresolved_attributes: vec!["my_attr".to_string()],
            },
        );

        store.insert(
            NodeId::new(42, 3),
            MacroNodeMetadata {
                cfg_condition: Some("feature = \"serde\"".to_string()),
                ..Default::default()
            },
        );

        let bytes = postcard::to_allocvec(&store).expect("serialize");
        let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");

        assert_eq!(store, deserialized);
    }

    #[test]
    fn test_empty_metadata_store_zero_overhead() {
        let store = NodeMetadataStore::new();
        let bytes = postcard::to_allocvec(&store).expect("serialize");

        // Empty HashMap serializes to a single varint length of 0
        assert!(
            bytes.len() <= 2,
            "Empty store should serialize to minimal bytes, got {} bytes",
            bytes.len()
        );
    }

    #[test]
    fn test_metadata_store_merge() {
        let mut store1 = NodeMetadataStore::new();
        let mut store2 = NodeMetadataStore::new();

        store1.insert(
            NodeId::new(1, 0),
            MacroNodeMetadata {
                macro_generated: Some(true),
                ..Default::default()
            },
        );

        store2.insert(
            NodeId::new(2, 0),
            MacroNodeMetadata {
                cfg_condition: Some("test".to_string()),
                ..Default::default()
            },
        );

        store1.merge(&store2);
        assert_eq!(store1.len(), 2);
        assert!(store1.get(NodeId::new(1, 0)).is_some());
        assert!(store1.get(NodeId::new(2, 0)).is_some());
    }

    #[test]
    fn test_proc_macro_function_kind_serde() {
        let kinds = [
            ProcMacroFunctionKind::Derive,
            ProcMacroFunctionKind::Attribute,
            ProcMacroFunctionKind::FunctionLike,
        ];

        for kind in kinds {
            let bytes = postcard::to_allocvec(&kind).expect("serialize");
            let deserialized: ProcMacroFunctionKind =
                postcard::from_bytes(&bytes).expect("deserialize");
            assert_eq!(kind, deserialized);
        }
    }

    #[test]
    fn test_metadata_get_or_insert_default() {
        let mut store = NodeMetadataStore::new();
        let node = NodeId::new(10, 0);

        // First access creates default
        let meta = store.get_or_insert_default(node);
        meta.cfg_condition = Some("test".to_string());

        // Second access returns existing
        let meta = store.get(node).unwrap();
        assert_eq!(meta.cfg_condition.as_deref(), Some("test"));
    }

    #[test]
    fn test_metadata_remove() {
        let mut store = NodeMetadataStore::new();
        let node = NodeId::new(1, 0);

        store.insert(
            node,
            MacroNodeMetadata {
                macro_generated: Some(true),
                ..Default::default()
            },
        );

        assert!(store.get(node).is_some());
        let removed = store.remove(node);
        assert!(removed.is_some());
        assert!(store.get(node).is_none());
        assert!(store.is_empty());
    }

    #[test]
    fn test_metadata_store_large_scale() {
        let mut store = NodeMetadataStore::new();

        // Insert 10K entries (simulating ~10% of a 100K-node codebase)
        for i in 0..10_000u32 {
            store.insert(
                NodeId::new(i, 0),
                MacroNodeMetadata {
                    cfg_condition: Some(format!("feature_{i}")),
                    ..Default::default()
                },
            );
        }

        assert_eq!(store.len(), 10_000);

        // Verify O(1) lookups
        assert!(store.get(NodeId::new(0, 0)).is_some());
        assert!(store.get(NodeId::new(5_000, 0)).is_some());
        assert!(store.get(NodeId::new(9_999, 0)).is_some());
        assert!(store.get(NodeId::new(10_000, 0)).is_none());

        // Verify round-trip
        let bytes = postcard::to_allocvec(&store).expect("serialize");
        let deserialized: NodeMetadataStore = postcard::from_bytes(&bytes).expect("deserialize");
        assert_eq!(store, deserialized);
    }
}
