/**
 * Base class for movement abilities (wall-run, mantle, slide, ledge-grab,
 * wall-jump, etc.). Each ability is a small state machine that:
 *
 *   1. Reports whether it wants to activate ({@link canActivate})
 *   2. Owns the player's motion while active ({@link tick})
 *   3. Reports whether it can be preempted right now ({@link canInterrupt})
 *   4. Cleans up when control is released ({@link onDeactivate})
 *
 * Abilities are owned by an {@link AbilitySet}. At most one ability is
 * active at a time. Preemption and queueing are handled by the set — see
 * its docs for the hybrid policy.
 *
 * Subclasses should set:
 *   - `priority` — used to order preemption attempts
 *   - `name` — for telemetry / debugging
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class Ability {
    constructor() {
        /**
         * Higher priority preempts lower. Defaults:
         *   slide=10, mantle=30, ledgeGrab=40, wallRun=50, wallJump=60.
         * Wall-jump is highest because it's the most input-direct response
         * (button press) and should fire reliably regardless of what else
         * is going on.
         * @type {number}
         */
        this.priority = 0;

        /**
         * Stable string identifier for telemetry / debugging overlays.
         * @type {string}
         */
        this.name = "Ability";
    }

    /**
     * Ask: do you want to take control RIGHT NOW?
     *
     * Read controller state and sensor data; return true iff conditions
     * are met. Side-effect-free; called every tick when no ability is
     * active, and once on each tick when this ability is queued behind
     * another.
     *
     * @param {*} controller  FirstPersonPlayerController
     * @param {*} runtime     per-entity runtime
     * @param {*} sensors     FirstPersonSensors (may be null in stub mode)
     * @returns {boolean}
     */
    // eslint-disable-next-line no-unused-vars
    canActivate(controller, runtime, sensors) {
        return false;
    }

    /**
     * Called once when this ability acquires control. Use for one-shot
     * setup (kicking impulses, capturing entry velocity, etc.).
     *
     * @param {*} controller
     * @param {*} runtime
     */
    // eslint-disable-next-line no-unused-vars
    onActivate(controller, runtime) {}

    /**
     * Runs each fixed step while active. The ability OWNS body motion
     * during this window — writes directly to runtime.velocity*, the body
     * Transform position, springs, etc. Returning false releases control
     * back to the AbilitySet (which will run base locomotion or the next
     * queued ability in the same tick).
     *
     * The `system` parameter is the {@link FirstPersonPlayerControllerSystem}
     * itself, passed through so abilities can call shared helpers like
     * vertical-integration-and-ground-resolution without re-implementing
     * them. Use it sparingly — the cleaner the ability's tick is, the
     * easier it is to reason about ownership.
     *
     * @param {*} controller
     * @param {*} runtime
     * @param {*} bodyTransform
     * @param {number} dt
     * @param {*} system  the FirstPersonPlayerControllerSystem instance
     * @returns {boolean} true = stay active; false = release control
     */
    // eslint-disable-next-line no-unused-vars
    tick(controller, runtime, bodyTransform, dt, system) {
        return false;
    }

    /**
     * Ask: can a higher-priority ability preempt you right now?
     *
     * Defaults to true (interruptible). Override to declare windows where
     * this ability *must* finish what it's doing — e.g. mantle returns
     * false while on its scripted path; slide returns false for the
     * first ~150ms after entry.
     *
     * @param {*} controller
     * @param {*} runtime
     * @returns {boolean}
     */
    // eslint-disable-next-line no-unused-vars
    canInterrupt(controller, runtime) {
        return true;
    }

    /**
     * Called once when control is released (either tick returned false,
     * or a higher-priority ability preempted). Clean up internal state.
     *
     * @param {*} controller
     * @param {*} runtime
     */
    // eslint-disable-next-line no-unused-vars
    onDeactivate(controller, runtime) {}
}
