//! Mutation-driven merge of a value into a context's bundle slot.
//!
//! Mirrors `spliceSlot` in `frontends/mizan-base/src/index.ts`. The server
//! has already resolved which slot the value lands in (by matching the
//! mutation's return type against each context function's return type),
//! so the kernel does no inference — it writes directly to `bundle[slot]`.
//!
//! Rules:
//! - If the existing slot is an array and the new value is also an array,
//!   the array replaces the slot wholesale.
//! - If the existing slot is an array and the new value is an object with
//!   an `id` field, upsert by `id` — replace the matching entry in place
//!   or append.
//! - Otherwise the slot is replaced with the new value.

use serde_json::Value;


pub fn splice_slot(slot: &Value, value: &Value) -> Value {
    if let Value::Array(slot_arr) = slot {
        if let Value::Array(_) = value {
            return value.clone();
        }
        if let Some(id) = value.get("id") {
            let mut next = slot_arr.clone();
            let idx = next.iter().position(|item| item.get("id") == Some(id));
            match idx {
                Some(i) => next[i] = value.clone(),
                None => next.push(value.clone()),
            }
            return Value::Array(next);
        }
    }
    value.clone()
}


/// Apply a merge entry to the bundle of a context entry. Returns the new
/// bundle, or `None` if the slot wasn't present in the bundle (caller
/// should treat that as a no-op so server-driven merges into stale
/// caches don't fabricate slots).
pub fn merge_into_bundle(bundle: &Value, slot_name: &str, value: &Value) -> Option<Value> {
    let obj = bundle.as_object()?;
    if !obj.contains_key(slot_name) {
        return None;
    }
    let mut next = obj.clone();
    let spliced = splice_slot(obj.get(slot_name)?, value);
    next.insert(slot_name.to_string(), spliced);
    Some(Value::Object(next))
}


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

    #[test]
    fn replaces_scalar_slot() {
        let slot = json!(1);
        let value = json!(2);
        assert_eq!(splice_slot(&slot, &value), json!(2));
    }

    #[test]
    fn upserts_array_by_id() {
        let slot = json!([{"id": 1, "name": "a"}, {"id": 2, "name": "b"}]);
        let value = json!({"id": 1, "name": "A"});
        assert_eq!(
            splice_slot(&slot, &value),
            json!([{"id": 1, "name": "A"}, {"id": 2, "name": "b"}]),
        );
    }

    #[test]
    fn appends_when_id_not_in_array() {
        let slot = json!([{"id": 1, "name": "a"}]);
        let value = json!({"id": 9, "name": "z"});
        assert_eq!(
            splice_slot(&slot, &value),
            json!([{"id": 1, "name": "a"}, {"id": 9, "name": "z"}]),
        );
    }

    #[test]
    fn array_replaces_array() {
        let slot = json!([1, 2, 3]);
        let value = json!([7, 8]);
        assert_eq!(splice_slot(&slot, &value), json!([7, 8]));
    }

    #[test]
    fn merge_into_bundle_skips_missing_slot() {
        let bundle = json!({"existing": 1});
        let value = json!(42);
        assert!(merge_into_bundle(&bundle, "missing", &value).is_none());
    }

    #[test]
    fn merge_into_bundle_updates_present_slot() {
        let bundle = json!({"user_profile": {"id": 1, "name": "old"}});
        let value = json!({"id": 1, "name": "new"});
        let merged = merge_into_bundle(&bundle, "user_profile", &value).unwrap();
        assert_eq!(merged["user_profile"]["name"], "new");
    }
}
