import { ParentEntity } from "../../ecs/parent/ParentEntity.js";
import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
import { System } from "../../ecs/System.js";
import { Transform } from "../../ecs/transform/Transform.js";
import { Collider } from "./Collider.js";
import { RigidBody } from "./RigidBody.js";

/**
 * Observes every entity that gains a `(Collider, Transform)` tuple and
 * attaches the collider to the appropriate {@link PhysicsSystem} body:
 *
 *  - If the same entity also carries a {@link RigidBody}, the collider is
 *    a same-entity attachment — body = this entity.
 *  - Else if the entity carries a {@link ParentEntity} pointing at a body
 *    entity (which has a `RigidBody`), the collider is a child-collider of
 *    a compound body — body = parent entity.
 *  - Otherwise the collider is "orphan" (no body to attach to). The
 *    observer silently skips; a warning would just spam the console for
 *    common authoring transients (collider component added before the
 *    body is constructed). Add the body first, then the colliders.
 *
 * Pairs with {@link PhysicsSystem}: register both in the EntityManager.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class ColliderObserverSystem extends System {

    /**
     * @param {PhysicsSystem} physicsSystem
     */
    constructor(physicsSystem) {
        super();

        /** @type {PhysicsSystem} */
        this.__physics = physicsSystem;

        this.dependencies = [Collider, Transform];

        this.components_used = [
            ResourceAccessSpecification.from(Collider, ResourceAccessKind.Read | ResourceAccessKind.Write),
            ResourceAccessSpecification.from(Transform, ResourceAccessKind.Read),
            ResourceAccessSpecification.from(RigidBody, ResourceAccessKind.Read),
            ResourceAccessSpecification.from(ParentEntity, ResourceAccessKind.Read),
        ];
    }

    /**
     * @param {Collider} collider
     * @param {Transform} transform
     * @param {number} entity
     */
    link(collider, transform, entity) {
        const em = this.entityManager;
        if (em === null || em === undefined) return;
        const ecd = em.dataset;
        if (ecd === null || ecd === undefined) return;

        const body_entity = this.__resolve_body_entity(ecd, entity);
        if (body_entity === -1) return; // orphan — body must be added first

        this.__physics.attach_collider(body_entity, collider, transform, entity);
    }

    /**
     * @param {Collider} collider
     * @param {Transform} transform
     * @param {number} entity
     */
    unlink(collider, transform, entity) {
        const em = this.entityManager;
        if (em === null || em === undefined) return;
        const ecd = em.dataset;
        if (ecd === null || ecd === undefined) return;

        const body_entity = this.__resolve_body_entity(ecd, entity);
        if (body_entity === -1) return;

        this.__physics.detach_collider(body_entity, collider);
    }

    /**
     * Walk RigidBody-on-self → ParentEntity → orphan, returning the entity
     * that owns the body this collider belongs to.
     *
     * @private
     * @param {EntityComponentDataset} ecd
     * @param {number} entity
     * @returns {number} body entity or -1
     */
    __resolve_body_entity(ecd, entity) {
        if (ecd.hasComponent(entity, RigidBody)) {
            return entity;
        }
        const parent = ecd.getComponent(entity, ParentEntity);
        if (parent !== undefined && parent.entity >= 0
            && ecd.hasComponent(parent.entity, RigidBody)) {
            return parent.entity;
        }
        return -1;
    }
}
