import { cloneAnimationValue, deserializeKeyframe, lerpNumber, lerpVec3, serializeKeyframe, slerpQuat, validateKeyframes } from "./Keyframe.js";
export class AnimationTrack {
    target;
    valueType;
    keyframes;
    duration;
    constructor(descriptor) {
        if (descriptor.target.trim().length === 0) {
            throw new Error("AnimationTrack target cannot be empty.");
        }
        validateKeyframes(descriptor.keyframes);
        this.target = descriptor.target;
        this.valueType = descriptor.valueType;
        this.keyframes = descriptor.keyframes.map((keyframe) => ({ ...keyframe, value: cloneAnimationValue(keyframe.value) }));
        this.duration = this.keyframes[this.keyframes.length - 1].time;
    }
    sample(time) {
        if (!Number.isFinite(time)) {
            throw new Error("AnimationTrack sample time must be finite.");
        }
        if (time <= this.keyframes[0].time) {
            return cloneAnimationValue(this.keyframes[0].value);
        }
        const last = this.keyframes[this.keyframes.length - 1];
        if (time >= last.time) {
            return cloneAnimationValue(last.value);
        }
        for (let index = 0; index < this.keyframes.length - 1; index += 1) {
            const a = this.keyframes[index];
            const b = this.keyframes[index + 1];
            if (time >= a.time && time <= b.time) {
                if (a.interpolation === "step" || b.time === a.time) {
                    return cloneAnimationValue(a.value);
                }
                const t = (time - a.time) / (b.time - a.time);
                if (a.interpolation === "cubicspline") {
                    return interpolateCubicSpline(this.valueType, a.value, b.value, a.outTangent, b.inTangent, t, b.time - a.time);
                }
                return interpolate(this.valueType, a.value, b.value, t);
            }
        }
        return cloneAnimationValue(last.value);
    }
    toJSON() {
        return {
            target: this.target,
            valueType: this.valueType,
            keyframes: this.keyframes.map((keyframe) => serializeKeyframe(keyframe))
        };
    }
    static fromJSON(serialized) {
        return new AnimationTrack({
            target: serialized.target,
            valueType: serialized.valueType,
            keyframes: serialized.keyframes.map((keyframe) => deserializeKeyframe(keyframe))
        });
    }
}
function interpolateCubicSpline(type, a, b, outTangent, inTangent, t, deltaTime) {
    if (outTangent === undefined || inTangent === undefined) {
        throw new Error("CUBICSPLINE keyframes require outTangent on the first keyframe and inTangent on the next keyframe.");
    }
    switch (type) {
        case "scalar":
            return cubicScalar(a, b, outTangent, inTangent, t, deltaTime);
        case "vector3":
            return [
                cubicScalar(a[0], b[0], outTangent[0], inTangent[0], t, deltaTime),
                cubicScalar(a[1], b[1], outTangent[1], inTangent[1], t, deltaTime),
                cubicScalar(a[2], b[2], outTangent[2], inTangent[2], t, deltaTime)
            ];
        case "quaternion":
            return normalizeCubicQuaternion(a, b, outTangent, inTangent, t, deltaTime);
        case "number-array":
            return cubicNumberArray(a, b, outTangent, inTangent, t, deltaTime);
        case "boolean":
        case "string":
            throw new Error(`CUBICSPLINE interpolation is not supported for ${type} animation tracks.`);
    }
}
function cubicScalar(a, b, outTangent, inTangent, t, deltaTime) {
    const t2 = t * t;
    const t3 = t2 * t;
    return ((2 * t3 - 3 * t2 + 1) * a +
        (t3 - 2 * t2 + t) * deltaTime * outTangent +
        (-2 * t3 + 3 * t2) * b +
        (t3 - t2) * deltaTime * inTangent);
}
function cubicNumberArray(a, b, outTangent, inTangent, t, deltaTime) {
    if (a.length !== b.length || a.length !== outTangent.length || b.length !== inTangent.length) {
        throw new Error("CUBICSPLINE number-array keyframes require matching value and tangent lengths.");
    }
    return a.map((value, index) => cubicScalar(value, b[index], outTangent[index], inTangent[index], t, deltaTime));
}
function normalizeCubicQuaternion(a, b, outTangent, inTangent, t, deltaTime) {
    return slerpQuat([0, 0, 0, 1], [
        cubicScalar(a[0], b[0], outTangent[0], inTangent[0], t, deltaTime),
        cubicScalar(a[1], b[1], outTangent[1], inTangent[1], t, deltaTime),
        cubicScalar(a[2], b[2], outTangent[2], inTangent[2], t, deltaTime),
        cubicScalar(a[3], b[3], outTangent[3], inTangent[3], t, deltaTime)
    ], 1);
}
function interpolate(type, a, b, t) {
    switch (type) {
        case "scalar":
            return lerpNumber(a, b, t);
        case "vector3":
            return lerpVec3(a, b, t);
        case "quaternion":
            return slerpQuat(a, b, t);
        case "number-array":
            return interpolateNumberArray(a, b, t);
        case "boolean":
        case "string":
            return t < 1 ? a : b;
    }
}
function interpolateNumberArray(a, b, t) {
    if (a.length !== b.length) {
        throw new Error("number-array animation keyframes require matching lengths.");
    }
    return a.map((value, index) => lerpNumber(value, b[index], t));
}
//# sourceMappingURL=AnimationTrack.js.map