//! Map and Set behavior, including the `$add` goldens and reference equality.

mod support;

use immutability_helper::{update, Spec, Value};
use indexmap::{IndexMap, IndexSet};
use support::to_value;

/// Read a Map value or panic.
fn as_map(value: &Value) -> &IndexMap<Value, Value> {
    match value {
        Value::Map(m) => m,
        other => panic!("expected a Map, got {other:?}"),
    }
}

/// Read a Set value or panic.
fn as_set(value: &Value) -> &IndexSet<Value> {
    match value {
        Value::Set(s) => s,
        other => panic!("expected a Set, got {other:?}"),
    }
}

#[test]
fn add_on_map_matches_golden() {
    // Map{1:2, 3:4} + $add [[5,6]] => Map{1:2, 3:4, 5:6}.
    let state = Value::map(vec![
        (Value::from(1), Value::from(2)),
        (Value::from(3), Value::from(4)),
    ]);
    let pair = Value::array(vec![Value::from(5), Value::from(6)]);
    let result = update(&state, &Spec::add(vec![pair])).unwrap();

    let map = as_map(&result);
    let golden: Vec<(i64, i64)> = vec![(1, 2), (3, 4), (5, 6)];
    let got: Vec<(i64, i64)> = map.iter().map(|(k, v)| (num(k), num(v))).collect();
    assert_eq!(got, golden);
}

#[test]
fn add_on_set_matches_golden() {
    // Set{1,2,3,4} + $add [5,6] => Set{1,2,3,4,5,6}.
    let state = Value::set((1..=4).map(Value::from));
    let result = update(&state, &Spec::add(vec![Value::from(5), Value::from(6)])).unwrap();

    let set = as_set(&result);
    let got: Vec<i64> = set.iter().map(num).collect();
    assert_eq!(got, vec![1, 2, 3, 4, 5, 6]);
}

#[test]
fn add_duplicate_map_item_keeps_reference() {
    let state = Value::map(vec![(Value::from(1), Value::from(2))]);
    let pair = Value::array(vec![Value::from(1), Value::from(2)]);
    let result = update(&state, &Spec::add(vec![pair])).unwrap();
    assert!(result.same_value(&state));
}

#[test]
fn add_duplicate_set_item_keeps_reference() {
    let state = Value::set([Value::from(1), Value::from(2)]);
    let result = update(&state, &Spec::add(vec![Value::from(2)])).unwrap();
    assert!(result.same_value(&state));
}

#[test]
fn remove_on_map_drops_keys() {
    let state = Value::map(vec![
        (Value::from(1), Value::from(2)),
        (Value::from(3), Value::from(4)),
        (Value::from(5), Value::from(6)),
    ]);
    let result = update(&state, &Spec::remove(vec![Value::from(1), Value::from(5)])).unwrap();
    let map = as_map(&result);
    assert!(!map.contains_key(&Value::from(1)));
    assert!(map.contains_key(&Value::from(3)));
    assert_eq!(map.get(&Value::from(3)), Some(&Value::from(4)));
    assert!(!map.contains_key(&Value::from(6)));
}

#[test]
fn remove_on_set_drops_values() {
    let state = Value::set((1..=4).map(Value::from));
    let result = update(&state, &Spec::remove(vec![Value::from(2), Value::from(3)])).unwrap();
    let set = as_set(&result);
    assert!(set.contains(&Value::from(1)));
    assert!(!set.contains(&Value::from(2)));
}

#[test]
fn remove_absent_key_keeps_reference() {
    let set = Value::set([Value::from(1), Value::from(2)]);
    let r1 = update(&set, &Spec::remove(vec![Value::from(9)])).unwrap();
    assert!(r1.same_value(&set));

    let map = Value::map(vec![(Value::from("a"), Value::from(1))]);
    let r2 = update(&map, &Spec::remove(vec![Value::from("z")])).unwrap();
    assert!(r2.same_value(&map));
}

#[test]
fn set_on_map_does_not_mutate_original() {
    let state = Value::map(vec![
        (Value::from("foo"), Value::from("FOO")),
        (Value::from("bar"), Value::from("BAR")),
    ]);
    let modified = update(&state, &Spec::at("foo", Spec::set(Value::from("OFO")))).unwrap();

    // The original keeps FOO, the result has OFO, and they are distinct.
    let orig = to_value(&serde_json::json!({ "$$map": [["foo", "FOO"], ["bar", "BAR"]] }));
    assert_eq!(state, orig);
    assert!(!state.same_value(&modified));
    let map = as_map(&modified);
    assert_eq!(map.get(&Value::from("foo")), Some(&Value::from("OFO")));
}

#[test]
fn map_merge_keeps_reference_when_unchanged() {
    // Map{a: {b: 1}} with {a: {$merge: {}}} keeps the reference.
    let inner = Value::object(vec![("b", Value::from(1))]);
    let original = Value::map(vec![(Value::from("a"), inner)]);

    let same = update(
        &original,
        &Spec::at("a", Spec::merge(Vec::<(String, Value)>::new())),
    )
    .unwrap();
    assert!(same.same_value(&original));

    let changed = update(
        &original,
        &Spec::at("a", Spec::merge(vec![("c".to_string(), Value::from(2))])),
    )
    .unwrap();
    assert!(!changed.same_value(&original));
}

/// Pull an integer out of a numeric value.
fn num(value: &Value) -> i64 {
    match value {
        Value::Number(n) => *n as i64,
        other => panic!("expected a number, got {other:?}"),
    }
}
