import { combine_hash } from "../../core/collection/array/combine_hash.js";

/**
 * Sentinel for {@link Interpolated#key} before assignment. The
 * {@link InterpolationSystem} replaces it with the entity id at link time.
 * @type {number}
 */
export const INTERPOLATION_KEY_UNSET = -1;

/**
 * Default {@link Interpolated#sourceId}: the local fixed-step timeline (the
 * {@link InterpolationSystem}'s own log, blended on `fixedStepTick`). Every
 * physics-driven body uses this. Other source ids are registered with the
 * system (e.g. a network playout) and assigned to remote entities.
 * @type {number}
 */
export const INTERPOLATION_SOURCE_LOCAL = 0;

/**
 * Marker component opting an entity into render-time interpolation, and the
 * cooperation point producers (physics, network) and the consumer
 * ({@link InterpolationSystem}) share.
 *
 * Carries:
 *  - `key`: the integer identity this entity's snapshots are filed under in the
 *    interpolation log. Defaults to {@link INTERPOLATION_KEY_UNSET}; the
 *    {@link InterpolationSystem} assigns it the entity id at link time when
 *    unset — the common case needs no manual setup. The network layer can set it
 *    to the `network_id` instead, so a replicated entity keeps one identity
 *    across peers.
 *  - `interpolands`: the {@link Interpoland} kinds to blend for this entity (one
 *    pose interpoland in the common case; more for entities that smooth several
 *    quantities).
 *  - `snap`: when true, the next consume shows the latest snapshot exactly (no
 *    blend) and the flag clears — used to avoid sliding across a teleport.
 *
 * Transient render configuration: NOT serialized or replicated.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class Interpolated {

    /**
     * Log key. {@link INTERPOLATION_KEY_UNSET} until the system assigns the
     * entity id (or a producer assigns a network_id).
     * @type {number}
     */
    key = INTERPOLATION_KEY_UNSET;

    /**
     * When true, the next consume snaps to the latest snapshot (no blend) and
     * the flag clears. Set on teleport so the render doesn't slide across the jump.
     * @type {boolean}
     */
    snap = false;

    /**
     * The interpoland kinds to blend for this entity.
     * @type {Interpoland[]}
     */
    interpolands = [];

    /**
     * Which interpolation timeline this entity is blended on — a plain id, NOT a
     * resource or behavior (those live on the {@link InterpolationSystem}, keyed
     * by this id). {@link INTERPOLATION_SOURCE_LOCAL} (the default) is the local
     * fixed-step clock + the system's own log; a remote/networked entity carries
     * the id of a source the network registered with the system (its
     * render-delayed playout + log), so one render pass blends local and remote
     * entities coherently.
     * @type {number}
     */
    sourceId = INTERPOLATION_SOURCE_LOCAL;

    /**
     * Value equality over the configurable state: the timeline ({@link sourceId})
     * and the interpoland kinds (compared by their stable `type_id`). The
     * system-assigned log {@link key} and the transient {@link snap} flag are
     * excluded.
     *
     * @param {Interpolated} other
     * @returns {boolean}
     */
    equals(other) {
        if (other === this) {
            return true;
        }
        if (other === null || other === undefined || other.isInterpolated !== true) {
            return false;
        }
        if (this.sourceId !== other.sourceId) {
            return false;
        }
        const a = this.interpolands;
        const b = other.interpolands;
        if (a.length !== b.length) {
            return false;
        }
        for (let i = 0; i < a.length; i++) {
            if (a[i].type_id !== b[i].type_id) {
                return false;
            }
        }
        return true;
    }

    /**
     * Hash over the same configurable state as {@link equals}. Equal components
     * hash equal.
     * @returns {number}
     */
    hash() {
        let h = this.sourceId | 0;
        const ips = this.interpolands;
        for (let i = 0; i < ips.length; i++) {
            h = combine_hash(h, ips[i].type_id | 0);
        }
        return h;
    }

    /**
     * Copy `other`'s state into this component. The interpoland list is
     * duplicated as a new array sharing the (stateless, shared) interpoland
     * descriptors; `key`, `snap`, and `sourceId` are copied verbatim.
     *
     * @param {Interpolated} other
     * @returns {Interpolated} this
     */
    copy(other) {
        this.key = other.key;
        this.snap = other.snap;
        this.sourceId = other.sourceId;
        this.interpolands = other.interpolands.slice();
        return this;
    }
}

/**
 * @readonly
 * @type {string}
 */
Interpolated.typeName = "Interpolated";

/**
 * @readonly
 * @type {boolean}
 */
Interpolated.prototype.isInterpolated = true;
