#include <api.h>

static int on_remove_count = 0;

static
void Trigger(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);
}

static
void Trigger_w_value(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);

    test_int(it->count, 1);
    test_assert(it->entities != NULL);
    test_assert(it->entities[0] != 0);

    Position *p = ecs_field(it, Position, 1);
    test_int(p->x, 10);
    test_int(p->y, 20);
}

static
void Trigger_w_value_from_entity(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);

    test_int(it->count, 1);
    test_assert(it->entities != NULL);
    test_assert(it->entities[0] == 0);
    test_assert(it->sources[0] != 0);

    Position *p = ecs_field(it, Position, 1);
    test_int(p->x, 10);
    test_int(p->y, 20);
}

static
void Trigger_w_value_instanced(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);

    test_assert(it->count > 1);

    Position *p = ecs_field(it, Position, 1);
    test_int(p->x, 10);
    test_int(p->y, 20);
}

static
void Trigger_w_filter_term(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);

    test_assert(it->entities != NULL);
    test_assert(it->entities[0] != 0);

    test_bool(it->flags & EcsIterNoData, true);
}

static
void Trigger_n_w_values(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);

    Position *p = ecs_field(it, Position, 1);

    test_assert(it->entities != NULL);

    int i;
    for (i = 0; i < it->count; i ++) {
        test_assert(it->entities[i] != 0);    
        test_int(p[i].x, 10 + i * 20);
        test_int(p[i].y, 20 + i * 20);
    }
}

static
void TriggerAdd(ecs_iter_t *it) {
    ecs_id_t id = *(ecs_id_t*)it->ctx;

    int i;
    for (i = 0; i < it->count; i ++) {
        ecs_add_id(it->world, it->entities[i], id);
    }
}

static
void TriggerRemove(ecs_iter_t *it) {
    ecs_id_t id = *(ecs_id_t*)it->ctx;

    int i;
    for (i = 0; i < it->count; i ++) {
        ecs_remove_id(it->world, it->entities[i], id);
    }
}

static
void TriggerClear(ecs_iter_t *it) {
    int i;
    for (i = 0; i < it->count; i ++) {
        ecs_clear(it->world, it->entities[i]);
    }
}

static
void TriggerDelete(ecs_iter_t *it) {
    int i;
    for (i = 0; i < it->count; i ++) {
        ecs_delete(it->world, it->entities[i]);
    }
}

void Trigger_on_add_trigger_before_table(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    /* Create entity/table after trigger, should invoke trigger */
    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_on_add_trigger_after_table(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    /* Create entity/before trigger */
    ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {TagA}
    });

    /* Create trigger after table, should send notification to table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_on_remove_trigger_before_table(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    /* Create trigger after table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    /* Create entity/table after trigger, should invoke trigger */
    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_remove_id(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_on_remove_trigger_after_table(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    /* Create entity/before trigger */
    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    /* Create trigger after table, should send notification to table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_remove_id(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_on_add_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_on_add_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_on_add_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = EcsWildcard,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add_id(world, e, TagB);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagB);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagB);

    ecs_fini(world);
}

void Trigger_on_add_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Pred, Obj),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Pred, Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Pred, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, Obj));

    ecs_fini(world);
}

void Trigger_on_add_pair_obj_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Pred, EcsWildcard),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Pred, ObjA)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Pred, ObjA));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, ObjA));

    ecs_os_zeromem(&ctx);

    ecs_add_pair(world, e, Pred, ObjB);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Pred, ObjB));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, ObjB));

    ecs_fini(world);
}

void Trigger_on_add_pair_pred_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, PredA);
    ECS_TAG(world, PredB);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, Obj),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(PredA, Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(PredA, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(PredA, Obj));

    ecs_os_zeromem(&ctx);

    ecs_add_pair(world, e, PredB, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(PredB, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(PredB, Obj));

    ecs_fini(world);
}

void Trigger_on_add_pair_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, EcsWildcard),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Pred, Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Pred, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, Obj));

    ecs_fini(world);
}

void Trigger_on_add_any(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);
    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    Probe *ctx_any = ecs_os_calloc_t(Probe);
    ecs_entity_t t_any = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = EcsAny,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = ctx_any
    });

    Probe *ctx_wc = ecs_os_calloc_t(Probe);
    ecs_entity_t t_wc = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = EcsWildcard,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = ctx_wc
    });

    Probe *ctx_wc_pair = ecs_os_calloc_t(Probe);
    ecs_entity_t t_wc_pair = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, EcsWildcard),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = ctx_wc_pair
    });

    ecs_os_zeromem(ctx_any);
    ecs_os_zeromem(ctx_wc);
    ecs_os_zeromem(ctx_wc_pair);

    ecs_new(world, Tag);
    test_int(ctx_any->invoked, 1);
    test_int(ctx_any->count, 1);
    test_int(ctx_any->system, t_any);
    test_int(ctx_any->event, EcsOnAdd);
    test_int(ctx_any->event_id, Tag);
    test_int(ctx_wc->invoked, 1);
    test_int(ctx_wc->count, 1);
    test_int(ctx_wc->system, t_wc);
    test_int(ctx_wc->event, EcsOnAdd);
    test_int(ctx_wc->event_id, Tag);
    test_int(ctx_wc_pair->invoked, 0);
    ecs_os_zeromem(ctx_any);
    ecs_os_zeromem(ctx_wc);
    ecs_os_zeromem(ctx_wc_pair);

    ecs_new_w_pair(world, Rel, Obj);
    test_int(ctx_any->invoked, 1);
    test_int(ctx_any->count, 1);
    test_int(ctx_any->system, t_any);
    test_int(ctx_any->event, EcsOnAdd);
    test_int(ctx_any->event_id, ecs_pair(Rel, Obj));
    test_int(ctx_wc->invoked, 0);
    test_int(ctx_wc_pair->invoked, 1);
    test_int(ctx_wc_pair->count, 1);
    test_int(ctx_wc_pair->system, t_wc_pair);
    test_int(ctx_wc_pair->event, EcsOnAdd);
    test_int(ctx_wc_pair->event_id, ecs_pair(Rel, Obj));

    ecs_fini(world);

    ecs_os_free(ctx_any);
    ecs_os_free(ctx_wc);
    ecs_os_free(ctx_wc_pair);
}

void Trigger_on_remove_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove_id(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_on_remove_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove(world, e, Position);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_on_remove_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = EcsWildcard,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {TagA, TagB}
    });
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_remove_id(world, e, TagB);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagB);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagB);

    ecs_fini(world);
}

void Trigger_on_remove_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Pred, Obj),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Pred, Obj)}
    });
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, Pred, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, ecs_pair(Pred, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, Obj));

    ecs_fini(world);
}

void Trigger_on_remove_pair_obj_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Pred, EcsWildcard),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Pred, ObjA), ecs_pair(Pred, ObjB)}
    });
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, Pred, ObjA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, ecs_pair(Pred, ObjA));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, ObjA));

    ecs_os_zeromem(&ctx);

    ecs_add_pair(world, e, Pred, ObjB);

    ecs_remove_pair(world, e, Pred, ObjB);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, ecs_pair(Pred, ObjB));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, ObjB));

    ecs_fini(world);
}

void Trigger_on_remove_pair_pred_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, PredA);
    ECS_TAG(world, PredB);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, Obj),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(PredA, Obj), ecs_pair(PredB, Obj)}
    });
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, PredA, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, ecs_pair(PredA, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(PredA, Obj));

    ecs_os_zeromem(&ctx);

    ecs_remove_pair(world, e, PredB, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, ecs_pair(PredB, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(PredB, Obj));

    ecs_fini(world);
}

void Trigger_on_remove_pair_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Pred);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, EcsWildcard),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Pred, Obj)}
    });
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, Pred, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, ecs_pair(Pred, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Pred, Obj));

    // Delete trigger so it doesn't go crazy while shutting down the world
    ecs_delete(world, t);

    ecs_fini(world);
}

void Trigger_wildcard_pair_w_pred_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);
    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(ecs_id(Position), EcsWildcard),
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });
    test_assert(t != 0);

    ecs_entity_t e = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_set_pair(world, e, Position, ObjA, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), ObjA));

    /* Change existing component without triggering OnSet as the callback
     * expects value {10, 20}, then add a new component with {10, 20} */
    Position *p = ecs_get_mut_pair(world, e, Position, ObjA);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);
    p->x = 30;
    p->y = 40;

    ecs_os_zeromem(&ctx);

    ecs_set_pair(world, e, Position, ObjB, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), ObjB));

    ecs_fini(world);
}

void Trigger_wildcard_pair_w_obj_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);
    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, ecs_id(Position)),
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });
    test_assert(t != 0);

    ecs_entity_t e = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_set_pair_object(world, e, RelA, Position, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(RelA, ecs_id(Position)));

    /* Change existing component without triggering OnSet as the callback
     * expects value {10, 20}, then add a new component with {10, 20} */
    Position *p = ecs_get_mut_pair_second(world, e, RelA, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);
    p->x = 30;
    p->y = 40;

    ecs_os_zeromem(&ctx);

    ecs_set_pair_object(world, e, RelB, Position, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(RelB, ecs_id(Position)));

    ecs_fini(world);
}


void Trigger_on_set_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_set(world, e, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_on_set_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = EcsWildcard,
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_set(world, e, Position, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_on_set_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(ecs_id(Position), Obj),
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(ecs_id(Position), Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_set_pair(world, e, Position, Obj, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj));

    ecs_fini(world);
}

void Trigger_on_set_pair_w_obj_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(ecs_id(Position), EcsWildcard),
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(ecs_id(Position), Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_set_pair(world, e, Position, Obj, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj));

    ecs_fini(world);
}

void Trigger_on_set_pair_pred_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, Obj),
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(ecs_id(Position), Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_set_pair(world, e, Position, Obj, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj));

    ecs_fini(world);
}

void Trigger_on_set_pair_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Obj);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, EcsWildcard),
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    test_int(ctx.invoked, 1); /* Triggers for self */
    ecs_os_zeromem(&ctx);

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(ecs_id(Position), Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_set_pair(world, e, Position, Obj, {10, 20});

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_pair(ecs_id(Position), Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(ecs_id(Position), Obj));

    ecs_fini(world);
}

void Trigger_on_add_remove(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd, EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_remove_id(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_on_set_component_after_modified(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_modified(world, e, Position);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_un_set_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .events = {EcsUnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_remove(world, e, Position);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsUnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_un_set_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = EcsWildcard,
        .events = {EcsUnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_remove(world, e, Position);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsUnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_un_set_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    // UnSet only works on components
    ecs_set(world, Rel, EcsComponent, {.size = 1, .alignment = 1});

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, Obj),
        .events = {EcsUnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, Rel, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsUnSet);
    test_int(ctx.event_id, ecs_pair(Rel, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Rel, Obj));

    ecs_fini(world);
}

void Trigger_un_set_pair_w_obj_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    // UnSet only works on components
    ecs_set(world, Rel, EcsComponent, {.size = 1, .alignment = 1});

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard),
        .events = {EcsUnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, Rel, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsUnSet);
    test_int(ctx.event_id, ecs_pair(Rel, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Rel, Obj));

    ecs_fini(world);
}

void Trigger_un_set_pair_pred_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    // UnSet only works on components
    ecs_set(world, Rel, EcsComponent, {.size = 1, .alignment = 1});

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, Obj),
        .events = {EcsUnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, Rel, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsUnSet);
    test_int(ctx.event_id, ecs_pair(Rel, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Rel, Obj));

    ecs_fini(world);
}

void Trigger_un_set_pair_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    // UnSet only works on components
    ecs_set(world, Rel, EcsComponent, {.size = 1, .alignment = 1});

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(EcsWildcard, EcsWildcard),
        .events = {EcsUnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)}
    });
    test_assert(e != 0);

    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, Rel, Obj);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsUnSet);
    test_int(ctx.event_id, ecs_pair(Rel, Obj));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], ecs_pair(Rel, Obj));

    // Delete trigger so it doesn't go crazy while shutting down the world
    ecs_delete(world, t);

    ecs_fini(world);
}

void Trigger_on_add_not_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].oper = EcsNot,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add_id(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_remove_not_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].oper = EcsNot,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_remove(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_add_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].src.flags = EcsUp,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_no_comp = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_no_comp);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);
    test_int(ctx.s[0][0], base);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_add_superset_2_levels(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].src.flags = EcsUp,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_no_comp = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_entity_t base_of_base = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new_w_pair(world, EcsIsA, base_of_base);
    test_int(ctx.invoked, 1);
    ctx = (Probe){0};

    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_no_comp);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);
    test_int(ctx.s[0][0], base_of_base);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_remove_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].src.flags = EcsUp,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_no_comp = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_no_comp);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_add_superset_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = TagA,
            .src.trav = EcsChildOf,
            .src.flags = EcsUp
        },
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_no_comp = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, base_no_comp);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsChildOf, base);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_remove_superset_childof(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = TagA,
            .src.trav = EcsChildOf,
            .src.flags = EcsUp
        },
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_no_comp = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, base_no_comp);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsChildOf, base);
    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, EcsChildOf, base);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_add_self_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].src.flags = EcsSelf | EcsUp,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_no_comp = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new(world, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], base);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_no_comp);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_remove_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_remove_self_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].src.flags = EcsSelf | EcsUp,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_no_comp = ecs_new_id(world);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_os_zeromem(&ctx);

    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_no_comp);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);
    test_int(ctx.invoked, 0);
    
    ecs_remove(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_add_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 0);

    ecs_remove_pair(world, e, EcsIsA, base);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_add_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_add_id(world, e, TagA);

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_remove_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_remove_id(world, e, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_remove_id(world, e, TagA);

    test_int(ctx.invoked, 0);
    
    ecs_fini(world);
}

void Trigger_on_remove_w_clear(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_clear(world, e);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);
    
    ecs_fini(world);
}

void Trigger_on_remove_w_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_delete(world, e);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);
    
    ecs_fini(world);
}

void Trigger_on_remove_w_world_fini(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);
}

void Trigger_on_add_w_clone(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_os_zeromem(&ctx);

    ecs_entity_t e2 = ecs_clone(world, 0, e, true);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e2);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_add_in_trigger(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = TriggerAdd,
        .ctx = &TagB
    });

    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_assert(ecs_has_id(world, e, TagB));

    ecs_fini(world);
}

void Trigger_remove_in_trigger(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = TriggerRemove,
        .ctx = &TagB
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {TagA, TagB}
    });
    test_assert(e != 0);

    test_assert(!ecs_has_id(world, e, TagB));

    ecs_fini(world);
}

void Trigger_clear_in_trigger(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = TriggerClear,
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {TagA, TagB}
    });
    test_assert(e != 0);

    test_assert(!ecs_has_id(world, e, TagA));
    test_assert(!ecs_has_id(world, e, TagB));

    ecs_fini(world);
}

void Trigger_delete_in_trigger(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = TriggerDelete,
    });

    ecs_entity_t e = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {TagA, TagB}
    });
    test_assert(e != 0);

    test_assert(!ecs_is_alive(world, e));

    ecs_fini(world);
}

void Trigger_trigger_w_named_entity(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .entity = ecs_entity(world, {.name = "MyTrigger"}),
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    /* Create entity/table after trigger, should invoke trigger */
    ecs_entity_t e = ecs_new(world, TagA);
    test_assert(e != 0);

    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void RemoveSelf(ecs_iter_t *it) {
    Self *s = ecs_field(it, Self, 1);
    test_assert(s != NULL);

    ecs_id_t ecs_id(Self) = ecs_field_id(it, 1);

    int i;
    for (i = 0; i < it->count; i ++) {
        test_assert(s[i].value == it->entities[i]);
        
        const Self *ptr = ecs_get(it->world, it->entities[i], Self);
        test_assert(ptr != NULL);
        test_assert(ptr->value == it->entities[i]);

        // Set to 0 so that if an entity were to get matched twice, it wouldn't
        // silently succeed.
        s[i].value = 0;
        on_remove_count ++;
    }
}

void Trigger_on_remove_tree(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Self);

    ECS_OBSERVER(world, RemoveSelf, EcsOnRemove, Self);

    ecs_entity_t root = ecs_new_id(world);
    ecs_entity_t e1 = ecs_new_w_pair(world, EcsChildOf, root);
    ecs_entity_t e2 = ecs_new_w_pair(world, EcsChildOf, e1);
    ecs_entity_t e3 = ecs_new_w_pair(world, EcsChildOf, e1);
    ecs_entity_t e4 = ecs_new_w_pair(world, EcsChildOf, e2);
    ecs_entity_t e5 = ecs_new_w_pair(world, EcsChildOf, e2);
    
    ecs_entity_t e6 = ecs_new_w_pair(world, EcsChildOf, root);
    ecs_entity_t e7 = ecs_new_w_pair(world, EcsChildOf, e6);
    ecs_entity_t e8 = ecs_new_w_pair(world, EcsChildOf, e6);
    ecs_entity_t e9 = ecs_new_w_pair(world, EcsChildOf, e7);
    ecs_entity_t e10 = ecs_new_w_pair(world, EcsChildOf, e7);
    
    ecs_set(world, e1, Self, {e1});
    ecs_set(world, e2, Self, {e2});
    ecs_set(world, e3, Self, {e3});
    ecs_set(world, e4, Self, {e4});
    ecs_set(world, e5, Self, {e5});
    ecs_set(world, e6, Self, {e6});
    ecs_set(world, e7, Self, {e7});
    ecs_set(world, e8, Self, {e8});
    ecs_set(world, e9, Self, {e9});
    ecs_set(world, e10, Self, {e10});

    ecs_delete(world, root);

    test_int(on_remove_count, 10);

    test_assert(!ecs_is_alive(world, e1));
    test_assert(!ecs_is_alive(world, e2));
    test_assert(!ecs_is_alive(world, e3));
    test_assert(!ecs_is_alive(world, e4));
    test_assert(!ecs_is_alive(world, e5));
    test_assert(!ecs_is_alive(world, e6));
    test_assert(!ecs_is_alive(world, e7));
    test_assert(!ecs_is_alive(world, e8));
    test_assert(!ecs_is_alive(world, e9));
    test_assert(!ecs_is_alive(world, e10));
    
    ecs_fini(world);
}

void Trigger_set_get_context(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    int32_t ctx_a, ctx_b;
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .entity = ecs_entity(world, {.name = "MyTrigger"}),
        .filter.terms[0].id = Tag,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_a
    });
    test_assert(t != 0);

    test_assert(ecs_observer_get_ctx(world, t) == &ctx_a);

    test_assert(ecs_observer_init(world, &(ecs_observer_desc_t){
        .entity = t,
        .ctx = &ctx_b
    }) == t);

    test_assert(ecs_observer_get_ctx(world, t) == &ctx_b);

    ecs_fini(world);
}

void Trigger_set_get_binding_context(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    int32_t ctx_a, ctx_b;
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .entity = ecs_entity(world, {.name = "MyTrigger"}),
        .filter.terms[0].id = Tag,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .binding_ctx = &ctx_a
    });
    test_assert(t != 0);

    test_assert(ecs_observer_get_binding_ctx(world, t) == &ctx_a);

    test_assert(ecs_observer_init(world, &(ecs_observer_desc_t){
        .entity = t,
        .binding_ctx = &ctx_b
    }) == t);

    test_assert(ecs_observer_get_binding_ctx(world, t) == &ctx_b);

    ecs_fini(world);
}

static int ctx_value;
static
void ctx_free(void *ctx) {
    test_assert(&ctx_value == ctx);
    ctx_value ++;
}

static int binding_ctx_value;
static
void binding_ctx_free(void *ctx) {
    test_assert(&binding_ctx_value == ctx);
    binding_ctx_value ++;
}

void Trigger_delete_trigger_w_delete_ctx(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = Tag,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_value,
        .ctx_free = ctx_free,
        .binding_ctx = &binding_ctx_value,
        .binding_ctx_free = binding_ctx_free
    });
    test_assert(t != 0);

    test_assert(ecs_observer_get_ctx(world, t) == &ctx_value);
    test_assert(ecs_observer_get_binding_ctx(world, t) == &binding_ctx_value);

    ecs_delete(world, t);

    test_int(ctx_value, 1);
    test_int(binding_ctx_value, 1);

    ecs_fini(world);
}

void Trigger_trigger_w_index(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = Tag,
        .term_index = 50,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });
    test_assert(t != 0);

    ecs_new(world, Tag);

    test_int(ctx.invoked, 1);
    test_int(ctx.term_index, 50);
    
    ecs_fini(world);
}

static ecs_table_t *trigger_table;

void TypeTrigger(ecs_iter_t *it) {
    trigger_table = it->table;
}

void Trigger_iter_type_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = Tag,
        .events = {EcsOnAdd},
        .callback = TypeTrigger,
        .ctx = &ctx
    });
    test_assert(t != 0);

    ecs_new(world, Tag);

    test_assert(trigger_table != NULL);
    const ecs_type_t *type = ecs_table_get_type(trigger_table);
    test_int(type->count, 1);
    test_int(type->array[0], Tag);
    
    ecs_fini(world);
}

void TriggerReadonly(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);
    test_bool(ecs_field_is_readonly(it, 1), true);
}

void Trigger_readonly_term(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { TagA, .inout = EcsIn },
        .events = {EcsOnAdd},
        .callback = TriggerReadonly,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new_id(world);
    test_assert(e != 0);
    ecs_add(world, e, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e);
    test_int(ctx.c[0][0], TagA);

    ecs_fini(world);
}

void Trigger_trigger_on_prefab(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx_1 = {0};
    Probe ctx_2 = {0};

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { TagA },
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_1
    });

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms = {{ TagA }, { EcsPrefab, .oper = EcsOptional }},
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_2
    });

    ecs_new(world, TagA);
    test_int(ctx_1.invoked, 1);
    test_int(ctx_2.invoked, 1);
    ecs_os_zeromem(&ctx_1);
    ecs_os_zeromem(&ctx_2);

    ecs_entity_t e = ecs_new_w_id(world, EcsPrefab);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);
    ecs_add(world, e, TagA);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 1);
    ecs_os_zeromem(&ctx_2);

    e = ecs_new_w_id(world, EcsDisabled);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);
    ecs_add(world, e, TagA);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);

    ecs_fini(world);
}

void Trigger_trigger_on_disabled(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx_1 = {0};
    Probe ctx_2 = {0};

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { TagA },
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_1
    });

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms = {{ TagA }, { EcsDisabled, .oper = EcsOptional }},
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_2
    });

    ecs_new(world, TagA);
    test_int(ctx_1.invoked, 1);
    test_int(ctx_2.invoked, 1);
    ecs_os_zeromem(&ctx_1);
    ecs_os_zeromem(&ctx_2);

    ecs_entity_t e = ecs_new_w_id(world, EcsPrefab);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);
    ecs_add(world, e, TagA);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);
    ecs_os_zeromem(&ctx_2);

    e = ecs_new_w_id(world, EcsDisabled);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);
    ecs_add(world, e, TagA);
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 1);

    ecs_fini(world);
}

void Trigger_trigger_on_prefab_tag(void) {
    ecs_world_t *world = ecs_mini();

    Probe ctx_1 = {0};

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { EcsPrefab },
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_1
    });

    ecs_new_w_id(world, EcsPrefab);
    test_int(ctx_1.invoked, 1);

    ecs_fini(world);
}

void Trigger_trigger_on_disabled_tag(void) {
    ecs_world_t *world = ecs_mini();

    Probe ctx_1 = {0};

    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { EcsDisabled },
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_1
    });

    ecs_new_w_id(world, EcsDisabled);
    test_int(ctx_1.invoked, 1);

    ecs_fini(world);
}

void Trigger_trigger_cleanup_2_w_self_super_id(void) {
    ecs_world_t * world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { .id = Tag, .src.flags = EcsUp },
        .events = { EcsOnAdd },
        .callback = Trigger
    });

    ecs_entity_t t2 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { Tag },
        .events = { EcsOnAdd },
        .callback = Trigger
    });

    test_assert(t1 != 0);
    test_assert(t2 != 0);

    ecs_fini(world);

    /* Ensure two triggers for Tag and Tag(up) are cleaned up correctly */
}

void Trigger_on_add_yield_existing(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    /* Create entities before trigger */
    ecs_entity_t e1 = ecs_new(world, Tag);
    ecs_entity_t e2 = ecs_new(world, Tag);
    ecs_entity_t e3 = ecs_new(world, Tag);

    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = Tag,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx,
        .yield_existing = true
    });

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 3);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, Tag);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);
    test_int(ctx.e[2], e3);
    test_int(ctx.c[0][0], Tag);

    // Ensure normal triggering also still works
    ecs_os_zeromem(&ctx);
    ecs_new(world, Tag);
    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void Trigger_on_add_yield_existing_2_tables(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    /* Create entities before trigger */
    ecs_entity_t e1 = ecs_new(world, TagA);
    ecs_entity_t e2 = ecs_new(world, TagA);
    ecs_entity_t e3 = ecs_new(world, TagA);

    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    ecs_add(world, e3, TagB);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx,
        .yield_existing = true
    });

    test_int(ctx.invoked, 2);
    test_int(ctx.count, 3);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);
    test_int(ctx.e[2], e3);
    test_int(ctx.c[0][0], TagA);
    test_int(ctx.c[1][0], TagA);

    // Ensure normal triggering also still works
    ecs_os_zeromem(&ctx);
    ecs_new(world, TagA);
    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void Trigger_on_add_yield_existing_wildcard_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    /* Create entities before trigger */
    ecs_entity_t e1 = ecs_new_w_pair(world, Rel, ObjA);
    ecs_entity_t e2 = ecs_new_w_pair(world, Rel, ObjA);
    ecs_entity_t e3 = ecs_new_w_pair(world, Rel, ObjB);

    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx,
        .yield_existing = true
    });

    test_int(ctx.invoked, 2);
    test_int(ctx.count, 3);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Rel, ObjB)); /* last triggered id */
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);
    test_int(ctx.e[2], e3);
    test_int(ctx.c[0][0], ecs_pair(Rel, ObjA));
    test_int(ctx.c[1][0], ecs_pair(Rel, ObjB));

    // Ensure normal triggering also still works
    ecs_os_zeromem(&ctx);
    ecs_new_w_pair(world, Rel, ObjA);
    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void Trigger_on_set_yield_existing(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    /* Create entities before trigger */
    ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t e2 = ecs_set(world, 0, Position, {30, 40});
    ecs_entity_t e3 = ecs_set(world, 0, Position, {50, 60});

    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .events = {EcsOnSet},
        .callback = Trigger_n_w_values,
        .ctx = &ctx,
        .yield_existing = true
    });

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 3);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);
    test_int(ctx.e[2], e3);
    test_int(ctx.c[0][0], ecs_id(Position));

    // Ensure normal triggering also still works
    ecs_os_zeromem(&ctx);
    ecs_entity_t e = ecs_new(world, Position);
    test_int(ctx.invoked, 0);

    ecs_set(world, e, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.event, EcsOnSet);

    ecs_fini(world);
}

void Trigger_filter_term(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = { .id = ecs_id(Position), .inout = EcsInOutNone },
        .events = {EcsOnSet},
        .callback = Trigger_w_filter_term,
        .ctx = &ctx
    });

    /* Create entities before trigger */
    ecs_entity_t e1 = ecs_set(world, 0, Position, {10, 20});
    test_assert(e1 != 0);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.c[0][0], ecs_id(Position));

    ecs_fini(world);
}

void Trigger_on_add_remove_after_exclusive_add(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Exclusive);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    Probe ctx_add = {0};
    ecs_entity_t t_add = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard),
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx_add
    });

    Probe ctx_remove = {0};
    ecs_entity_t t_remove = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx_remove
    });

    ecs_entity_t e = ecs_new_id(world);

    ecs_add_pair(world, e, Rel, ObjA);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));

    test_int(ctx_add.invoked, 1);
    test_int(ctx_add.count, 1);
    test_int(ctx_add.system, t_add);
    test_int(ctx_add.event, EcsOnAdd);
    test_int(ctx_add.event_id, ecs_pair(Rel, ObjA));
    test_int(ctx_add.term_count, 1);
    test_null(ctx_add.param);

    test_int(ctx_remove.invoked, 0);

    ecs_os_zeromem(&ctx_add);

    ecs_add_pair(world, e, Rel, ObjB);
    test_assert( ecs_has_pair(world, e, Rel, ObjB));
    test_assert( !ecs_has_pair(world, e, Rel, ObjA));

    test_int(ctx_add.invoked, 1);
    test_int(ctx_add.count, 1);
    test_int(ctx_add.system, t_add);
    test_int(ctx_add.event, EcsOnAdd);
    test_int(ctx_add.event_id, ecs_pair(Rel, ObjB));
    test_int(ctx_add.term_count, 1);
    test_null(ctx_add.param);

    test_int(ctx_remove.invoked, 1);
    test_int(ctx_remove.count, 1);
    test_int(ctx_remove.system, t_remove);
    test_int(ctx_remove.event, EcsOnRemove);
    test_int(ctx_remove.event_id, ecs_pair(Rel, ObjA));
    test_int(ctx_remove.term_count, 1);
    test_null(ctx_remove.param);

    ecs_fini(world);
}

void Trigger_on_add_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA, /* Implicitly also listens to IsA */
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_add(world, base, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_os_zeromem(&ctx);

    ecs_add(world, base, TagA);
    ecs_add(world, base, TagB);
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_remove_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA, /* Implicitly also listens to IsA */
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_add(world, base, TagA);
    ecs_add(world, base, TagB);
    test_int(ctx.invoked, 0);

    ecs_remove(world, base, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnRemove);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_os_zeromem(&ctx);

    ecs_remove(world, base, TagA);
    ecs_remove(world, base, TagB);
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_set_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position), /* Implicitly also listens to IsA */
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_os_zeromem(&ctx);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_os_zeromem(&ctx);

    ecs_set(world, base, Velocity, {1, 2});
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_unset_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position), /* Implicitly also listens to IsA */
        .events = {EcsUnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_set(world, base, Position, {10, 20});
    ecs_set(world, base, Velocity, {1, 2});
    test_int(ctx.invoked, 0);

    ecs_remove(world, base, Position);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsUnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_os_zeromem(&ctx);

    ecs_remove(world, base, Position);
    ecs_remove(world, base, Velocity);
    test_int(ctx.invoked, 0);
    
    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_add_base_superset_trigger(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].src.flags = EcsUp,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_add(world, base, TagA);

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_os_zeromem(&ctx);

    ecs_add(world, base, TagA);
    ecs_add(world, base, TagB);
    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_add_base_superset_trigger_2_lvls(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .filter.terms[0].src.flags = EcsUp,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base_of_base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_add_pair(world, base, EcsIsA, base_of_base);

    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_add(world, base_of_base, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_os_zeromem(&ctx);

    ecs_add(world, base_of_base, TagA);
    ecs_add(world, base_of_base, TagB);
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_add_base_2_entities(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA, /* Implicitly also listens to IsA */
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_add(world, base, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 2);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_os_zeromem(&ctx);

    ecs_add(world, base, TagA);
    ecs_add(world, base, TagB);
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_add_base_2_entities_filter(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA, /* Implicitly also listens to IsA */
        .filter.terms[0].inout = EcsInOutNone,
        .events = {EcsOnAdd},
        .callback = Trigger_w_filter_term,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_add(world, base, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 2);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_os_zeromem(&ctx);

    ecs_add(world, base, TagA);
    ecs_add(world, base, TagB);
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_set_base_w_value_2_entities(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position), /* Implicitly also listens to IsA */
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 2);
    test_int(ctx.count, 2);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_os_zeromem(&ctx);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 2);
    test_int(ctx.count, 2);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_os_zeromem(&ctx);

    ecs_set(world, base, Velocity, {1, 2});
    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_set_base_w_value_2_entities_instanced(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position), /* Implicitly also listens to IsA */
        .filter.instanced = true,
        .events = {EcsOnSet},
        .callback = Trigger_w_value_instanced,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e1 = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t e2 = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 2);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_os_zeromem(&ctx);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 2);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_os_zeromem(&ctx);

    ecs_set(world, base, Velocity, {1, 2});
    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_add_base_w_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA, /* Implicitly also listens to IsA */
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_add(world, base, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.s[0][0], base);

    ecs_os_zeromem(&ctx);

    ecs_add(world, e, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.s[0][0], 0);

    ecs_os_zeromem(&ctx);

    ecs_remove(world, base, TagA);
    ecs_add(world, base, TagA);
    ecs_add(world, base, TagB);
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_on_set_base_w_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position), /* Implicitly also listens to IsA */
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.s[0][0], base);

    ecs_os_zeromem(&ctx);

    ecs_set(world, e, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);
    test_int(ctx.s[0][0], 0);

    ecs_os_zeromem(&ctx);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 0); // no trigger, overridden

    ecs_set(world, base, Velocity, {1, 2});
    test_int(ctx.invoked, 0);

    ecs_fini(world);

    test_int(ctx.invoked, 0);
}

void Trigger_entity_source_1_trigger(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t subj = ecs_new_id(world);
    ecs_entity_t dummy = ecs_new_id(world);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .filter.terms[0].src.id = subj,
        .events = {EcsOnSet},
        .callback = Trigger_w_value_from_entity,
        .ctx = &ctx
    });

    ecs_set(world, dummy, Position, {10, 20});
    test_int(ctx.invoked, 0);

    ecs_set(world, subj, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], 0);
    test_int(ctx.s[0][0], subj);

    ecs_fini(world);
}

void Trigger_entity_source_2_triggers(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t subj_a = ecs_new_id(world);
    ecs_entity_t subj_b = ecs_new_id(world);
    ecs_entity_t dummy = ecs_new_id(world);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .filter.terms[0].src.id = subj_a,
        .events = {EcsOnSet},
        .callback = Trigger_w_value_from_entity,
        .ctx = &ctx
    });

    ecs_entity_t t2 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .filter.terms[0].src.id = subj_b,
        .events = {EcsOnSet},
        .callback = Trigger_w_value_from_entity,
        .ctx = &ctx
    });

    ecs_set(world, dummy, Position, {10, 20});
    test_int(ctx.invoked, 0);

    ecs_set(world, subj_a, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t1);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], 0);
    test_int(ctx.s[0][0], subj_a);

    ecs_os_zeromem(&ctx);

    ecs_set(world, subj_b, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t2);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], 0);
    test_int(ctx.s[0][0], subj_b);

    ecs_fini(world);
}

void Trigger_entity_source_base_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t subj = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t dummy = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t dummy_no_base = ecs_new_w_pair(world, EcsIsA, base);

    /* Create trigger before table */
    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .filter.terms[0].src.id = subj,
        .events = {EcsOnSet},
        .callback = Trigger_w_value_from_entity,
        .ctx = &ctx
    });

    ecs_set(world, dummy, Position, {10, 20});
    test_int(ctx.invoked, 0);

    ecs_set(world, dummy_no_base, Position, {10, 20});
    test_int(ctx.invoked, 0);

    ecs_set(world, base, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnSet);
    test_int(ctx.event_id, ecs_id(Position));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], 0);
    test_int(ctx.s[0][0], base);

    ecs_fini(world);
}

void Trigger_not_from_superset(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = Tag,
        .filter.terms[0].oper = EcsNot,
        .filter.terms[0].src.flags = EcsUp,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    ecs_entity_t base = ecs_new(world, Tag);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_delete(world, inst);
    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void Trigger_create_stresstest(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    /* Make sure we can create & delete 100k triggers without running out of
     * memory to verify there are no leaks in the administration (sanitizer 
     * builds don't detect leaks that are cleaned up @ ecs_fini). */
    for (int i = 0; i < 100 * 1000; i ++) {
        ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
            .filter.terms[0].id = Tag,
            .filter.terms[0].src.flags = EcsSelf | EcsUp,
            .events = {EcsOnAdd},
            .callback = Trigger
        });

        ecs_delete(world, t);
    }

    test_assert(true);

    ecs_fini(world);
}

static
void Trigger_w_new_entity_value(ecs_iter_t *it) {
    probe_iter(it);
}

void Trigger_add_non_existing_entity(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);

    Probe ctx = {0};
    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard),
        .events = {EcsOnAdd},
        .callback = Trigger_w_new_entity_value,
        .ctx = &ctx
    });

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, Rel, 1000);

    test_assert( ecs_has_pair(world, e, Rel, 1000));
    test_assert( ecs_is_alive(world, 1000));

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t1);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Rel, 1000));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    ecs_fini(world);
}

void Trigger_on_add_self_trigger_with_add_isa(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    Probe ctx = {0};
    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = Tag,
        .filter.terms[0].src.flags = EcsSelf,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });

    test_assert(t1 != 0);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_new_id(world);
    ecs_add(world, base, Tag);
    test_int(ctx.invoked, 1);

    ctx = (Probe){0};

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_set_self_trigger_with_add_isa(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx = {0};
    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_id(Position),
        .filter.terms[0].src.flags = EcsSelf,
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });

    test_assert(t1 != 0);
    test_int(ctx.invoked, 0);

    ecs_entity_t base = ecs_set(world, 0, Position, {10, 20});
    test_int(ctx.invoked, 1);

    ctx = (Probe){0};

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, EcsIsA, base);
    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

static int invoke_count = 0;
static ecs_entity_t base_ent = 0;
static ecs_entity_t inst_ent_a = 0;
static ecs_entity_t inst_ent_b = 0;

static void TriggerTwice(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);

    test_assert(base_ent != 0);
    test_assert(inst_ent_a != 0);
    test_assert(inst_ent_b != 0);
    test_int(it->count, 1);

    if (invoke_count == 0) {
        test_assert(it->entities[0] == base_ent);
        invoke_count ++;
    } else if (invoke_count == 1) {
        test_assert(it->entities[0] == inst_ent_b);
        invoke_count ++;
    } else {
        test_int(invoke_count, 2);
        test_assert(it->entities[0] == inst_ent_a);
        invoke_count ++;
    }
}

void Trigger_notify_propagated_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    Probe ctx = {0};
    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = TriggerTwice,
        .ctx = &ctx
    });
    test_assert(t1 != 0);

    base_ent = ecs_new_id(world);
    inst_ent_a = ecs_new_w_pair(world, EcsIsA, base_ent);
    ecs_add(world, inst_ent_a, TagB);
    inst_ent_b = ecs_new_w_pair(world, EcsIsA, base_ent);

    test_int(ctx.invoked, 0);

    ecs_add(world, base_ent, TagA);

    test_int(ctx.invoked, 3);
    test_int(invoke_count, 3);

    ecs_fini(world);
}

void Trigger_trigger_superset_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t base = ecs_new_id(world);
    ecs_entity_t inst = ecs_new_id(world);
    ecs_add_pair(world, inst, EcsIsA, base);

    Probe ctx = {0};
    ecs_entity_t t = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard), 
        .filter.terms[0].src.flags = EcsUp,
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });
    test_assert(t != 0);

    test_int(ctx.invoked, 0);

    ecs_add_pair(world, base, Rel, ObjA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Rel, ObjA));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], inst);
    test_int(ctx.s[0][0], base);

    ecs_os_zeromem(&ctx);

    ecs_add_pair(world, base, Rel, ObjB);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.system, t);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, ecs_pair(Rel, ObjB));
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], inst);
    test_int(ctx.s[0][0], base);

    ecs_fini(world);
}

void Trigger_remove_wildcard_1_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);

    ecs_entity_t e = ecs_new_w_pair(world, Rel, ObjA);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));
    test_assert( ecs_has_pair(world, e, Rel, EcsWildcard));

    Probe ctx_a = {0};
    ecs_entity_t t_a = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, ObjA), 
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx_a
    });
    test_assert(t_a != 0);

    Probe ctx_b = {0};
    ecs_entity_t t_b = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = &ctx_b
    });
    test_assert(t_b != 0);

    test_int(ctx_a.invoked, 0);
    test_int(ctx_b.invoked, 0);

    ecs_remove_pair(world, e, Rel, EcsWildcard);
    test_assert( !ecs_has_pair(world, e, Rel, ObjA));
    test_assert( !ecs_has_pair(world, e, Rel, EcsWildcard));

    test_int(ctx_a.invoked, 1);
    test_int(ctx_a.count, 1);
    test_int(ctx_a.system, t_a);
    test_int(ctx_a.event, EcsOnRemove);
    test_int(ctx_a.event_id, ecs_pair(Rel, ObjA));
    test_int(ctx_a.term_count, 1);
    test_int(ctx_a.e[0], e);

    test_int(ctx_b.invoked, 1);
    test_int(ctx_b.count, 1);
    test_int(ctx_b.system, t_b);
    test_int(ctx_b.event, EcsOnRemove);
    test_int(ctx_b.event_id, ecs_pair(Rel, ObjA));
    test_int(ctx_b.term_count, 1);
    test_int(ctx_b.e[0], e);

    ecs_fini(world);
}

void Trigger_remove_wildcard_2_ids(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    Probe *ctx_array = ecs_os_calloc_n(Probe, 3);

    ecs_entity_t e = ecs_new_w_pair(world, Rel, ObjA);
    ecs_add_pair(world, e, Rel, ObjB);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));
    test_assert( ecs_has_pair(world, e, Rel, ObjB));
    test_assert( ecs_has_pair(world, e, Rel, EcsWildcard));

    Probe *ctx_a = &ctx_array[0];
    ecs_entity_t t_a = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, EcsWildcard), 
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = ctx_a
    });
    test_assert(t_a != 0);

    Probe *ctx_b = &ctx_array[1];
    ecs_entity_t t_b = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, ObjA),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = ctx_b
    });
    test_assert(t_b != 0);

    Probe *ctx_c = &ctx_array[2];
    ecs_entity_t t_c = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = ecs_pair(Rel, ObjB),
        .events = {EcsOnRemove},
        .callback = Trigger,
        .ctx = ctx_c
    });
    test_assert(t_c != 0);

    test_int(ctx_a->invoked, 0);
    test_int(ctx_b->invoked, 0);
    test_int(ctx_c->invoked, 0);

    ecs_remove_pair(world, e, Rel, EcsWildcard);
    test_assert( !ecs_has_pair(world, e, Rel, ObjA));
    test_assert( !ecs_has_pair(world, e, Rel, ObjB));
    test_assert( !ecs_has_pair(world, e, Rel, EcsWildcard));

    test_int(ctx_a->invoked, 2);
    test_int(ctx_a->count, 2);
    test_int(ctx_a->system, t_a);
    test_int(ctx_a->event, EcsOnRemove);
    test_int(ctx_a->event_id, ecs_pair(Rel, ObjB));
    test_int(ctx_a->term_count, 1);
    test_int(ctx_a->e[0], e);

    test_int(ctx_b->invoked, 1);
    test_int(ctx_b->count, 1);
    test_int(ctx_b->system, t_b);
    test_int(ctx_b->event, EcsOnRemove);
    test_int(ctx_b->event_id, ecs_pair(Rel, ObjA));
    test_int(ctx_b->term_count, 1);
    test_int(ctx_b->e[0], e);

    test_int(ctx_c->invoked, 1);
    test_int(ctx_c->count, 1);
    test_int(ctx_c->system, t_c);
    test_int(ctx_c->event, EcsOnRemove);
    test_int(ctx_c->event_id, ecs_pair(Rel, ObjB));
    test_int(ctx_c->term_count, 1);
    test_int(ctx_c->e[0], e);

    ecs_os_free(ctx_array);

    ecs_fini(world);
}

void Trigger_on_set_w_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnSet},
        .callback = Trigger,
        .ctx = &ctx
    });
    test_assert(t1 != 0);

    ecs_entity_t e = ecs_new_id(world);
    test_assert(e != 0);

    ecs_add(world, e, TagA);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], e);
    test_int(ctx.event, EcsOnAdd);
    test_int(ctx.event_id, TagA);

    ecs_fini(world);
}

static int nested_trigger_invoked = 0;

static void NestedTrigger(ecs_iter_t *it) {
    nested_trigger_invoked ++;
}

static int create_trigger_invoked = 0;

typedef struct {
    int32_t count;
    ecs_entity_t first;
} CreateTriggers_ctx;

static void CreateTriggers(ecs_iter_t *it) {
    CreateTriggers_ctx *ctx = it->ctx;

    create_trigger_invoked ++;

    ctx->first = ecs_new_id(it->world);

    int i;
    for (i = 0; i < ctx->count - 1; i ++) {
        ecs_new_id(it->world);
    }

    for (i = 0; i < ctx->count; i ++) {
        ecs_observer_init(it->world, &(ecs_observer_desc_t){
            .filter.terms[0].id = ctx->first + i,
            .events = {EcsOnAdd},
            .callback = NestedTrigger
        });
    }
}

void Trigger_create_triggers_in_trigger(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    int CreateCount = 10;

    CreateTriggers_ctx ctx = { .count = CreateCount };
    ecs_entity_t t1 = ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0].id = TagA,
        .events = {EcsOnAdd},
        .callback = CreateTriggers,
        .ctx = &ctx
    });
    test_assert(t1 != 0);

    ecs_entity_t e = ecs_new_id(world);
    test_assert(e != 0);

    ecs_add(world, e, TagA);
    test_int(create_trigger_invoked, 1);
    test_int(nested_trigger_invoked, 0);

    int i;
    for (i = 0; i < CreateCount; i ++) {
        ecs_entity_t t = ecs_new_id(world);
        ecs_add_id(world, t, ctx.first + i);
        test_int(nested_trigger_invoked, i + 1);
    }

    test_int(create_trigger_invoked, 1);
    test_int(nested_trigger_invoked, i);

    ecs_fini(world);
}

static
void Trigger_w_nonzero_value(ecs_iter_t *it) {
    probe_system_w_ctx(it, it->ctx);

    test_int(it->count, 1);
    test_assert(it->entities != NULL);
    test_assert(it->entities[0] != 0);

    Position *p = ecs_field(it, Position, 1);
    test_assert(p != NULL);
}

void Trigger_on_add_superset_w_component(void) {
	ecs_world_t *world = ecs_mini();

	ECS_COMPONENT(world, Position);

    Probe ctx = {0};
	ecs_observer_init(world, &(ecs_observer_desc_t){
		.filter.terms[0].id = ecs_id(Position),
		.events = {EcsOnAdd},
		.callback = Trigger_w_nonzero_value,
        .ctx = &ctx
	});

	ecs_entity_t b = ecs_new(world, 0);
	ecs_set(world, b, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], b);
    test_int(ctx.s[0][0], 0);

    ecs_os_zeromem(&ctx);
	
	ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, b);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], i);
    test_int(ctx.s[0][0], b);

    ecs_fini(world);
}

void Trigger_on_set_superset_w_component(void) {
	ecs_world_t *world = ecs_mini();

	ECS_COMPONENT(world, Position);

    Probe ctx = {0};
	ecs_observer_init(world, &(ecs_observer_desc_t){
		.filter.terms[0].id = ecs_id(Position),
		.events = {EcsOnSet},
		.callback = Trigger_w_value,
        .ctx = &ctx
	});

	ecs_entity_t b = ecs_new(world, 0);
	ecs_set(world, b, Position, {10, 20});
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], b);
    test_int(ctx.s[0][0], 0);

    ecs_os_zeromem(&ctx);

	ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, b);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], i);
    test_int(ctx.s[0][0], b);

    ecs_fini(world);
}

void Trigger_on_add_base_superset_w_owned(void) {
	ecs_world_t *world = ecs_mini();

	ECS_TAG(world, Tag);

    Probe ctx = {0};
	ecs_observer_init(world, &(ecs_observer_desc_t){
		.filter.terms[0] = {
            .id = ecs_id(Tag),
            .src.flags = EcsUp,
        },
		.events = {EcsOnAdd},
		.callback = Trigger,
        .ctx = &ctx
	});

	ecs_entity_t b = ecs_new(world, 0);
	ecs_add(world, b, Tag);
    test_int(ctx.invoked, 0);
	
	ecs_entity_t i = ecs_new(world, Tag);
    test_int(ctx.invoked, 0);
    
    ecs_add_pair(world, i, EcsIsA, b);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], i);
    test_int(ctx.s[0][0], b);

    ecs_fini(world);
}

void Trigger_on_add_base_self_superset_w_owned(void) {
	ecs_world_t *world = ecs_mini();

	ECS_TAG(world, Tag);

    Probe ctx = {0};
	ecs_observer_init(world, &(ecs_observer_desc_t){
		.filter.terms[0] = {
            .id = ecs_id(Tag),
            .src.flags = EcsSelf|EcsUp,
        },
		.events = {EcsOnAdd},
		.callback = Trigger,
        .ctx = &ctx
	});

	ecs_entity_t b = ecs_new(world, 0);
	ecs_add(world, b, Tag);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], b);
    test_int(ctx.s[0][0], 0);

    ecs_os_zeromem(&ctx);
	
	ecs_entity_t i1 = ecs_new(world, Tag);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], i1);
    test_int(ctx.s[0][0], 0);

    ecs_os_zeromem(&ctx);

    ecs_add_pair(world, i1, EcsIsA, b);
    test_int(ctx.invoked, 0);

    ecs_entity_t i2 = ecs_new_w_pair(world, EcsIsA, b);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], i2);
    test_int(ctx.s[0][0], b);

    ecs_fini(world);
}

void Trigger_on_set_self_from_child_of_prefab(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t base_child = ecs_new_entity(world, "Child");
    ecs_add_id(world, base_child, EcsPrefab);
    ecs_add_pair(world, base_child, EcsChildOf, base);
    ecs_set(world, base_child, Position, {10, 20});

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Position),
            .src.flags = EcsSelf
        },
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);

    ecs_entity_t inst_child = ecs_lookup_child(world, inst, "Child");
    test_assert(inst_child != 0);
    test_int(ctx.e[0], inst_child);
    test_int(ctx.s[0][0], 0);

    test_assert(ecs_has(world, inst_child, Position));
    test_assert(ecs_owns(world, inst_child, Position));

    ecs_fini(world);
}

void Trigger_on_set_self_superset_from_child_of_prefab(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t base_child = ecs_new_entity(world, "Child");
    ecs_add_id(world, base_child, EcsPrefab);
    ecs_add_pair(world, base_child, EcsChildOf, base);
    ecs_set(world, base_child, Position, {10, 20});

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Position),
            .src.flags = EcsSelf | EcsUp
        },
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t inst_child = ecs_lookup_child(world, inst, "Child");
    test_assert(inst_child != 0);
    test_assert(ecs_has(world, inst_child, Position));
    test_assert(ecs_owns(world, inst_child, Position));

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], inst_child);
    test_int(ctx.s[0][0], 0);

    ecs_fini(world);
}

void Trigger_on_set_self_from_child_base_of_prefab(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t base_child_base = ecs_new_w_id(world, EcsPrefab);
    ecs_add_id(world, base_child_base, EcsPrefab);
    ecs_set(world, base_child_base, Position, {10, 20});

    ecs_entity_t base_child = ecs_new_entity(world, "Child");
    ecs_add_pair(world, base_child, EcsChildOf, base);
    ecs_add_pair(world, base_child, EcsIsA, base_child_base);

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Position),
            .src.flags = EcsSelf
        },
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t inst_child = ecs_lookup_child(world, inst, "Child");
    test_assert(inst_child != 0);
    test_assert(ecs_has(world, inst_child, Position));
    test_assert(!ecs_owns(world, inst_child, Position));

    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_set_self_superset_from_child_base_of_prefab(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_new_w_id(world, EcsPrefab);
    ecs_entity_t base_child_base = ecs_new_w_id(world, EcsPrefab);
    ecs_add_id(world, base_child_base, EcsPrefab);
    ecs_set(world, base_child_base, Position, {10, 20});

    ecs_entity_t base_child = ecs_new_entity(world, "Child");
    ecs_add_pair(world, base_child, EcsChildOf, base);
    ecs_add_pair(world, base_child, EcsIsA, base_child_base);

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Position),
            .src.flags = EcsSelf | EcsUp
        },
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t inst_child = ecs_lookup_child(world, inst, "Child");
    test_assert(inst_child != 0);
    test_assert(ecs_has(world, inst_child, Position));
    test_assert(!ecs_owns(world, inst_child, Position));

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], inst_child);
    test_int(ctx.s[0][0], base_child_base);

    ecs_fini(world);
}

void Trigger_on_set_self_auto_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_set(world, 0, Position, {10, 20});
    ecs_add_id(world, base, ECS_OVERRIDE | ecs_id(Position));

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Position),
            .src.flags = EcsSelf
        },
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    test_assert( ecs_has(world, inst, Position));
    test_assert( ecs_owns(world, inst, Position));

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], inst);
    test_int(ctx.s[0][0], 0);

    ecs_fini(world);
}

void Trigger_on_set_self_superset_auto_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_set(world, 0, Position, {10, 20});
    ecs_add_id(world, base, ECS_OVERRIDE | ecs_id(Position));

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Position),
            .src.flags = EcsSelf | EcsUp
        },
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    test_assert( ecs_has(world, inst, Position));
    test_assert( ecs_owns(world, inst, Position));

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], inst);
    test_int(ctx.s[0][0], 0);

    ecs_fini(world);
}

void Trigger_on_set_superset_auto_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t base = ecs_set(world, 0, Position, {10, 20});
    ecs_add_id(world, base, ECS_OVERRIDE | ecs_id(Position));

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = ecs_id(Position),
            .src.flags = EcsUp
        },
        .events = {EcsOnSet},
        .callback = Trigger_w_value,
        .ctx = &ctx
    });

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    test_assert( ecs_has(world, inst, Position));
    test_assert( ecs_owns(world, inst, Position));

    test_int(ctx.invoked, 1);
    test_int(ctx.count, 1);
    test_int(ctx.e[0], inst);
    test_int(ctx.s[0][0], base);

    ecs_fini(world);
}

void Trigger_not_only(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    Probe ctx = {0};
    ecs_observer(world, {
        .filter.terms = {
            { TagA, .oper = EcsNot }
        },
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx
    });

    test_int(ctx.invoked, 0);

    ecs_entity_t i = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_delete(world, i);
    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void Trigger_not_only_w_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);

    Probe ctx = {0};
    ecs_observer(world, {
        .filter.terms = {
            { TagA, .oper = EcsNot }
        },
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx
    });

    test_int(ctx.invoked, 0);

    ecs_entity_t p = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p);
    test_int(ctx.invoked, 0);

    ecs_delete(world, i);
    test_int(ctx.invoked, 1);

    ecs_fini(world);
}

void Trigger_not_only_w_base_no_match(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    Probe ctx = {0};
    ecs_observer(world, {
        .filter.terms = {
            { TagB, .oper = EcsNot }
        },
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx
    });

    test_int(ctx.invoked, 0);

    ecs_entity_t p = ecs_new(world, TagA);
    test_int(ctx.invoked, 0);

    ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p);
    test_int(ctx.invoked, 0);

    ecs_delete(world, i);
    test_int(ctx.invoked, 0);

    ecs_fini(world);
}

void Trigger_on_set_superset_after_filter_observer(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx_1 = {0};
    ecs_observer(world, {
        .filter.terms = {
            { ecs_id(Position) }
        },
        .filter.flags = EcsFilterNoData,
        .events = { EcsOnSet },
        .callback = Trigger,
        .ctx = &ctx_1
    });

    Probe ctx_2 = {0};
    ecs_observer(world, {
        .filter.terms = {
            { ecs_id(Position), .src.flags = EcsUp }
        },
        .events = { EcsOnSet },
        .callback = Trigger_w_value,
        .ctx = &ctx_2
    });

    ecs_entity_t base = ecs_new_id(world);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);

    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);

    ecs_set(world, base, Position, {10, 20});

    test_int(ctx_1.invoked, 2); 

    test_int(ctx_2.invoked, 1);
    test_int(ctx_2.count, 1);
    test_int(ctx_2.e[0], inst);
    test_int(ctx_2.c[0][0], ecs_id(Position));
    test_int(ctx_2.s[0][0], base);

    ecs_fini(world);
}

void Trigger_on_set_superset_after_filter_observer_w_on_add(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx_1 = {0};
    ecs_observer(world, {
        .filter.terms = {
            { ecs_id(Position) }
        },
        .filter.flags = EcsFilterNoData,
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx_1
    });

    Probe ctx_2 = {0};
    ecs_observer(world, {
        .filter.terms = {
            { ecs_id(Position), .src.flags = EcsUp }
        },
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx_2
    });

    ecs_entity_t base = ecs_new_id(world);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);

    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);

    ecs_set(world, base, Position, {10, 20});

    test_int(ctx_1.invoked, 2);

    test_int(ctx_2.invoked, 1);
    test_int(ctx_2.count, 1);
    test_int(ctx_2.e[0], inst);
    test_int(ctx_2.c[0][0], ecs_id(Position));
    test_int(ctx_2.s[0][0], base);

    ecs_fini(world);
}

void Trigger_on_set_superset_after_filter_observer_w_on_add_isa_after_set(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    Probe ctx_1 = {0};
    ecs_observer(world, {
        .filter.terms = {
            { ecs_id(Position), .src.flags = EcsUp }
        },
        .filter.flags = EcsFilterNoData,
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx_1
    });

    Probe ctx_2 = {0};
    ecs_observer(world, {
        .filter.terms = {
            { ecs_id(Position), .src.flags = EcsUp }
        },
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx_2
    });

    ecs_entity_t base = ecs_new_id(world);
    ecs_set(world, base, Position, {10, 20});
    test_int(ctx_1.invoked, 0);
    test_int(ctx_2.invoked, 0);

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);

    test_int(ctx_1.invoked, 1);
    test_int(ctx_1.count, 1);
    test_int(ctx_1.e[0], inst);
    test_int(ctx_1.c[0][0], ecs_id(Position));
    test_int(ctx_1.s[0][0], base);

    test_int(ctx_2.invoked, 1);
    test_int(ctx_2.count, 1);
    test_int(ctx_2.e[0], inst);
    test_int(ctx_2.c[0][0], ecs_id(Position));
    test_int(ctx_2.s[0][0], base);

    ecs_fini(world);
}

void Trigger_on_set_superset_after_filter_observer_w_on_add_2(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    // first observer causes observer to be initialized with filter flag
    Probe ctx_1 = {0};
    ecs_observer(world, { 
        .filter.terms = {
            { ecs_id(Position) }
        },
        .filter.flags = EcsFilterNoData,
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx_1
    });

    // second observer is not a filter, so value should be avaulable
    Probe ctx_2 = {0};
    ecs_observer(world, {
        .filter.terms = {
            { ecs_id(Position), .src.flags = EcsUp }
        },
        .events = { EcsOnAdd },
        .callback = Trigger,
        .ctx = &ctx_2
    });

    ecs_entity_t base = ecs_new_id(world);
    ecs_set(world, base, Position, {10, 20});
    ecs_override(world, base, Position); // override causes emit with 2 ids
    
    test_int(ctx_1.invoked, 1);
    test_int(ctx_2.invoked, 0);

    ecs_os_zeromem(&ctx_1);

    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);

    test_int(ctx_1.invoked, 1);
    test_int(ctx_1.count, 1);
    test_int(ctx_1.e[0], inst);
    test_int(ctx_1.c[0][0], ecs_id(Position));
    test_int(ctx_1.s[0][0], 0); // override

    test_int(ctx_2.invoked, 1);
    test_int(ctx_2.count, 1);
    test_int(ctx_2.e[0], inst);
    test_int(ctx_2.c[0][0], ecs_id(Position));
    test_int(ctx_2.s[0][0], base);

    ecs_fini(world);
}

void Trigger_propagate_w_union_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);
    ECS_TAG(world, RelX);
    ECS_ENTITY(world, RelY, Union);

    ecs_entity_t base = ecs_new_id(world);
    ecs_entity_t tgt = ecs_new_id(world);
    ecs_new_w_pair(world, EcsIsA, base);

    Probe ctx = {0};
    ecs_observer_init(world, &(ecs_observer_desc_t){
        .filter.terms[0] = {
            .id = Tag,
            .src.flags = EcsUp
        },
        .events = {EcsOnAdd},
        .callback = Trigger,
        .ctx = &ctx
    });
    test_int(ctx.invoked, 0);
    
    ecs_add_id(world, base, Tag);
    test_int(ctx.invoked, 1);

    ecs_os_zeromem(&ctx);
    ecs_add_pair(world, base, RelX, tgt);
    test_int(ctx.invoked, 0);

    ecs_add_pair(world, base, RelY, tgt);
    test_int(ctx.invoked, 0);

    ecs_fini(world);
}
