/**
 * Activity A of the distributed render pipeline.
 *
 * `buildLocalExecutionPlan(projectDir, config, executionPlanDir)` composes the
 * existing render stages (compile → probe → extract videos → audio → freeze)
 * into a self-contained local execution directory that downstream chunk
 * workers consume:
 *
 *     <planDir>/
 *     ├── plan.json
 *     ├── compiled/                # compileForRender output (self-contained)
 *     ├── video-frames/            # per-video JPEG sequences (dereferenced)
 *     ├── audio.m4a                # only when composition has audio
 *     └── meta/
 *         ├── composition.json
 *         ├── encoder.json         # LockedRenderConfig
 *         └── chunks.json
 *
 * Pure function over local paths. No networking. Two invocations with the
 * same inputs produce the same execution-plan hash. Transport adapters use
 * that representation either through the legacy v1 `plan()` wrapper or the
 * v2 manifest/CAS publisher.
 *
 * Banned configurations (GPU encode, hardware browser GL, system primary
 * fonts) are rejected at plan time via `planValidation.ts` so chunk workers
 * never have to handle them.
 */
import { type CanvasResolution } from "@hyperframes/core";
import { type EngineConfig, type VideoFrameFormat, type AudioProcessingFailure } from "@hyperframes/engine";
import { type ProducerLogger } from "../../logger.js";
import { type RenderJob, type RenderStrictness } from "../renderOrchestrator.js";
import { type ChunkSliceJson } from "../render/stages/freezePlan.js";
import { type DistributedFormat } from "./shared.js";
import { type PlanProtocolV1Descriptor } from "./planProtocol.js";
import { type PlanSizeBreakdown } from "./planSize.js";
/**
 * Caller-supplied configuration for a distributed render. `fps`, `width`,
 * `height`, and `format` are required; everything else carries a default
 * sensible for AWS Lambda fan-out.
 */
export interface DistributedRenderConfig {
    /** Integer frame rate. Distributed renders only accept integer fps; the in-process renderer's `Fps` rational handles NTSC. */
    fps: 24 | 30 | 60;
    width: number;
    height: number;
    /**
     * Output container format. HDR mp4 is not supported in distributed
     * mode — `plan()` refuses it up front with a typed
     * `FormatNotSupportedInDistributedError`. The in-process renderer
     * supports it.
     *
     * `"webm"` (VP9 + Opus) is distributed-supported via closed-GOP
     * concat-copy: `lockGopForChunkConcat=true` forces a keyframe at every
     * chunk boundary and disables libvpx-vp9's alt-ref frames so chunk
     * files stitch losslessly. See `chunkEncoder.ts` for the VP9 args and
     * `tests/distributed/_smoke/webm-concat-copy.test.ts` for the gating
     * experiment that proved the contract.
     */
    format: DistributedFormat;
    /**
     * Codec selection for `format: "mp4"`. `"h264"` (the default) → libx264 +
     * yuv420p; `"h265"` → libx265 + yuv420p with closed-GOP keyint params
     * (`min-keyint=N:scenecut=0:open-gop=0:repeat-headers=1`) so chunked
     * concat-copy round-trips losslessly the same way h264 does. Ignored for
     * `format: "mov"` (always ProRes 4444) and `format: "png-sequence"`
     * (no encoder). Passing `codec` with a non-mp4 format throws at plan
     * time so caller errors surface immediately rather than producing a
     * silently-wrong planDir.
     */
    codec?: "h264" | "h265";
    quality?: "draft" | "standard" | "high";
    /** Constant-rate-factor override; mutually exclusive with `bitrate`. */
    crf?: number;
    /** Target video bitrate (e.g. `"10M"`); mutually exclusive with `crf`. */
    bitrate?: string;
    /**
     * Source-video frame extraction format. Defaults to `"auto"`, matching the
     * in-process renderer: alpha/alpha-capable sources extract as PNG, other
     * sources extract as JPG unless the caller explicitly requests `"png"`.
     */
    videoFrameFormat?: VideoFrameFormat;
    /** Output resolution preset; engages Chrome `deviceScaleFactor` supersampling. */
    outputResolution?: CanvasResolution;
    /**
     * True when `outputResolution` was normalized from an aspect-agnostic alias
     * (`1080p`, `hd`, `4k`, `uhd`) — the compile stage re-targets the preset
     * to the composition's orientation.
     */
    outputResolutionAspectAgnostic?: boolean;
    /**
     * Frames per chunk. When explicitly set, that value is used and
     * `chunkCount = min(maxParallelChunks, ceil(totalFrames / chunkSize))`
     * — useful when the caller wants a specific per-chunk runtime
     * regardless of fan-out. When `undefined` (the default), `plan()`
     * auto-sizes from `maxParallelChunks` so the caller's fan-out
     * intent is honored: `effectiveChunkSize = max(MIN_CHUNK_SIZE,
     * ceil(totalFrames / maxParallelChunks))`. The auto-size floor
     * (`MIN_CHUNK_SIZE = 10`) keeps per-chunk fixed overhead from
     * swamping the parallelism gain on tiny renders.
     *
     * `effectiveChunkSize` also drives `LockedRenderConfig.gopSize` — every
     * chunk's first frame is an IDR keyframe, so smaller chunks mean a
     * tighter GOP and larger encoded files. Callers who optimize for
     * output bytes (rather than wall-clock parallelism) should pass an
     * explicit `chunkSize` matching their target GOP — e.g. `240` for the
     * old 8-second-GOP behavior.
     */
    chunkSize?: number;
    /** Default `16`. Caps long renders to fewer-but-longer chunks for operational fairness. */
    maxParallelChunks?: number;
    /**
     * Upper bound on frames-per-chunk, in frames. Optional; when omitted (the
     * default) chunk sizing is unchanged. When set, chunking targets the fewest
     * chunks whose per-chunk frame count stays at or below this bound, still
     * capped by `maxParallelChunks`:
     *
     *     chunkCount = clamp(ceil(totalFrames / targetChunkFrames), 1, maxParallelChunks)
     *
     * This bounds per-chunk render *time* (which scales with frames-per-chunk) so
     * a single chunk can't exceed a downstream per-chunk timeout on a long video,
     * while short videos still collapse to few chunks. It is a ceiling, not a
     * fixed size: a video short enough to fit in fewer chunks gets fewer. Ignored
     * when `chunkSize` is set (an explicit fixed size already pins per-chunk
     * frames). Mutually exclusive with `chunkSize` in intent; if both are passed,
     * `chunkSize` wins and `targetChunkFrames` is a no-op.
     */
    targetChunkFrames?: number;
    /** Runtime hint; consumed by future per-runtime budget checks. The current implementation records the value but does not enforce. */
    runtimeCap?: "lambda" | "temporal" | "cloud-run-job" | "k8s-job" | "none";
    /**
     * Reject compositions whose primary font-family resolves to a host-OS /
     * generic family. Default `true` for distributed renders — overriding to
     * `false` is unsupported and exists only as an escape hatch for tests.
     */
    rejectOnSystemFonts?: boolean;
    /**
     * Threaded into the `injectDeterministicFontFaces` font loader. Default
     * `true` — distributed renders must not silently fall back to system fonts.
     */
    failClosedFontFetch?: boolean;
    /** HDR is not supported in distributed mode; `force-hdr` trips a `FormatNotSupportedInDistributedError`. Defaults to `force-sdr`. */
    hdrMode?: "auto" | "force-sdr";
    /**
     * Opt-in exact-CFR re-encode at the assemble stage. When `true`, the
     * stitched output is re-encoded once with `-fps_mode cfr -r <fps>` so
     * the stream-level `avg_frame_rate` matches the container's
     * `r_frame_rate` exactly (and the file duration is exact, not
     * PTS-derived). Useful for downstream consumers that strict-check
     * `avg_frame_rate` or ms-precision duration. Default `false` retains
     * the existing `-c copy` stitch path, which is faster and lossless.
     * mp4 only — webm / mov stream-copy paths already produce exact
     * avg_frame_rate. Consumed by `assemble`; does not affect `planHash`
     * (chunks render identically; only the final stitch step differs).
     */
    cfr?: boolean;
    logger?: ProducerLogger;
    /** JSON-safe engine snapshot carried across cloud/process boundaries. */
    engineConfig?: EngineConfig;
    /** Optional engine config override (env vars are not read when provided). */
    producerConfig?: EngineConfig;
    /** Entry HTML file relative to `projectDir`. Defaults to `"index.html"`. */
    entryFile?: string;
    /** Strict rejects correctness warnings; best-effort returns a qualified outcome. */
    strictness?: RenderStrictness;
    /** Caller-supplied AbortSignal. Threaded through compile / probe / extract / audio stages. */
    abortSignal?: AbortSignal;
    /**
     * Hard ceiling on `<planDir>/` size in bytes; trips a non-retryable
     * `PLAN_TOO_LARGE` error after freeze. Defaults to
     * {@link PLAN_DIR_SIZE_LIMIT_BYTES} (2 GB — fits inside AWS Lambda's
     * 10 GB `/tmp` budget alongside the chunk worker's frame buffer +
     * ffmpeg working set). Adapters that deploy onto storage with
     * tighter ceilings can pass a smaller cap; tests pass a tiny cap to
     * exercise the throw path. This applies to the monolithic v1 transport;
     * `planV2()` emits content-addressed role dependencies and bypasses it.
     */
    planDirSizeLimitBytes?: number;
    /**
     * Render-time variable overrides for the composition. Snapshotted into
     * `meta/encoder.json` at plan time and re-injected by every chunk
     * worker as `window.__hfVariables` before the first capture, mirroring
     * the in-process renderer's
     * `RenderConfig.variables` → `CaptureOptions.variables` path. The
     * runtime helper `getVariables()` merges these over the declared
     * defaults from `<html data-composition-variables="…">`.
     *
     * Folded into `planHash`: different variables produce different hashes
     * because rendered frames depend on the injected values. Must be a
     * JSON-serializable plain object — `freezePlan`'s canonical-JSON pass
     * throws on non-serializable values (functions, Symbols, BigInts) when
     * the variables reach this layer. Adapters that ship to Lambda (the
     * `@hyperframes/aws-lambda` SDK) also validate the shape client-side
     * before any AWS call so the rejection lands at the SDK boundary
     * rather than mid-plan; the producer-side throw is the fallback.
     */
    variables?: Record<string, unknown>;
}
/** Shared local representation consumed by both distributed plan transports. */
export interface LocalExecutionPlan {
    executionPlanDir: string;
    executionPlanHash: string;
    chunkCount: number;
    totalFrames: number;
    fps: 24 | 30 | 60;
    width: number;
    height: number;
    format: DistributedFormat;
    ffmpegVersion: string;
    producerVersion: string;
}
export interface BuildLocalExecutionPlanOptions {
    /**
     * Transport-specific size ceiling for the local execution representation.
     * Legacy v1 uses the monolithic plan-directory limit; v2 disables that
     * transport cap because it publishes role-scoped content-addressed objects.
     */
    readonly executionPlanSizeLimitBytes?: number;
}
/**
 * Result of the legacy v1 {@link plan} wrapper. The `planHash` is the
 * content-addressed identifier that adapters key replay short-circuits off of.
 *
 * @deprecated Use `planV2()` or `planV2WithPublisher()` for new integrations.
 * The v1 result remains supported for existing transports.
 */
export interface PlanResult {
    planDir: string;
    planProtocol: Readonly<PlanProtocolV1Descriptor>;
    planHash: string;
    chunkCount: number;
    totalFrames: number;
    fps: 24 | 30 | 60;
    width: number;
    height: number;
    format: DistributedFormat;
    ffmpegVersion: string;
    producerVersion: string;
}
/** Applies the same audio correctness policy used by the in-process renderer. */
export declare function applyDistributedAudioWarningPolicy(job: RenderJob, audioError: string, audioFailures?: readonly AudioProcessingFailure[], log?: ProducerLogger): void;
/**
 * Top-level directory names skipped by the `projectDir → planDir/compiled/`
 * pre-seed copy. Real projects often contain `node_modules/`, VCS metadata,
 * and harness artifacts that have no business in a planDir — they bloat
 * the 2 GB planDir cap and slow the S3/Lambda round-trip for no benefit.
 * Matched against the path relative to `projectDir` so a `projectDir`
 * whose absolute path happens to contain one of these names (e.g.
 * `~/work/output/comp/`) doesn't false-positive-skip the entire copy.
 */
export declare const PLAN_PROJECT_DIR_SKIP_SEGMENTS: ReadonlySet<string>;
/**
 * Default chunk size in frames (~8s @ 30fps; fits Lambda's 15-min cap).
 * Used when the caller explicitly passes this value. When `chunkSize` is
 * `undefined`, `plan()` auto-sizes from `maxParallelChunks` instead.
 */
export declare const DEFAULT_CHUNK_SIZE = 240;
/** Default cap on parallel chunks for operational fairness across renders. */
export declare const DEFAULT_MAX_PARALLEL_CHUNKS = 16;
/**
 * Floor for the auto-sized `chunkSize` when the caller leaves it
 * `undefined`. Anything smaller hits a per-chunk fixed-overhead wall
 * (worker boot + plan download + planHash recompute + ffmpeg init) that
 * outweighs the parallelism gain on tiny renders.
 */
export declare const MIN_CHUNK_SIZE = 10;
/**
 * Default hard ceiling on `<planDir>/` size in bytes. 2 GB fits inside
 * AWS Lambda's 10 GB `/tmp` alongside the chunk worker's captured frames
 * and ffmpeg's temporary files. Compositions that exceed this can opt into
 * `planV2()` or fall back to the in-process renderer.
 */
export declare const PLAN_DIR_SIZE_LIMIT_BYTES: number;
/**
 * Non-retryable error code raised when `plan()` produces a planDir whose
 * total size exceeds the configured limit. Workflow adapters key retry
 * policies off `code` — the planDir would fail the same way on every
 * retry, so the failure must not auto-retry.
 */
export declare const PLAN_TOO_LARGE = "PLAN_TOO_LARGE";
/** Typed error raised when the produced planDir exceeds {@link PLAN_DIR_SIZE_LIMIT_BYTES}. */
export declare class PlanTooLargeError extends Error {
    readonly code: typeof PLAN_TOO_LARGE;
    readonly sizeBytes: number;
    readonly limitBytes: number;
    readonly breakdown?: PlanSizeBreakdown;
    readonly observedAt?: string;
    constructor(sizeBytes: number, limitBytes: number, breakdown?: PlanSizeBreakdown, observedAt?: string);
}
/**
 * Non-retryable error code raised when `plan()` is asked for an output
 * format that distributed mode doesn't support (currently: HDR mp4). The
 * same config would fail on every retry, so the failure must not
 * auto-retry.
 */
export declare const FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED = "FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED";
/**
 * Typed error raised by `plan()` for outputs that distributed mode
 * refuses to ship.
 *
 *   - mp4 + HDR (PQ / HLG) — chunked HDR pre-extract + HDR signaling
 *     re-apply on the assembled file is not implemented yet.
 *
 * The in-process renderer (`executeRenderJob`) handles it natively.
 *
 * WebM was previously refused here; v0.7+ supports it via closed-GOP
 * concat-copy. See {@link DistributedRenderConfig.format} for the
 * supported set and {@link rejectUnsupportedDistributedFormat} for the
 * gate.
 */
export declare class FormatNotSupportedInDistributedError extends Error {
    readonly code: typeof FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED;
    readonly format: string;
    readonly reason: string;
    constructor(format: string, reason: string);
}
/**
 * Reject formats the distributed pipeline cannot ship (HDR mp4 only —
 * webm is supported as of v0.7 via closed-GOP concat-copy).
 *
 * Throws {@link FormatNotSupportedInDistributedError} with a message
 * naming the rejected format. Runs at the very top of `plan()` so a
 * banned input never produces a partial planDir.
 *
 * Exported so adapters can call the same gate at their own input layer
 * (Step Functions input validation, Temporal workflow start) before the
 * activity even runs — the resulting non-retryable error then matches
 * what `plan()` would have thrown.
 */
export declare function rejectUnsupportedDistributedFormat(config: Pick<DistributedRenderConfig, "format" | "hdrMode">): void;
/**
 * Walk `<planDir>/` depth-first and sum all regular file sizes. Symlinks
 * are not traversed — they shouldn't appear inside a planDir to begin with
 * (the extract stage materializes them), and following them could push the
 * walker outside the planDir.
 */
export declare function measurePlanDirBytes(planDir: string): number;
/**
 * Compute `(chunkCount, effectiveChunkSize)` from total frames and the
 * caller's chunking knobs. The operative chunk size is
 * `resolvedChunkSize` — equal to `configChunkSize` when the caller
 * passes one, otherwise auto-sized from `maxParallelChunks`:
 *
 *     resolvedChunkSize   = configChunkSize ?? max(MIN_CHUNK_SIZE, ceil(totalFrames / maxParallelChunks))
 *     chunkCount          = min(maxParallelChunks, ceil(totalFrames / resolvedChunkSize))
 *     effectiveChunkSize  = max(resolvedChunkSize, ceil(totalFrames / chunkCount))
 *     chunkCount          = min(chunkCount, ceil(totalFrames / effectiveChunkSize))  // drop empty trailing slice
 *
 * Long renders auto-rescale to fewer-but-longer chunks rather than
 * fragmenting infinitely. Returned `chunkCount >= 1` (`totalFrames === 0`
 * is rejected upstream); `effectiveChunkSize >= resolvedChunkSize`.
 *
 * The auto-sizer (triggered when `configChunkSize` is `undefined`) honors
 * the caller's fan-out intent: passing `maxParallelChunks=16` without
 * `chunkSize` produces 16 chunks (subject to the `MIN_CHUNK_SIZE` floor
 * on tiny renders). Explicit numbers, including `240`, take precedence.
 *
 * Optional `targetChunkFrames` caps per-chunk frames in the auto-sized path:
 * the auto-sizer then targets `clamp(ceil(totalFrames / targetChunkFrames), 1,
 * maxParallelChunks)` chunks, so short videos collapse to fewer chunks and long
 * videos add chunks (up to the cap) to keep each one under the bound. It is a
 * no-op when omitted, and ignored when `configChunkSize` is set.
 */
export declare function resolveChunkPlan(totalFrames: number, configChunkSize: number | undefined, maxParallelChunks: number, targetChunkFrames?: number): {
    chunkCount: number;
    effectiveChunkSize: number;
};
/**
 * Slice `totalFrames` into `chunkCount` consecutive ranges. Each chunk gets
 * `effectiveChunkSize` frames except the last, which absorbs the remainder
 * so the union is exactly `[0, totalFrames)`. `endFrame` is the EXCLUSIVE
 * upper bound — chunk workers iterate `i in [startFrame, endFrame)`.
 */
export declare function buildChunkSlices(totalFrames: number, chunkCount: number, effectiveChunkSize: number): ChunkSliceJson[];
/** Test-visible construction of the engine config used by distributed planning. */
export declare function resolveDistributedEngineConfig(config: DistributedRenderConfig): EngineConfig;
/**
 * Build the shared local execution representation used by both transport
 * protocols. See the module docstring for the directory layout.
 */
export declare function buildLocalExecutionPlan(projectDir: string, config: DistributedRenderConfig, executionPlanDir: string, options?: Readonly<BuildLocalExecutionPlanOptions>): Promise<LocalExecutionPlan>;
/**
 * Legacy v1 transport wrapper around {@link buildLocalExecutionPlan}.
 *
 * @deprecated Use `planV2()` or `planV2WithPublisher()` for new integrations.
 * This wrapper, its layout, and its result shape remain supported.
 */
export declare function plan(projectDir: string, config: DistributedRenderConfig, planDir: string): Promise<PlanResult>;
//# sourceMappingURL=plan.d.ts.map