import { assert } from "../../../core/assert.js";
import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
import { System } from "../../ecs/System.js";
import { NetworkIdentity } from "./components/NetworkIdentity.js";

/**
 * ECS system that owns the per-entity replication lifecycle.
 *
 * Responsibilities:
 *   - When an entity gains a {@link NetworkIdentity}, allocate a `network_id`
 *     in the peer's slot table (or honour an explicit pre-set one — used by
 *     `Snapshotter` and incoming-packet flows).
 *   - When an entity loses its `NetworkIdentity` (or is destroyed), free the
 *     slot.
 *
 * **Not** responsible for the per-tick `begin_tick` / `end_tick` cadence —
 * the engine's `EntityManager` doesn't expose pre/post-simulate hooks, so the
 * application code wraps its `engine.simulate(dt)` call:
 *
 * ```js
 *   peer.begin_tick(frame_number);
 *   engine.simulate(dt);
 *   peer.end_tick();
 * ```
 *
 * Add this system to the engine once per `NetworkPeer` instance:
 *
 * ```js
 *   const peer = new NetworkPeer({ ... });
 *   const network_system = new NetworkSystem(peer);
 *   engine.entityManager.addSystem(network_system);
 * ```
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class NetworkSystem extends System {

    dependencies = [NetworkIdentity];

    components_used = [
        ResourceAccessSpecification.from(NetworkIdentity, ResourceAccessKind.Write),
    ];

    /**
     * @param {NetworkPeer} peer
     */
    constructor(peer) {
        super();
        assert.ok(peer && peer.slot_table, 'NetworkSystem: peer must be a NetworkPeer instance with a slot_table');

        /**
         * @type {NetworkPeer}
         */
        this.peer = peer;
    }

    /**
     * @param {NetworkIdentity} identity
     * @param {number} entity
     */
    link(identity, entity) {
        if (identity.network_id < 0) {
            // Local spawn: allocate a fresh network_id.
            identity.network_id = this.peer.slot_table.allocate(entity);
        } else {
            // Pre-set (e.g. from a Snapshotter or a remote peer's spawn action).
            // Honour the explicit ID so both sides share the same slot.
            this.peer.slot_table.allocate_at(identity.network_id, entity);
        }
    }

    /**
     * @param {NetworkIdentity} identity
     * @param {number} entity
     */
    unlink(identity, entity) {
        if (identity.network_id >= 0) {
            this.peer.slot_table.free(identity.network_id);
            identity.network_id = -1;
        }
    }
}
