import type { Keyframe, KeyframeProperty } from "./composition.js";
/** Time-equality tolerance for dedup at the same (property, time). ~one-quarter of a 60 fps frame. */
export declare const KEYFRAME_TIME_EPSILON = 0.0001;
/**
 * Returns the interpolated value for `property` at clip-local time `currentTime`,
 * or `null` if the array contains no keyframe for that property.
 *
 * Contract:
 * - Out-of-range times **clamp** to the nearest endpoint (D3). No extrapolation, no wrap.
 * - The easing of the *outgoing* keyframe (segment start) controls the segment.
 * - Input order is irrelevant — internally sorts a filtered copy by `time` ASC.
 * - Volume on a VideoClip / OverlayClip is structurally allowed but renderers ignore it (D5).
 *   This helper does NOT enforce (clip × property) compatibility — that is the renderer's job.
 */
export declare function interpolateProperty(keyframes: readonly Keyframe[] | undefined, property: KeyframeProperty, currentTime: number): number | null;
/**
 * Idempotent insert: replaces an existing entry at `(property, time ± KEYFRAME_TIME_EPSILON)`,
 * otherwise inserts the new entry and re-sorts the array by `(property, time)`. D4 contract:
 * adding twice at the same (property, time) yields the second value, never duplicates.
 *
 * Returns a new array — never mutates the input.
 */
export declare function addOrReplaceKeyframe(keyframes: readonly Keyframe[] | undefined, next: Keyframe): Keyframe[];
/**
 * Partition + rebase a clip's keyframes when the clip is split at clip-local
 * time `localSplit` (#46).
 *
 * Keyframe `time` is clip-local seconds measured from the clip's timeline start
 * (each renderer mounts the clip in a `<Sequence from={trackOffset}>` and reads
 * `useCurrentFrame()`, which resets to 0). So splitting at timeline `atSec`
 * means `localSplit = atSec - clip.trackOffset` for video / audio / overlay
 * alike. The previous `{ ...orig }` shallow copy carried the WHOLE array into
 * both halves unrebased, so child B fired child A's keyframes at the wrong
 * clip-local times and never reached the late ones — every split of an animated
 * clip corrupted both halves.
 *
 * For each property that has keyframes we:
 *   - keep the keyframes strictly before the split in `a` and those strictly
 *     after in `b` (rebased by `-localSplit` back to clip-local 0);
 *   - insert a BOUNDARY keyframe at the split (value = the original curve's
 *     interpolated value there) into both halves — `a` at `localSplit`, `b` at
 *     `0`. This preserves C0 continuity (no jump at the cut) and pins each
 *     half's held value instead of letting it fall back to the clip's static
 *     transform. For the default `linear` easing the reconstruction is exact:
 *     a line split into two sub-segments is still the same line. For a manually
 *     eased segment the boundary keeps endpoints exact and the interior is a
 *     close approximation (perfect easing subdivision would need De Casteljau).
 *
 * Boundary easing: child B's `time:0` keyframe inherits the easing of the
 * segment the split fell inside (the easing of the last keyframe at/under the
 * split), so the leading sub-segment keeps the original segment's character.
 * Child A's boundary is terminal, so its easing is irrelevant (kept linear).
 *
 * Returns fresh arrays; never mutates the input. Empty/undefined input → empty
 * halves (text clips carry no keyframes — D8 — so this is a harmless no-op).
 */
export declare function splitKeyframesAtLocal(keyframes: readonly Keyframe[] | undefined, localSplit: number): {
    a: Keyframe[];
    b: Keyframe[];
};
//# sourceMappingURL=keyframes.d.ts.map