import { assert } from "../../../core/assert.js";
import { computeHashFloat } from "../../../core/primitives/numbers/computeHashFloat.js";

/**
 * Single keyframe of {@link AnimationCurve}.
 * Note that {@link AnimationCurve} relies on {@link Keyframe}s to be static, so treat them as immutable.
 * Do not change keyframe values unless you understand the implications of doing so.
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class Keyframe {
    /**
     *
     * @type {number}
     */
    value = 0;

    /**
     * Timestamp/position for the value
     * @type {number}
     */
    time = 0;

    /**
     * Incoming tangent, affects the slope of the curve from previous key
     * @type {number}
     */
    inTangent = 0;

    /**
     * Outgoing tangent, affect the slope of the curve from this key to the next key
     * @type {number}
     */
    outTangent = 0;

    /**
     *
     * @param {number} time
     * @param {number} value
     * @param {number} inTangent
     * @param {number} outTangent
     * @return {Keyframe}
     */
    static from(time, value, inTangent = 0, outTangent = 0) {
        const r = new Keyframe();

        r.set(time, value, inTangent, outTangent);

        return r;
    }

    /**
     *
     * @param {number} time
     * @param {number} value
     * @param {number} inTangent
     * @param {number} outTangent
     */
    set(time, value, inTangent, outTangent) {
        assert.isNumber(time, 'time');
        assert.notNaN(time, 'time');

        assert.isNumber(value, 'value');
        assert.notNaN(value, 'value');

        assert.isNumber(inTangent, 'inTangent');
        assert.notNaN(inTangent, 'inTangent');
        assert.isFinite(inTangent, 'inTangent');

        assert.isNumber(outTangent, 'outTangent');
        assert.notNaN(outTangent, 'outTangent');
        assert.isFinite(outTangent, 'outTangent');

        this.time = time;
        this.value = value;
        this.inTangent = inTangent;
        this.outTangent = outTangent;
    }


    /**
     *
     * @param {Keyframe} other
     */
    copy(other) {
        this.time = other.time;
        this.value = other.value;
        this.inTangent = other.inTangent;
        this.outTangent = other.outTangent;
    }

    /**
     *
     * @return {Keyframe}
     */
    clone() {
        const r = new Keyframe();

        r.copy(this);

        return r;
    }

    /**
     *
     * @param {Keyframe} other
     * @returns {boolean}
     */
    equals(other) {
        return this.time === other.time
            && this.value === other.value
            && this.inTangent === other.inTangent
            && this.outTangent === other.outTangent;
    }

    /**
     *
     * @return {number}
     */
    hash() {
        return computeHashFloat(this.time)
            ^ (computeHashFloat(this.value) * 31);
    }

    toJSON() {
        return {
            value: this.value,
            time: this.time,
            inTangent: this.inTangent,
            outTangent: this.outTangent
        }
    };

    fromJSON({
                 value,
                 time,
                 inTangent,
                 outTangent
             }) {

        this.set(time, value, inTangent, outTangent);
    }

}

/**
 * Useful for type checks
 * @readonly
 * @type {boolean}
 */
Keyframe.prototype.isKeyframe = true;
