/**
 * HTML Compiler for Producer
 *
 * Two-phase compilation that guarantees every media element has data-end:
 * 1. Static pass via core's compileTimingAttrs() (data-start + data-duration → data-end)
 * 2. ffprobe resolution for elements without data-duration
 *
 * Also handles sub-compositions referenced via data-composition-src,
 * recursively extracting nested media from sub-sub-compositions.
 */
import { type ResolvedDuration, type UnresolvedElement } from "@hyperframes/core";
import { type VideoElement, type ImageElement, type AudioElement, type AudioVolumeKeyframe } from "@hyperframes/engine";
import type { Page } from "puppeteer-core";
import { type ProducerLogger } from "../logger.js";
export interface CompiledComposition {
    html: string;
    subCompositions: Map<string, string>;
    videos: VideoElement[];
    audios: AudioElement[];
    images: ImageElement[];
    unresolvedCompositions: UnresolvedElement[];
    /** Assets that resolve outside projectDir. Keys are the path used in HTML, values are absolute filesystem paths. */
    externalAssets: Map<string, string>;
    width: number;
    height: number;
    staticDuration: number;
    renderModeHints: RenderModeHints;
    hasShaderTransitions: boolean;
    /** Author HTML/CSS/scripts use a CSS 3D rendering context (pre-CDN-inline scan). */
    usesThreeDTransforms: boolean;
    /** Author HTML/CSS use mix-blend-mode (pre-CDN-inline scan). */
    usesMixBlendMode: boolean;
    /** Ancestors of the composition root carry a background-image (gradient/url). */
    hasAncestorBackgroundImage: boolean;
}
export declare function injectSdkPositionEditsRenderScript(html: string): string;
export type RenderModeHintCode = "iframe" | "requestAnimationFrame" | "htmlInCanvas";
export interface RenderModeHint {
    code: RenderModeHintCode;
    message: string;
}
export interface RenderModeHints {
    recommendScreenshot: boolean;
    reasons: RenderModeHint[];
}
export declare function detectRenderModeHints(html: string): RenderModeHints;
export declare function detectThreeDTransformUsage(html: string): boolean;
/**
 * Background-image signals on ancestors of the composition root.
 * drawElementImage only paints the captured subtree; drawElementService's
 * per-frame ancestor fill replicates what lies behind it by walking up the
 * DOM for the nearest non-transparent `backgroundColor`. A background-IMAGE
 * (linear-gradient, url) on <body>/<html>/a wrapper reads as transparent in
 * that scan, so a deeper ancestor's solid color paints instead — measured:
 * a body `linear-gradient` replaced by the html background color wherever
 * the subtree left pixels uncovered (30.9 dB min vs baseline), and the
 * damage can set in late enough to slip past the self-verify sample grid.
 * Backgrounds on elements INSIDE the root are painted correctly and are
 * deliberately not matched — this walks only the root's ancestor chain and
 * the style rules that select into it.
 */
export declare function detectAncestorBackgroundImage(html: string): boolean;
export declare function detectShaderTransitionUsage(html: string): boolean;
/**
 * Download external CDN scripts and inline them into the HTML so rendering
 * works without network access (Docker, CI, restricted environments).
 */
export declare function inlineExternalScripts(html: string): Promise<string>;
/**
 * Scan compiled HTML for asset references that resolve outside projectDir.
 * For each, map the normalized in-HTML path to the real filesystem path so
 * the orchestrator can copy them into the compiled output directory.
 *
 * Handles: src/href attributes, CSS url(), inline style url().
 */
export declare function collectExternalAssets(html: string, projectDir: string): {
    html: string;
    externalAssets: Map<string, string>;
};
/**
 * Download any remote `src` URLs on `<video>` and `<audio>` elements into a
 * local subdirectory of `downloadDir`, rewrite the HTML src attributes to
 * relative paths, and return the updated HTML along with a map of
 * `{ relativePath → absoluteLocalPath }` for callers to add to `externalAssets`.
 *
 * Skips URLs that fail to download (warns and preserves the original URL so
 * the browser can still attempt the remote fetch as a fallback).
 *
 * Why: remote S3 sources require Chrome to buffer every video file over the
 * network before `readyState >= 2` (HAVE_CURRENT_DATA). With 10+ large clips
 * this reliably exhausts `pageReadyTimeout`, producing blank black frames for
 * every clip. Localising the sources before the file server starts eliminates
 * the race entirely and keeps the render hermetic.
 */
/** @internal exported for unit testing only */
export declare function localizeRemoteMediaSources(html: string, downloadDir: string): Promise<{
    html: string;
    remoteMediaAssets: Map<string, string>;
}>;
/**
 * Download any remote `src` URLs on `<img>` elements into a local subdirectory
 * of `downloadDir`, rewrite the HTML src attributes to relative paths, and
 * return a `{ relativePath → absoluteLocalPath }` map for the orchestrator.
 *
 * Why: a composition with remote S3 `<img src>` URLs reaches Chrome unchanged;
 * the readiness check can pass before the image is fully decoded, *and* Chrome
 * may evict decoded pixels mid-render under memory pressure and re-fetch from
 * the remote origin. Either path produces blank-frame flicker. Localising the
 * sources before render eliminates both races — once the file is local,
 * Chrome's image cache is bounded by fast disk reads, not S3 latency, so a
 * mid-render re-fetch lands within a frame instead of flickering. This is the
 * primary fix; frameCapture's `pollImagesReady` is the defense-in-depth layer.
 *
 * Scope: only `<img src>` is localised here. Remote `srcset`,
 * `<picture><source>`, SVG `<image href>`, and CSS `background-image: url()`
 * outside `@font-face` are NOT covered — agent-pipeline compositions emit
 * plain `<img src>`, but those are open follow-ups if other shapes appear.
 *
 * This bites agent-pipeline-generated compositions (astral / daphne /
 * hyperion `multi-v2` outputs) which render directly without going through
 * `hyperframes publish`'s archive-time localize step.
 */
/** @internal exported for unit testing only */
export declare function localizeRemoteImageSources(html: string, downloadDir: string): Promise<{
    html: string;
    remoteMediaAssets: Map<string, string>;
}>;
/**
 * Download remote CSS `background-image: url(https://...)` references and rewrite
 * them to local same-origin paths.
 *
 * Why: `drawElementImage` (fast capture) OMITS cross-origin content, so a remote
 * background image renders BLACK on the drawElement path while the screenshot
 * baseline captures it (origin-agnostic) — a whole-region mismatch (e.g. 10f79c0b
 * picsum.photos backgrounds, 9.3 dB). `<img>`/`<video>`/`@font-face` are localized
 * by their own passes; this closes the background-image gap so the fast path sees
 * the same pixels as the baseline.
 *
 * @internal exported for unit testing only
 */
export declare function localizeRemoteBackgroundImages(html: string, downloadDir: string): Promise<{
    html: string;
    remoteMediaAssets: Map<string, string>;
}>;
/** @internal exported for unit testing only */
export declare function localizeRemoteFontFaces(html: string, downloadDir: string): Promise<{
    html: string;
    remoteMediaAssets: Map<string, string>;
}>;
/**
 * Optional behavior toggles for {@link compileForRender}. All fields are
 * additive; omitting `options` preserves the in-process renderer's defaults.
 */
export interface CompileForRenderOptions {
    /**
     * Logger for compile-time diagnostics (e.g. the data-duration vs. media
     * mismatch warning). Optional so non-render callers can omit it.
     */
    log?: ProducerLogger;
    /**
     * Threaded through to {@link injectDeterministicFontFaces}. When `true`,
     * deterministic font resolution and exhausted transient fetch failures
     * surface as typed errors instead of silently falling back to system fonts.
     * Distributed `plan()` sets this to `true` so font availability is part of
     * the planDir's content-addressed hash. Default `false` preserves the
     * in-process behavior.
     */
    failClosedFontFetch?: boolean;
    /**
     * When `true`, fonts not resolved by the bundled alias map or Google Fonts
     * are located on the local filesystem, compressed to woff2, and embedded.
     * Default `true` for local renders. Distributed callers pass `false` to
     * prevent host-specific font capture from leaking into the planDir.
     */
    allowSystemFontCapture?: boolean;
    /** Caller cancellation propagated through compile-time font fetches. */
    abortSignal?: AbortSignal;
    /**
     * Optional persistent cache directory for prep-time animated GIF → WebM
     * transcodes. When omitted, the render's downloadDir is used.
     */
    animatedGifCacheDir?: string;
    /** FFmpeg timeout for animated GIF transcodes. */
    ffmpegProcessTimeout?: number;
    /**
     * Render-time variable overrides (`--variables`). Layered over declared
     * defaults in the compile-time CSS custom-property stylesheet so eval-time
     * reads (GSAP .from immediateRender) see the overridden value — the
     * `window.__hfVariables` injection covers script reads, not var() in CSS.
     */
    variables?: Record<string, unknown>;
}
/**
 * Compile an HTML composition project into a single self-contained HTML string
 * with all media metadata resolved.
 */
export declare function compileForRender(projectDir: string, htmlPath: string, downloadDir: string, options?: CompileForRenderOptions): Promise<CompiledComposition>;
/**
 * Discover media elements from the browser DOM after JavaScript has run.
 * This catches videos/audios whose `src` is set dynamically via JS
 * (e.g. `document.getElementById("pip-video").src = URL`), which the
 * static regex parsers miss because the HTML has `src=""`.
 */
export interface BrowserMediaElement {
    id: string;
    tagName: "video" | "audio" | "image";
    src: string;
    start: number;
    end: number;
    duration: number;
    mediaStart: number;
    loop: boolean;
    hasAudio: boolean;
    volume: number;
    /** The `muted` attribute/property. Preview silences muted media; the mix must too. */
    muted: boolean;
}
export interface BrowserAudioVolumeAutomation {
    id: string;
    keyframes: AudioVolumeKeyframe[];
}
export declare function discoverMediaFromBrowser(page: Page): Promise<BrowserMediaElement[]>;
export declare function discoverAudioVolumeAutomationFromTimeline(page: Page, audioIds: string[], compositionDuration: number, sampleFps: number): Promise<BrowserAudioVolumeAutomation[]>;
export interface VideoVisibilityWindow {
    videoId: string;
    visibleStart: number;
    visibleEnd: number;
}
/**
 * Seek the GSAP timeline to discover when each video's parent scene is visible.
 * Only processes videos with the data-hf-auto-start sentinel (auto-injected timing).
 */
export declare function discoverVideoVisibilityFromTimeline(page: Page, compositionDuration: number): Promise<VideoVisibilityWindow[]>;
/**
 * Resolve composition durations via Puppeteer by querying window.__timelines.
 * The page must already have the interceptor loaded and timelines registered.
 */
export declare function resolveCompositionDurations(page: Page, unresolved: UnresolvedElement[]): Promise<ResolvedDuration[]>;
/**
 * Re-compile after composition durations are resolved.
 * Injects durations into the HTML and re-parses sub-composition media with proper bounds.
 */
export declare function recompileWithResolutions(compiled: CompiledComposition, resolutions: ResolvedDuration[], projectDir: string, downloadDir: string): Promise<CompiledComposition>;
//# sourceMappingURL=htmlCompiler.d.ts.map