/**
 * freezePlan — write the meta/{composition,encoder,chunks}.json + plan.json
 * manifest at the end of `plan()`, compute the planHash from the frozen
 * artifacts, and return the manifest path.
 *
 * Called from `services/distributed/plan.ts` after all earlier phases have
 * materialized their on-disk artifacts under `<planDir>/`. The function is
 * deliberately the last step so `planHash` is computed from the actual bytes
 * the chunk worker will read — not from intermediate values the controller
 * has in memory.
 */
import type { Fps } from "@hyperframes/core";
import { type PlanDimensions } from "./planHash.js";
/**
 * The encoder configuration locked in at plan time.
 */
export interface LockedRenderConfig {
    captureMode: "beginframe" | "screenshot";
    forceScreenshot: boolean;
    deviceScaleFactor: number;
    useLayeredHdrComposite: boolean;
    /** Hard-pinned to "software" in v1 distributed renders. */
    browserGpuMode: "software";
    warmupTicks: number;
    encoder: "libx264-software" | "libx265-software" | "libvpx-vp9-software" | "prores-software" | "png-sequence";
    /**
     * Caller-supplied quality enum, persisted so chunk workers can rebuild
     * the matching `getEncoderPreset(quality, format, …)` instead of
     * inferring quality from the encoder discriminant (which loses
     * information when the encoder→quality table grows non-injective).
     */
    quality: "draft" | "standard" | "high";
    ffmpegVersion: string;
    preset: string;
    crf?: number;
    bitrate?: string;
    /**
     * VP9-only: new WebM plans persist the resolved libvpx-vp9 `-cpu-used`
     * value so distributed workers reproduce the controller's speed/quality
     * choice. Omitted for non-VP9 plans to avoid shifting unrelated planHash
     * baselines. Optional for compatibility with older WebM planDirs; workers
     * replay those with the historical distributed VP9 value.
     */
    vp9CpuUsed?: number;
    /** Equal to chunkSize for closed-GOP concat-copy. */
    gopSize: number;
    closedGop: true;
    forceKeyframes: "n=0";
    pixelFormat: string;
    chunkSize: number;
    chunkCount: number;
    /** Snapshot of `PRODUCER_RUNTIME_*` env vars at plan time. */
    runtimeEnv: Record<string, string>;
    /**
     * Render-time variable overrides snapshotted at plan time. Chunk workers
     * re-inject these into the page as `window.__hfVariables` before the
     * first capture, so every chunk sees the same `getVariables()` resolution
     * the controller used to size the plan.
     *
     * Folded into the canonical encoder.json bytes that feed `planHash` —
     * two plans with different variables produce different hashes (the
     * intended behavior: different variables can produce different rendered
     * frames). Two plans with the same variables produce identical hashes
     * because canonical-JSON sorts object keys.
     *
     * Optional: omitted (undefined) when the caller doesn't pass variables;
     * stripped from the canonical JSON via the same `stripUndefined` pass
     * that handles `crf`/`bitrate`, so an absent value hashes the same as
     * before this field existed.
     */
    variables?: Record<string, unknown>;
}
export interface CompositionMetadataJson {
    durationSeconds: number;
    width: number;
    height: number;
    fps: Fps;
    videoCount: number;
    audioCount: number;
    imageCount: number;
}
export interface ChunkSliceJson {
    index: number;
    startFrame: number;
    /** Exclusive upper bound — chunk workers iterate frames in `[startFrame, endFrame)`. */
    endFrame: number;
}
/**
 * Inputs to `freezePlan`. `planDir` already contains `compiled/`,
 * `video-frames/`, and (optionally) `audio.aac` by the time freezePlan
 * runs — those are materialized by the upstream compile/probe/extract/audio
 * stages composed in `services/distributed/plan.ts`.
 */
export interface FreezePlanInput {
    /** Absolute path to the plan directory being frozen. */
    planDir: string;
    composition: CompositionMetadataJson;
    encoder: LockedRenderConfig;
    chunks: readonly ChunkSliceJson[];
    dimensions: PlanDimensions;
    producerVersion: string;
    /** Hash of the deterministic-font snapshot baked into the plan. */
    fontSnapshotSha: string;
    /** Composition duration in seconds (mirrors `composition.durationSeconds`; carried separately for `plan.json`). */
    durationSeconds: number;
    /** Total frame count, separately materialized for callers that read `plan.json` without parsing chunks.json. */
    totalFrames: number;
    /** Whether `<planDir>/audio.aac` was produced. */
    hasAudio: boolean;
}
export interface FreezePlanResult {
    /** Absolute path to `plan.json`. */
    planJsonPath: string;
    /** Content-addressed planHash; see {@link computePlanHash}. */
    planHash: string;
}
/**
 * Read a frozen plan directory back from disk and recompute its
 * content-addressed `planHash` over the actual on-disk bytes — including
 * the canonical encoder JSON, which is written via
 * {@link canonicalJsonStringify} so reading the file gives us the exact
 * string that fed the controller's hash.
 *
 * Distributed chunk workers call this at boot to verify their planDir is
 * the same one the controller wrote: any mismatch (corrupted artifact,
 * partial S3 download, manual tampering) trips a non-retryable
 * `PLAN_HASH_MISMATCH` before the chunk renders.
 *
 * Throws if `plan.json` or `meta/encoder.json` are missing/malformed —
 * callers should catch those as `MISSING_PLAN_ARTIFACT` rather than
 * lumping them with hash drift.
 */
export declare function recomputePlanHashFromPlanDir(planDir: string): string;
/**
 * Freeze a plan directory: write `meta/*.json` + top-level `plan.json`, then
 * compute `planHash` over the canonicalized contents.
 *
 * The encoder JSON is written via {@link canonicalJsonStringify} so the bytes
 * fed into {@link computePlanHash} match the bytes on disk exactly. Consumers
 * can re-validate a plan by hashing `meta/encoder.json` directly.
 */
export declare function freezePlan(input: FreezePlanInput): Promise<FreezePlanResult>;
//# sourceMappingURL=freezePlan.d.ts.map