//! WI1 phase 5a — gate! atom additions for declarative `Requirement::PickEntities`
//! `where_clause` authoring: `is_self()` and `in_range(<int>)`.

use maat_sdk::predicate::{EntityRef, MetricKind, Op, Predicate, Value};
use maat_sdk_derive::gate;

// ---------------------------------------------------------------------------
// is_self()
// ---------------------------------------------------------------------------

#[test]
fn is_self_lowers_to_refeq_target_self() {
    let p = gate!(is_self());
    let expected = Predicate::RefEq(EntityRef::Target, EntityRef::Self_);
    assert_eq!(p, expected);
}

#[test]
fn is_self_negation_composes() {
    let p = gate!(!is_self());
    let expected = Predicate::Not(Box::new(Predicate::RefEq(
        EntityRef::Target,
        EntityRef::Self_,
    )));
    assert_eq!(p, expected);
}

#[test]
fn is_self_inside_conjunction() {
    let p = gate!(in_range(60) && !is_self());
    let expected = Predicate::All(vec![
        Predicate::Cmp(
            Value::Distance {
                from: EntityRef::Observer,
                to: EntityRef::Target,
                kind: MetricKind::MotionLimited,
            },
            Op::Le,
            Value::Const(60),
        ),
        Predicate::Not(Box::new(Predicate::RefEq(
            EntityRef::Target,
            EntityRef::Self_,
        ))),
    ]);
    assert_eq!(p, expected);
}

// ---------------------------------------------------------------------------
// in_range(<int>)
// ---------------------------------------------------------------------------

#[test]
fn in_range_lowers_to_distance_observer_target_le() {
    let p = gate!(in_range(120));
    let expected = Predicate::Cmp(
        Value::Distance {
            from: EntityRef::Observer,
            to: EntityRef::Target,
            kind: MetricKind::MotionLimited,
        },
        Op::Le,
        Value::Const(120),
    );
    assert_eq!(p, expected);
}

#[test]
fn in_range_zero_compiles() {
    let _ = gate!(in_range(0));
}

#[test]
fn in_range_supports_typical_distances() {
    let _ = gate!(in_range(5));
    let _ = gate!(in_range(30));
    let _ = gate!(in_range(120));
}

// ---------------------------------------------------------------------------
// RefEq evaluation semantics
// ---------------------------------------------------------------------------

#[test]
fn refeq_eval_true_when_refs_resolve_to_same_id() {
    use maat_sdk::predicate::eval::EvalContext;
    use maat_sdk::types::EntityId;

    struct FixedCtx {
        self_id: EntityId,
        target_id: EntityId,
    }
    impl EvalContext for FixedCtx {
        fn visible(&self, _observer: EntityId, _target: EntityId) -> bool { false }
        fn resolve_self(&self) -> Option<EntityId> { Some(self.self_id) }
        fn resolve_target(&self) -> Option<EntityId> { Some(self.target_id) }
        fn resolve_observer(&self) -> Option<EntityId> { None }
        fn resolve_owner(&self, _of: EntityId) -> Option<EntityId> { None }
        fn type_of(&self, _: EntityId) -> Option<maat_sdk::types::EntityTypeId> { None }
        fn pool(&self, _: EntityId, _: &maat_sdk::PoolId,
                _: maat_sdk::predicate::PoolMode) -> Option<i64> { None }
        fn attached_to(&self, _: EntityId) -> Vec<EntityId> { vec![] }
        fn contained_in(&self, _: EntityId) -> Vec<EntityId> { vec![] }
    }

    let same = EntityId::from_raw(42);
    let ctx = FixedCtx { self_id: same, target_id: same };
    let p = gate!(is_self());
    assert!(p.evaluate(&ctx));
}

#[test]
fn refeq_eval_false_when_refs_resolve_to_different_ids() {
    use maat_sdk::predicate::eval::EvalContext;
    use maat_sdk::types::EntityId;

    struct FixedCtx {
        self_id: EntityId,
        target_id: EntityId,
    }
    impl EvalContext for FixedCtx {
        fn visible(&self, _observer: EntityId, _target: EntityId) -> bool { false }
        fn resolve_self(&self) -> Option<EntityId> { Some(self.self_id) }
        fn resolve_target(&self) -> Option<EntityId> { Some(self.target_id) }
        fn resolve_observer(&self) -> Option<EntityId> { None }
        fn resolve_owner(&self, _of: EntityId) -> Option<EntityId> { None }
        fn type_of(&self, _: EntityId) -> Option<maat_sdk::types::EntityTypeId> { None }
        fn pool(&self, _: EntityId, _: &maat_sdk::PoolId,
                _: maat_sdk::predicate::PoolMode) -> Option<i64> { None }
        fn attached_to(&self, _: EntityId) -> Vec<EntityId> { vec![] }
        fn contained_in(&self, _: EntityId) -> Vec<EntityId> { vec![] }
    }

    let ctx = FixedCtx {
        self_id: EntityId::from_raw(1),
        target_id: EntityId::from_raw(2),
    };
    let p = gate!(is_self());
    assert!(!p.evaluate(&ctx));
}

#[test]
fn refeq_eval_false_when_either_ref_is_null() {
    use maat_sdk::predicate::eval::EvalContext;
    use maat_sdk::types::EntityId;

    struct OneSideCtx;
    impl EvalContext for OneSideCtx {
        fn visible(&self, _observer: EntityId, _target: EntityId) -> bool { false }
        fn resolve_self(&self) -> Option<EntityId> { Some(EntityId::from_raw(7)) }
        fn resolve_target(&self) -> Option<EntityId> { None } // unresolved
        fn resolve_observer(&self) -> Option<EntityId> { None }
        fn resolve_owner(&self, _of: EntityId) -> Option<EntityId> { None }
        fn type_of(&self, _: EntityId) -> Option<maat_sdk::types::EntityTypeId> { None }
        fn pool(&self, _: EntityId, _: &maat_sdk::PoolId,
                _: maat_sdk::predicate::PoolMode) -> Option<i64> { None }
        fn attached_to(&self, _: EntityId) -> Vec<EntityId> { vec![] }
        fn contained_in(&self, _: EntityId) -> Vec<EntityId> { vec![] }
    }

    let p = gate!(is_self());
    assert!(!p.evaluate(&OneSideCtx));
}
