/// MutationObserver simulation.
///
/// The sandbox already observes ALL DOM mutations. MutationObserver
/// exposes that observation stream back to JavaScript — when JS
/// registers a MutationObserver, we feed it the mutations that
/// occur during subsequent script execution.
///
use crate::dom::NodeId;

/// A registered MutationObserver.
#[derive(Debug, Clone)]
pub struct MutationObserverRegistration {
    /// Target node being observed.
    pub target: NodeId,
    /// Observation options.
    pub options: MutationObserverOptions,
    /// Callback function reference (opaque ID for JS).
    pub callback_id: u32,
}

/// MutationObserver init options.
#[derive(Debug, Clone, Default)]
pub struct MutationObserverOptions {
    pub child_list: bool,
    pub attributes: bool,
    pub character_data: bool,
    pub subtree: bool,
    pub attribute_old_value: bool,
    pub character_data_old_value: bool,
    pub attribute_filter: Vec<String>,
}

impl MutationObserverOptions {
    pub fn from_json(json: &str) -> Self {
        #[derive(serde::Deserialize, Default)]
        struct Opts {
            #[serde(default, alias = "childList")]
            child_list: bool,
            #[serde(default)]
            attributes: bool,
            #[serde(default, alias = "characterData")]
            character_data: bool,
            #[serde(default)]
            subtree: bool,
            #[serde(default, alias = "attributeOldValue")]
            attribute_old_value: bool,
            #[serde(default, alias = "characterDataOldValue")]
            character_data_old_value: bool,
            #[serde(default, alias = "attributeFilter")]
            attribute_filter: Vec<String>,
        }

        let opts: Opts = serde_json::from_str(json).unwrap_or_default();
        Self {
            child_list: opts.child_list,
            attributes: opts.attributes,
            character_data: opts.character_data,
            subtree: opts.subtree,
            attribute_old_value: opts.attribute_old_value,
            character_data_old_value: opts.character_data_old_value,
            attribute_filter: opts.attribute_filter,
        }
    }
}

/// A DOM mutation record.
#[derive(Debug, Clone, serde::Serialize)]
pub struct MutationRecord {
    #[serde(rename = "type")]
    pub mutation_type: String,
    pub target: u32,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub added_nodes: Vec<u32>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub removed_nodes: Vec<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub attribute_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub old_value: Option<String>,
}

/// Registry of active MutationObservers.
#[derive(Debug, Default)]
pub struct MutationObserverRegistry {
    observers: Vec<MutationObserverRegistration>,
    next_id: u32,
    /// Pending records to deliver on next microtask.
    pending: Vec<(u32, Vec<MutationRecord>)>,
}

impl MutationObserverRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    /// Register a new observer. Returns callback ID.
    pub fn observe(&mut self, target: NodeId, options: MutationObserverOptions) -> u32 {
        let id = self.next_id;
        self.next_id += 1;
        self.observers.push(MutationObserverRegistration {
            target,
            options,
            callback_id: id,
        });
        id
    }

    /// Disconnect an observer by callback ID.
    pub fn disconnect(&mut self, callback_id: u32) {
        self.observers.retain(|o| o.callback_id != callback_id);
    }

    /// Notify observers of a child list change.
    pub fn notify_child_change(&mut self, target: NodeId, added: &[NodeId], removed: &[NodeId]) {
        for observer in &self.observers {
            if (observer.target == target || observer.options.subtree)
                && observer.options.child_list
            {
                let record = MutationRecord {
                    mutation_type: "childList".into(),
                    target: target.0,
                    added_nodes: added.iter().map(|n| n.0).collect(),
                    removed_nodes: removed.iter().map(|n| n.0).collect(),
                    attribute_name: None,
                    old_value: None,
                };
                self.pending.push((observer.callback_id, vec![record]));
            }
        }
    }

    /// Notify observers of an attribute change.
    pub fn notify_attribute_change(
        &mut self,
        target: NodeId,
        attribute_name: &str,
        old_value: Option<&str>,
    ) {
        for observer in &self.observers {
            if (observer.target == target || observer.options.subtree)
                && observer.options.attributes
            {
                if !observer.options.attribute_filter.is_empty()
                    && !observer
                        .options
                        .attribute_filter
                        .iter()
                        .any(|f| f == attribute_name)
                {
                    continue;
                }
                let record = MutationRecord {
                    mutation_type: "attributes".into(),
                    target: target.0,
                    added_nodes: Vec::new(),
                    removed_nodes: Vec::new(),
                    attribute_name: Some(attribute_name.to_string()),
                    old_value: if observer.options.attribute_old_value {
                        old_value.map(|s| s.to_string())
                    } else {
                        None
                    },
                };
                self.pending.push((observer.callback_id, vec![record]));
            }
        }
    }

    /// Drain pending records as JSON to deliver to JS callbacks.
    pub fn drain_pending(&mut self) -> Vec<(u32, String)> {
        let pending = std::mem::take(&mut self.pending);
        pending
            .into_iter()
            .map(|(id, records)| {
                let json = serde_json::to_string(&records).unwrap_or_default();
                (id, json)
            })
            .collect()
    }
}
