/**
 * File Server for Render Mode
 *
 * Lightweight HTTP server that serves the project directory inside Docker.
 * Key responsibility: inject the verified Hyperframe runtime + render mode extension
 * into index.html on-the-fly, so Puppeteer can load the composition with
 * all relative URLs (compositions, CSS, JS, assets) resolving correctly.
 */
import { injectScriptsAtHeadStart } from "@hyperframes/core/compiler";
import { type Fps } from "@hyperframes/core";
import { type ProducerLogger } from "../logger.js";
export { injectScriptsAtHeadStart };
type PathModuleLike = {
    resolve: (...segments: string[]) => string;
    sep: string;
};
type IsPathInsideOptions = {
    resolveSymlinks?: boolean;
    /**
     * Path module used for resolution and separator comparison. Defaults to
     * `node:path` for the running platform. Tests inject `path.win32` /
     * `path.posix` to exercise cross-platform behavior on a single OS.
     */
    pathModule?: PathModuleLike;
};
/**
 * Returns true iff `child` is the same as, or nested inside, `parent` after
 * path normalization. Used to reject path-traversal attempts (e.g.
 * GET `/../etc/passwd`) before opening any file.
 *
 * `path.join(root, "..")` normalizes traversal segments and can escape `root`
 * entirely, so the join return value alone is not a safe guard. Callers must
 * resolve both sides and compare prefixes with the platform separator
 * appended to `parent` to avoid `/foo` matching `/foobar`.
 *
 * Exported for unit tests; not part of the public package surface.
 */
export declare function isPathInside(child: string, parent: string, options?: IsPathInsideOptions): boolean;
/**
 * Result of parsing a `Range:` request header against a known total size.
 *
 * - `kind: "satisfiable"`: `start <= end < size`. The response should be 206
 *   with `Content-Range: bytes start-end/size` and the sliced body.
 * - `kind: "unsatisfiable"`: the header was syntactically valid (`bytes=...`)
 *   but the resolved range falls outside `[0, size)` (e.g. `start >= size`,
 *   `end < start`, or a suffix request on a zero-byte file). Per RFC 7233
 *   the response should be 416 with `Content-Range: bytes (asterisk)/size`.
 * - `kind: "absent"`: there is no `Range:` header on the request, or it is
 *   syntactically malformed, uses a non-`bytes` unit, or requests multiple
 *   ranges. RFC 7233 allows ignoring such headers and serving the full body
 *   with a 200, which is what callers should do.
 */
export type RangeRequest = {
    kind: "satisfiable";
    start: number;
    end: number;
} | {
    kind: "unsatisfiable";
} | {
    kind: "absent";
};
/**
 * Parse a single-range `Range:` request header per RFC 7233 §2.1.
 *
 * Supports the three forms of `bytes=...`:
 *   - `bytes=START-END`: closed range, both bounds inclusive.
 *   - `bytes=START-`: open-ended, serve from START to EOF.
 *   - `bytes=-SUFFIX`: last SUFFIX bytes.
 *
 * Multi-range requests (`bytes=0-99,200-299`) are treated as `absent`. The
 * caller serves the full body with 200. The hyperframes producer's use case
 * (Chrome `<video>` seeks, range-aware media stack) only ever issues single
 * ranges, so we don't take on the multipart-byteranges complexity here.
 *
 * Exported for unit tests; not part of the public package surface.
 */
export declare function parseRangeHeader(header: string | null | undefined, size: number): RangeRequest;
/**
 * Options for {@link buildVirtualTimeShim}.
 */
export interface VirtualTimeShimOptions {
    /**
     * When `true`, the shim additionally replaces `Math.random` and
     * `crypto.getRandomValues` with a Mulberry32-seeded PRNG keyed by the
     * current frame's virtual time. Compositions that call `Math.random()`
     * during render then produce byte-identical pixels across machines and
     * across replays of the same `(planDir, chunkIndex)` pair.
     *
     * Default `false`: leaves `Math.random` / `crypto.getRandomValues` native,
     * preserving the in-process renderer's non-deterministic behavior for
     * compositions that rely on it.
     */
    seedRandomFromFrame: boolean;
}
/**
 * Build the page-side virtual-time shim script.
 *
 * The shim freezes `Date.now`, `performance.now`, and the rAF/setTimeout
 * pipeline so a render seek can deterministically advance the page's
 * notion of "now". The renderer issues `__HF_VIRTUAL_TIME__.seekToTime(ms)`
 * before every frame capture; everything timing-related on the page sees
 * exactly `ms` until the next seek.
 *
 * When `options.seedRandomFromFrame` is `true`, the returned script also
 * installs a seeded `Math.random` / `crypto.getRandomValues` keyed by the
 * current virtual time — so compositions with stochastic visuals retry
 * identically. When `false`, the shim emits no random-override code; the
 * page's native `Math.random` is left alone (the in-process default).
 */
export declare function buildVirtualTimeShim(options: VirtualTimeShimOptions): string;
/**
 * Default in-process virtual-time shim — `seedRandomFromFrame: false`.
 * Existing call sites (`renderOrchestrator`, `probeStage`) import this
 * constant. Distributed callers build their own with seeding enabled.
 */
declare const VIRTUAL_TIME_SHIM: string;
/**
 * Early stub: ensures `window.__hf` exists *before* any user `<script>` in
 * `<body>` executes, and batches GSAP timeline construction via
 * requestAnimationFrame to prevent the main-thread hang described in
 * https://github.com/heygen-com/hyperframes/issues/1231.
 *
 * Source: packages/producer/stubs/hf-early-stub.ts
 * Generated: packages/producer/src/generated/hf-early-stub-inline.ts
 * Injected at the very start of `<head>` so it runs before all other scripts.
 */
declare const HF_EARLY_STUB: string;
/**
 * Page-side compositing opt-in flag stub.
 *
 * When the engine is launched with `enablePageSideCompositing: true`, the
 * orchestrator injects this stub into the very top of every served HTML
 * page. The flag is read by `@hyperframes/shader-transitions`' engine-mode
 * `init()` to switch from the default opacity-flip mode (which leaves
 * shader blending to the Node side via the hf#677 layered pipeline) to a
 * page-side WebGL compositor that runs the shader inside Chrome and
 * exposes a single opaque RGB frame for the engine to capture.
 *
 * Sentinel ONLY — no logic here. The compositor itself ships inside
 * `@hyperframes/shader-transitions` and is loaded by the composition's
 * regular script bundle.
 *
 * Default OFF: when the flag is not set, behavior is byte-identical to
 * the existing layered path.
 */
export declare const HF_PAGE_SIDE_COMPOSITING_STUB = "(function() {\n  if (typeof window === \"undefined\") return;\n  window.__HF_PAGE_SIDE_COMPOSITING__ = true;\n})();";
/**
 * Bridge script: maps window.__player (Hyperframe runtime) → window.__hf (engine protocol).
 * Injected after RENDER_MODE_SCRIPT so the engine's frameCapture can find window.__hf.
 *
 * This script *patches* the existing __hf object rather than replacing it, so
 * fields written during page-script execution (e.g. transitions metadata from
 * @hyperframes/shader-transitions) are preserved through to engine query time.
 */
declare const HF_BRIDGE_SCRIPT = "(function() {\n  var __realSetInterval =\n    window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === \"function\"\n      ? window.__HF_VIRTUAL_TIME__.originalSetInterval\n      : window.setInterval.bind(window);\n  var __realClearInterval =\n    window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === \"function\"\n      ? window.__HF_VIRTUAL_TIME__.originalClearInterval\n      : window.clearInterval.bind(window);\n  function getDeclaredDuration() {\n    var root = document.querySelector('[data-composition-id]');\n    if (!root) return 0;\n    var d = Number(root.getAttribute('data-duration'));\n    if (Number.isFinite(d) && d > 0) return d;\n    var comps = document.querySelectorAll('[data-composition-src]');\n    var maxEnd = 0;\n    for (var i = 0; i < comps.length; i++) {\n      var start = Number(comps[i].getAttribute('data-start')) || 0;\n      var dur = Number(comps[i].getAttribute('data-duration')) || 0;\n      if (dur > 0) maxEnd = Math.max(maxEnd, start + dur);\n    }\n    if (maxEnd > 0) console.warn('[HF Bridge] No root data-duration; derived ' + maxEnd + 's from sub-compositions');\n    return maxEnd;\n  }\n  function seekSameOriginChildFrames(frameWindow, nextTimeMs) {\n    var frames;\n    try {\n      frames = frameWindow.frames;\n    } catch (_error) {\n      return;\n    }\n    if (!frames || typeof frames.length !== \"number\") return;\n    for (var i = 0; i < frames.length; i++) {\n      var childWindow = null;\n      try {\n        childWindow = frames[i];\n        if (!childWindow || childWindow === frameWindow) continue;\n        if (\n          childWindow.__HF_VIRTUAL_TIME__ &&\n          typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === \"function\"\n        ) {\n          childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);\n        }\n      } catch (_error) {\n        continue;\n      }\n      seekSameOriginChildFrames(childWindow, nextTimeMs);\n    }\n  }\n  function bridge() {\n    var p = window.__player;\n    if (!p || typeof p.renderSeek !== \"function\" || typeof p.getDuration !== \"function\") {\n      return false;\n    }\n    var hf = window.__hf || {};\n    Object.defineProperty(hf, \"duration\", {\n      configurable: true,\n      enumerable: true,\n      get: function() {\n        // While the GSAP tween-batching interceptor (HF_EARLY_STUB) is draining\n        // queued tweens via rAF, the real timelines are still empty. Return 0\n        // here so pollHfReady in the engine keeps waiting (its condition is\n        // __hf.duration > 0), preventing the capture pipeline from seeking\n        // empty timelines and producing blank/incorrect frames.\n        if (window.__hfTimelinesBuilding) return 0;\n        if (!window.__renderReady) return 0;\n        var d = p.getDuration();\n        return d > 0 ? d : getDeclaredDuration();\n      },\n    });\n    hf.seek = function(t, options) {\n      p.renderSeek(t, options);\n      var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;\n      if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === \"function\") {\n        window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);\n      }\n      seekSameOriginChildFrames(window, nextTimeMs);\n    };\n    window.__hf = hf;\n    return true;\n  }\n  if (bridge()) return;\n  var iv = __realSetInterval(function() {\n    if (bridge()) __realClearInterval(iv);\n  }, 50);\n})();";
export interface FileServerOptions {
    projectDir: string;
    compiledDir?: string;
    port?: number;
    /** Scripts injected into <head> of every served HTML file before authored scripts. */
    preHeadScripts?: string[];
    /** Scripts injected into <head> of index.html. Default: verified Hyperframe runtime. */
    headScripts?: string[];
    /** Scripts injected before </body> of index.html. Default: render mode extension. */
    bodyScripts?: string[];
    /** Actual render fps so page-side runtime quantization matches the output container. */
    fps?: Fps;
    /** Strip embedded runtime scripts from HTML before injection. Default: true. */
    stripEmbeddedRuntime?: boolean;
}
export interface FileServerHandle {
    url: string;
    port: number;
    close: () => void;
    addPreHeadScript: (script: string) => void;
}
/**
 * Set before the Hyperframes runtime executes so render/probe pages can avoid
 * preview-only initialization work that mutates the live visual timeline.
 * Audio automation is discovered by the producer in an isolated pass and
 * baked before frame capture.
 */
export declare const RENDER_CAPTURE_MODE_SHIM = "globalThis.__HF_RENDER_CAPTURE_MODE = true;";
/**
 * Close a file server handle, swallowing and logging any error.
 *
 * `FileServerHandle.close` tears down the underlying http.Server, whose
 * `close()` throws `ERR_SERVER_NOT_RUNNING` if the server is already torn down
 * (for example a cancellation path that closed it once already). An unguarded
 * throw inside a cleanup or `finally` block would mask the original render or
 * plan result, so cleanup callers must go through this instead of calling
 * `close()` directly.
 */
export declare function closeFileServerSafely(fileServer: Pick<FileServerHandle, "close">, label: string, log?: ProducerLogger): void;
export declare function createFileServer(options: FileServerOptions): Promise<FileServerHandle>;
export { HF_BRIDGE_SCRIPT, HF_EARLY_STUB, VIRTUAL_TIME_SHIM };
//# sourceMappingURL=fileServer.d.ts.map