'use strict';

/**
 * motionPath.ts — Spline-based motion path editor for AnimationEditor.
 *
 * Cubic Bézier paths that can be evaluated at any t ∈ [0..1]
 * to drive bone position or object translation.
 */
// ── Path Creation ───────────────────────────────────────────────
/** Create a straight-line motion path between two points. */
function createLinearPath(id, from, to, name = "Linear Path") {
    return {
        id,
        name,
        closed: false,
        points: [
            { pos: from, cpIn: from, cpOut: lerp2(from, to, 1 / 3) },
            { pos: to, cpIn: lerp2(from, to, 2 / 3), cpOut: to },
        ],
    };
}
/** Create a circular motion path. */
function createCircularPath(id, cx, cy, radius, name = "Circular Path") {
    // Approximate circle with 4 cubic Bézier segments
    const k = 0.5522847498; // (4/3)*tan(π/8)
    const r = radius;
    const points = [
        { pos: { x: cx + r, y: cy }, cpIn: { x: cx + r, y: cy - r * k }, cpOut: { x: cx + r, y: cy + r * k } },
        { pos: { x: cx, y: cy + r }, cpIn: { x: cx + r * k, y: cy + r }, cpOut: { x: cx - r * k, y: cy + r } },
        { pos: { x: cx - r, y: cy }, cpIn: { x: cx - r, y: cy + r * k }, cpOut: { x: cx - r, y: cy - r * k } },
        { pos: { x: cx, y: cy - r }, cpIn: { x: cx - r * k, y: cy - r }, cpOut: { x: cx + r * k, y: cy - r } },
    ];
    return { id, name, points, closed: true };
}
/** Create a figure-8 motion path. */
function createFigure8Path(id, cx, cy, width, height, name = "Figure-8 Path") {
    const hw = width / 2;
    const hh = height / 2;
    const points = [
        { pos: { x: cx, y: cy }, cpIn: { x: cx - hw * 0.6, y: cy + hh }, cpOut: { x: cx + hw * 0.6, y: cy - hh } },
        { pos: { x: cx + hw, y: cy - hh }, cpIn: { x: cx + hw * 0.4, y: cy - hh }, cpOut: { x: cx + hw, y: cy } },
        { pos: { x: cx, y: cy }, cpIn: { x: cx + hw * 0.6, y: cy + hh }, cpOut: { x: cx - hw * 0.6, y: cy - hh } },
        { pos: { x: cx - hw, y: cy + hh }, cpIn: { x: cx - hw, y: cy }, cpOut: { x: cx - hw * 0.4, y: cy + hh } },
    ];
    return { id, name, points, closed: true };
}
// ── Path Evaluation ─────────────────────────────────────────────
function lerp2(a, b, t) {
    return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
}
function cubicBezier(p0, p1, p2, p3, t) {
    const t2 = t * t;
    const t3 = t2 * t;
    const mt = 1 - t;
    const mt2 = mt * mt;
    const mt3 = mt2 * mt;
    return {
        x: mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,
        y: mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y,
    };
}
function cubicBezierDerivative(p0, p1, p2, p3, t) {
    const mt = 1 - t;
    return {
        x: 3 * mt * mt * (p1.x - p0.x) + 6 * mt * t * (p2.x - p1.x) + 3 * t * t * (p3.x - p2.x),
        y: 3 * mt * mt * (p1.y - p0.y) + 6 * mt * t * (p2.y - p1.y) + 3 * t * t * (p3.y - p2.y),
    };
}
/** Evaluate a position on the motion path at parameter t ∈ [0..1]. */
function evaluatePath(path, t) {
    var _a, _b;
    const n = path.closed ? path.points.length : path.points.length - 1;
    if (n <= 0)
        return (_b = (_a = path.points[0]) === null || _a === void 0 ? void 0 : _a.pos) !== null && _b !== void 0 ? _b : { x: 0, y: 0 };
    const tClamped = path.closed ? ((t % 1) + 1) % 1 : Math.max(0, Math.min(1, t));
    const segFloat = tClamped * n;
    const seg = Math.min(Math.floor(segFloat), n - 1);
    const localT = segFloat - seg;
    const i0 = seg;
    const i1 = path.closed ? (seg + 1) % path.points.length : seg + 1;
    const p0 = path.points[i0];
    const p1 = path.points[i1];
    return cubicBezier(p0.pos, p0.cpOut, p1.cpIn, p1.pos, localT);
}
/** Get the tangent direction at parameter t. */
function evaluatePathTangent(path, t) {
    const n = path.closed ? path.points.length : path.points.length - 1;
    if (n <= 0)
        return { x: 1, y: 0 };
    const tClamped = path.closed ? ((t % 1) + 1) % 1 : Math.max(0, Math.min(1, t));
    const segFloat = tClamped * n;
    const seg = Math.min(Math.floor(segFloat), n - 1);
    const localT = segFloat - seg;
    const i0 = seg;
    const i1 = path.closed ? (seg + 1) % path.points.length : seg + 1;
    const p0 = path.points[i0];
    const p1 = path.points[i1];
    const d = cubicBezierDerivative(p0.pos, p0.cpOut, p1.cpIn, p1.pos, localT);
    const len = Math.hypot(d.x, d.y) || 1;
    return { x: d.x / len, y: d.y / len };
}
/** Approximate total path length by sampling. */
function pathLength(path, samples = 64) {
    let len = 0;
    let prev = evaluatePath(path, 0);
    for (let i = 1; i <= samples; i++) {
        const cur = evaluatePath(path, i / samples);
        len += Math.hypot(cur.x - prev.x, cur.y - prev.y);
        prev = cur;
    }
    return len;
}
/** Compute arc-length parameterization lookup table. */
function arcLengthTable(path, samples = 64) {
    const table = [0];
    let prev = evaluatePath(path, 0);
    let cumLen = 0;
    for (let i = 1; i <= samples; i++) {
        const t = i / samples;
        const cur = evaluatePath(path, t);
        cumLen += Math.hypot(cur.x - prev.x, cur.y - prev.y);
        table.push(cumLen);
        prev = cur;
    }
    // Normalize to [0..1]
    const total = cumLen || 1;
    for (let i = 0; i <= samples; i++)
        table[i] /= total;
    return table;
}
/** Convert distance fraction → parametric t using arc-length table. */
function distanceToT(arcTable, d) {
    const n = arcTable.length - 1;
    const clamped = Math.max(0, Math.min(1, d));
    for (let i = 0; i < n; i++) {
        if (arcTable[i + 1] >= clamped) {
            const segLen = arcTable[i + 1] - arcTable[i];
            const frac = segLen > 0 ? (clamped - arcTable[i]) / segLen : 0;
            return (i + frac) / n;
        }
    }
    return 1;
}
// ── Animation Evaluation ────────────────────────────────────────
const EASING_FNS = {
    "linear": (t) => t,
    "ease-in": (t) => t * t,
    "ease-out": (t) => t * (2 - t),
    "ease-in-out": (t) => t * t * (3 - 2 * t),
};
/** Evaluate a motion path animation at a given time (ms). Returns position on path. */
function evaluateMotionPathAnimation(path, anim, timeMs) {
    var _a;
    if (anim.keyframes.length === 0)
        return null;
    const kfs = [...anim.keyframes].sort((a, b) => a.time - b.time);
    const totalDuration = kfs[kfs.length - 1].time - kfs[0].time;
    let time = timeMs;
    if (anim.loop && totalDuration > 0) {
        time = kfs[0].time + ((timeMs - kfs[0].time) % totalDuration + totalDuration) % totalDuration;
    }
    // Find surrounding keyframes
    if (time <= kfs[0].time)
        return evaluatePath(path, kfs[0].t);
    if (time >= kfs[kfs.length - 1].time)
        return evaluatePath(path, kfs[kfs.length - 1].t);
    for (let i = 0; i < kfs.length - 1; i++) {
        if (time >= kfs[i].time && time <= kfs[i + 1].time) {
            const segDur = kfs[i + 1].time - kfs[i].time;
            const rawT = segDur > 0 ? (time - kfs[i].time) / segDur : 0;
            const easing = EASING_FNS[(_a = kfs[i].easing) !== null && _a !== void 0 ? _a : "linear"];
            const easedT = easing(rawT);
            const pathT = kfs[i].t + (kfs[i + 1].t - kfs[i].t) * easedT;
            return evaluatePath(path, pathT);
        }
    }
    return evaluatePath(path, kfs[kfs.length - 1].t);
}
// ── Preset Paths ────────────────────────────────────────────────
const PRESET_PATHS = {
    bouncingArc: (cx, cy, w, h) => createLinearPath("preset-arc", { x: cx - w / 2, y: cy }, { x: cx + w / 2, y: cy }, "Bouncing Arc"),
    circle: (cx, cy, r) => createCircularPath("preset-circle", cx, cy, r, "Circle"),
    figure8: (cx, cy, w, h) => createFigure8Path("preset-figure8", cx, cy, w, h, "Figure-8"),
};

exports.PRESET_PATHS = PRESET_PATHS;
exports.arcLengthTable = arcLengthTable;
exports.createCircularPath = createCircularPath;
exports.createFigure8Path = createFigure8Path;
exports.createLinearPath = createLinearPath;
exports.distanceToT = distanceToT;
exports.evaluateMotionPathAnimation = evaluateMotionPathAnimation;
exports.evaluatePath = evaluatePath;
exports.evaluatePathTangent = evaluatePathTangent;
exports.pathLength = pathLength;
//# sourceMappingURL=motionPath.js.map
