/**
 * Content-Addressed Extraction Cache
 *
 * Video frame extraction is the single most expensive phase of a render
 * after capture. Repeat renders of the same composition (preview → final,
 * studio iteration) re-extract identical frames from the same source file,
 * burning ffmpeg time that adds no value. This module keys extracted frame
 * bundles on the (path, mtime, size, mediaStart, duration, fps, format,
 * optional transform)
 * tuple so re-renders resolve to a pre-extracted directory instead of
 * re-invoking ffmpeg.
 *
 * ### Scheme
 *
 * - The key is the SHA-256 of a stable JSON encoding of the tuple above.
 * - Cache entries live under `<rootDir>/<SCHEMA_PREFIX><key[0..16]>/` so
 *   `ls` output and tracing logs stay short. Truncation to 16 hex chars
 *   leaves 64 bits of entropy — collision risk at cache scale is negligible.
 * - Frames are extracted into a unique `<entry>.partial-<pid>-<uuid>/` dir.
 *   Once all frames are written, the partial dir receives the `.hf-complete`
 *   sentinel and is atomically renamed to the final key dir. Concurrent
 *   same-key writers may duplicate ffmpeg work, but readers only ever serve
 *   complete entries.
 * - The sentinel mtime is touched on hits and used as the cache's LRU clock.
 *   `gcExtractionCache` evicts by that mtime and also clears old partial dirs
 *   left behind by crashed writers.
 *
 * ### Versioning
 *
 * `SCHEMA_PREFIX` bumps when the cache-contents invariant changes (e.g.
 * extraction format, frame layout). Old entries under the previous prefix
 * become inert and can be gc'd by the caller.
 */
import type { VideoMetadata } from "../utils/ffprobe.js";
/** Filename prefix for extracted frames. Shared with the extractor. */
export declare const FRAME_FILENAME_PREFIX = "frame_";
/** Sentinel filename written after a cache entry is fully populated. */
export declare const COMPLETE_SENTINEL = ".hf-complete";
/** Marker file stamped after each GC sweep; drives the staleness fallback. */
export declare const GC_MARKER = ".hf-last-gc";
/**
 * Current schema version. Bump when the cache-contents invariant changes.
 * v2 -> v3: one-pass VFR extraction (-fps_mode cfr) replaces the two-pass
 * VFR-to-CFR re-encode, changing frame contents for VFR sources under
 * identical key tuples. Without the bump, warm v2 entries (two-pass frames)
 * would keep being served across the deploy boundary.
 * v3 -> v4: the target fps identity is the exact FFmpeg argument instead of
 * a JavaScript number. This invalidates entries created after rational NTSC
 * rates had already been rounded to a decimal.
 */
export declare const SCHEMA_PREFIX = "hfcache-v4-";
export type CacheFrameFormat = "jpg" | "png";
export interface CacheKeyInput {
    /** Absolute path to the source video file. Part of the key so moved files
     *  re-extract rather than match by (size, mtime) alone. */
    videoPath: string;
    /** Source file modification time in ms (floored). Invalidates the key on edit. */
    mtimeMs: number;
    /** Source file size in bytes. Invalidates the key on content change. */
    size: number;
    /** Seconds into source the composition starts reading (video.mediaStart). */
    mediaStart: number;
    /** Seconds of source the composition uses. Infinity is normalized to -1
     *  so callers that pass an unresolved "natural duration" still produce a
     *  stable key across invocations. */
    duration: number;
    /** Exact target output frame-rate argument (for example `30000/1001`). */
    fps: string;
    /** Output image format. */
    format: CacheFrameFormat;
    /** Optional source transform applied during extraction. */
    transform?: string;
}
export interface CacheEntry {
    /** Absolute path to the cache entry directory. */
    dir: string;
    /** Full 64-char SHA-256 hex digest (parent of the truncated key). */
    keyHash: string;
}
export interface CacheLookup {
    /** Cache entry information — always returned even on a miss so the caller
     *  can derive a partial dir and publish it after extraction. */
    entry: CacheEntry;
    /** True when the entry exists AND carries the completion sentinel. */
    hit: boolean;
}
export interface CachePublishResult {
    dir: string;
    published: boolean;
}
/**
 * Read `(mtimeMs, size)` for a path. Returns `null` if the file is missing —
 * callers should skip the cache path for that entry so the extractor surfaces
 * the real file-not-found error. Returning a zero-stat sentinel would let two
 * missing files share the same `(0, 0)` tuple and pollute the cache with an
 * orphaned entry.
 */
export declare function readKeyStat(videoPath: string): {
    mtimeMs: number;
    size: number;
} | null;
/**
 * Compute the SHA-256 hex digest for a cache key input.
 */
export declare function computeCacheKey(input: CacheKeyInput): string;
/**
 * Derive the truncated cache-entry directory name from a full key hash.
 * Exposed so tests and the entry dir resolver share one truncation rule.
 */
export declare function cacheEntryDirName(keyHash: string): string;
/**
 * Look up a cache entry by key input. Returns the resolved entry path plus a
 * `hit` flag. On miss, callers should extract frames into a
 * `partialCacheEntryDir(entry)` directory and publish it with
 * `publishCacheEntry` once extraction succeeds.
 */
export declare function lookupCacheEntry(rootDir: string, input: CacheKeyInput): CacheLookup;
/**
 * Ensure a cache entry's directory exists so the extractor can write into it.
 * Idempotent: `mkdirSync({recursive:true})` is a no-op when the dir exists.
 */
export declare function ensureCacheEntryDir(entry: CacheEntry): void;
/**
 * Unique render-owned directory used to populate a cache entry before the
 * atomic publish rename.
 */
export declare function partialCacheEntryDir(entry: CacheEntry): string;
export declare function publishCacheEntry(entry: CacheEntry, partialDir: string): CachePublishResult;
/**
 * Update the LRU clock for a complete cache entry. Misses and filesystem
 * races are harmless: the caller can still use the entry it already found.
 */
export declare function touchCacheEntry(entry: CacheEntry): void;
/**
 * Write the completion sentinel so subsequent lookups treat this entry as a
 * hit. Must be called only after every frame has been written. The extractor
 * now publishes new entries via `publishCacheEntry`; this helper remains
 * exported for tests and legacy callers that materialize entries directly.
 *
 * Concurrency: direct mark is non-atomic and should not be used for shared
 * writer paths. `publishCacheEntry` writes the sentinel inside a partial dir
 * and atomically renames it into place, so concurrent writers duplicate work
 * but never serve torn frames.
 */
export declare function markCacheEntryComplete(entry: CacheEntry): void;
export interface GcStats {
    /** Complete entries evicted by the LRU size sweep. */
    evictedEntries: number;
    /** Bytes reclaimed by evicted entries. */
    evictedBytes: number;
    /** Aged `.partial-*` dirs (crashed writers) removed. */
    agedPartialsRemoved: number;
}
/**
 * Opportunistic size-capped LRU cleanup for extracted video frames.
 *
 * Scans only direct cache-looking children and never throws. The age guard is
 * a liveness heuristic, not a lock. Returns counts so the caller can surface
 * eviction pressure in render observability.
 */
/**
 * Whether the staleness fallback should force a sweep: true when no sweep
 * marker exists or the last sweep is older than `maxAgeMs`. Lets 100%-warm
 * workloads (which skip the per-miss sweep) still reclaim space eventually.
 */
export declare function gcSweepDue(rootDir: string, maxAgeMs: number): boolean;
export declare function gcExtractionCache(rootDir: string, opts: {
    maxBytes: number;
    minAgeMs: number;
}): GcStats;
/**
 * Rebuild the in-memory frame index for a cached entry. Called on cache hits
 * so the extractor's caller receives the same `ExtractedFrames` shape it
 * would get from a fresh extraction — without re-running ffmpeg or ffprobe.
 *
 * The `metadata` argument is the `VideoMetadata` probed in the extractor's
 * Phase 2 (pre-preflight). Passing it here avoids an extra ffprobe on the
 * hit path.
 */
export interface RehydrateOptions {
    videoId: string;
    srcPath: string;
    fps: number;
    format: CacheFrameFormat;
    metadata: VideoMetadata;
}
export interface RehydratedFrames {
    videoId: string;
    srcPath: string;
    outputDir: string;
    framePattern: string;
    fps: number;
    totalFrames: number;
    metadata: VideoMetadata;
    framePaths: Map<number, string>;
}
export declare function rehydrateCacheEntry(entry: CacheEntry, options: RehydrateOptions): RehydratedFrames;
//# sourceMappingURL=extractionCache.d.ts.map