use crate::ir::NodeId;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;

/// Monotonic counter for compact, stable node-ID serialization.
static NEXT_ID: AtomicU64 = AtomicU64::new(1);

/// Maps SlotMap `NodeId` keys to compact integer strings.
/// Once a NodeId is assigned a string, it keeps it for the lifetime of the
/// process.  This avoids depending on slotmap's `Debug` output format.
static NODE_ID_MAP: Mutex<Option<HashMap<NodeId, String>>> = Mutex::new(None);

/// Stable, compact serialization for NodeId.
///
/// Returns a short numeric string (e.g. `"1"`, `"42"`) instead of the
/// previous `"NodeId(0v1)"` Debug format.  The mapping is deterministic
/// per NodeId for the lifetime of the process.
#[inline]
pub fn node_id_str(id: NodeId) -> String {
    let mut guard = NODE_ID_MAP.lock().unwrap();
    let map = guard.get_or_insert_with(HashMap::new);
    map.entry(id)
        .or_insert_with(|| NEXT_ID.fetch_add(1, Ordering::Relaxed).to_string())
        .clone()
}

/// Platform-agnostic patch operations for updating the UI
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Patch {
    /// Create a new node
    #[serde(rename_all = "camelCase")]
    Create {
        id: String,
        element_type: String,
        props: indexmap::IndexMap<String, Value>,
    },

    /// Set a property on an existing node
    #[serde(rename_all = "camelCase")]
    SetProp {
        id: String,
        name: String,
        value: Value,
    },

    /// Remove a property from an existing node
    #[serde(rename_all = "camelCase")]
    RemoveProp { id: String, name: String },

    /// Set text content of a node
    #[serde(rename_all = "camelCase")]
    SetText { id: String, text: String },

    /// Insert a node into the tree
    #[serde(rename_all = "camelCase")]
    Insert {
        parent_id: String,
        id: String,
        before_id: Option<String>,
    },

    /// Move a node to a new position
    #[serde(rename_all = "camelCase")]
    Move {
        parent_id: String,
        id: String,
        before_id: Option<String>,
    },

    /// Remove a node from the tree
    #[serde(rename_all = "camelCase")]
    Remove { id: String },
}

impl Patch {
    pub fn create(
        id: NodeId,
        element_type: String,
        props: indexmap::IndexMap<String, Value>,
    ) -> Self {
        Self::Create {
            id: node_id_str(id),
            element_type,
            props,
        }
    }

    pub fn set_prop(id: NodeId, name: String, value: Value) -> Self {
        Self::SetProp {
            id: node_id_str(id),
            name,
            value,
        }
    }

    pub fn remove_prop(id: NodeId, name: String) -> Self {
        Self::RemoveProp {
            id: node_id_str(id),
            name,
        }
    }

    pub fn set_text(id: NodeId, text: String) -> Self {
        Self::SetText {
            id: node_id_str(id),
            text,
        }
    }

    pub fn insert(parent_id: NodeId, id: NodeId, before_id: Option<NodeId>) -> Self {
        Self::Insert {
            parent_id: node_id_str(parent_id),
            id: node_id_str(id),
            before_id: before_id.map(node_id_str),
        }
    }

    /// Insert a root node into the "root" container
    pub fn insert_root(id: NodeId) -> Self {
        Self::Insert {
            parent_id: "root".to_string(),
            id: node_id_str(id),
            before_id: None,
        }
    }

    pub fn move_node(parent_id: NodeId, id: NodeId, before_id: Option<NodeId>) -> Self {
        Self::Move {
            parent_id: node_id_str(parent_id),
            id: node_id_str(id),
            before_id: before_id.map(node_id_str),
        }
    }

    pub fn remove(id: NodeId) -> Self {
        Self::Remove {
            id: node_id_str(id),
        }
    }
}
