/**
 * The size report per route: Forma's code and the route's own, the route's
 * class and its ceilings, the service worker, the preview frame's document,
 * and the build's times; and the gate on it (FORMA-021, FORMA-190; decision
 * brief L-64, DR-34, DR-35). One report: the build prints it, returns it as
 * `BuildResult.sizeReport`, and writes it beside the manifest as
 * `size-report.json`.
 *
 * N2 as restated by L-55 and raised by L-58, L-59 and L-64: Forma's own browser code, `@getforma/core` and its
 * signal library `alien-signals` as the route's bundles carry them, is at most
 * 28,100 B gzip at level 9 (1 KB = 1,000 B) on a route that has any, Brotli
 * reported beside it; the application's own code is outside that ceiling. The
 * 10 KB of v2's appendix C is unreachable under the accepted strict adoption
 * (FORMA-104) and the declared island state block (FORMA-103), so the ceiling
 * is the measured Forma code rounded up to the next 500 B (DR-35's fallback):
 * 25,500 B under L-55, 27,500 B under the founder's L-58, which a client
 * `createList` in an island needs (26,366 B, FORMA-188's `negative/list-island`).
 * L-59 (2026-10-15) raises only N2 to 28,000 B after the combined indexed-row
 * and deployment-identity route measured 27,724 B; its runtime and fixture stay intact.
 * L-64 (2026-10-16) permits 28,100 B for cooperative activation, conditional
 * on the full 20-run campaign meeting the original 50 ms N3 task ceiling.
 * The prior indexed result was 28,071 B; this is a policy change, not a
 * performance pass, and supersedes the historical rounding rule above.
 *
 * The route classes (DR-35; the manifest decides them, see `generateManifest`)
 * and what each may ship, in gzip -9 bytes (1 KiB = 1,024 B):
 *
 * - `no-interactive-region`: a page with nothing to activate. It ships 0 bytes
 *   of JavaScript, a service worker counted: its manifest lists no script, and
 *   forma-server writes the worker's registration only on a page that publishes
 *   a JavaScript entry.
 * - `islands`: a server-rendered page that activates its islands. Forma's code
 *   at most 28,100 B; all of its JavaScript at most 75 KiB, the "other
 *   server-rendered pages" ceiling of N2, or 365 KiB if the route is declared a
 *   canvas (`canvas: true` on the route).
 * - `whole-page-mount`: a route whose page the browser mounts whole (a declared
 *   `ssr: false` entry, or any route of an application built without `ssr`),
 *   one island spanning the page: the same ceilings as islands.
 *
 * What a route loads cold is what the manifest lists for it: its `js` files
 * and their static `modulepreload` dependencies (dynamic `import()` chunks are
 * loaded later, if at all, and are not counted, as the manifest excludes
 * them), and the service worker a route with JavaScript registers. A route with
 * no JavaScript (a static SSR route) is 0 bytes.
 *
 * How the bytes are attributed. A route's file is one minified bundle, so its
 * gzip size is a property of the whole file; the bundler's metafile says how
 * many of its raw bytes each input module contributed (`bytesInOutput`, the
 * minified bytes). Forma's share of the file's raw bytes is applied to the
 * file's gzip and Brotli sizes: `formaGzip = round(gzip × formaRaw / raw)`.
 * That is an estimate, stated as such; a marginal measurement (gzip with and
 * without Forma's bytes) is not additive either, and the two agree to within
 * one percent on the minimal template's Counter route (FORMA-021's analysis).
 *
 * One thing besides those packages' bytes is Forma's: the identity banner
 * (FORMA-186, DR-29) that Forma's build writes at the top of every JavaScript
 * file, the lead's ruling. No input owns it, so it is counted from the file's
 * own first bytes. It is a 16-hex id, which does not compress, so it costs about
 * 30 B gzip for each JavaScript file a route loads cold (measured on the SSR
 * lane's Counter: 29 B for one file, 74 B for the same page split in two).
 *
 * The file holds no clock reading: the application embeds the output directory
 * in its binary, and two builds of the same sources write the same bytes. The
 * build's times are in the printed report and in `BuildResult.sizeReport.times`.
 *
 * Verified by: packages/build/tests/route-size.test.ts > "reports each route's Forma code beside its own, in gzip -9 and Brotli, from the bundler's metafile"
 * Verified by: packages/build/tests/route-size.test.ts > "counts the identity banner at the top of a file as Forma's code, and a comment anywhere else as nobody's"
 * Verified by: packages/build/tests/route-size.test.ts > "fails the build with BUILD-819 when a route's Forma code is over the ceiling, leaving no output"
 */

/** How a route is classified (DR-35). */
type RouteClass = 'no-interactive-region' | 'islands' | 'whole-page-mount';
/** What a class may ship, bytes gzip -9: Forma's code, and all of the route's JavaScript. */
interface RouteCeilings {
    forma: number;
    javascript: number;
}
/** Raw (minified), gzip -9 and Brotli-11 bytes. */
interface ByteSizes {
    raw: number;
    gzip: number;
    brotli: number;
}
/** One file a route loads cold, with Forma's share of it. */
interface RouteSizeFile extends ByteSizes {
    file: string;
    forma: ByteSizes;
}
/** One route's size report. */
interface RouteSize extends ByteSizes {
    route: string;
    /** What the route is: the ceilings follow from it (`ceilings`). */
    class: RouteClass;
    /** The route is declared a canvas (`canvas: true`): 365 KiB of JavaScript instead of 75 KiB. */
    canvas: boolean;
    /** The files the route loads cold: its `js` and `modulepreload` lists. Empty for a route with no JavaScript. */
    files: RouteSizeFile[];
    /** `@getforma/core` and `alien-signals` as bundled: N2's figure is `forma.gzip`. */
    forma: ByteSizes;
    /** Everything else in the route's files: the application's own code and its other dependencies. */
    application: ByteSizes;
    /** The service worker the route registers: `sw.js` for a route with JavaScript, zero for one without. */
    worker: ByteSizes;
    /** All the JavaScript the route ships: `files` and `worker`. The class's JavaScript ceiling is measured on `javascript.gzip`. */
    javascript: ByteSizes;
    ceilings: RouteCeilings;
    /** Which ceilings the route is over: Forma's code, all of its JavaScript, or both. */
    exceeded: Array<'forma' | 'javascript'>;
    /** `exceeded` is not empty: the build fails. */
    over: boolean;
}
/** A file the build writes that is not a route's: the service worker, the preview frame's runtime. */
interface FileSize extends ByteSizes {
    file: string;
}
/** The preview frame's runtime in the output: the served file, and core's before the build minified it. */
interface FrameSize extends FileSize {
    /** Raw bytes of core's `dist/formajs-frame.global.js`. */
    sourceRaw: number;
}
/** Wall-clock milliseconds, per phase of a production build and in all (N7). */
interface BuildTimes {
    totalMs: number;
    phases: {
        /** CSS, fonts, the JavaScript bundles and the SSR modules. */
        bundle: number;
        /** Content hashing. */
        hash: number;
        /** Brotli 11 and gzip 9 of every script and style. */
        compress: number;
        /** The manifest. */
        manifest: number;
    };
}
/** The one report of a production build. */
interface SizeReport {
    version: 1;
    /** The policy: Forma's code per route, then all of a page's JavaScript, then a canvas's (gzip -9 bytes). */
    ceilings: {
        forma: number;
        page: number;
        canvas: number;
    };
    routes: RouteSize[];
    /** The generated `sw.js`. */
    worker?: FileSize;
    /** The preview frame's runtime, inlined into every preview document and never fetched (`previewFrame`); the frame document row. */
    frame?: FrameSize;
    /** In the printed report and the build result; never in the file. */
    times?: BuildTimes;
}

/**
 * @getforma/build — Type definitions
 *
 * Configuration and result types for the Forma build pipeline.
 */

interface BuildEntry {
    /** Entry point file path */
    entry: string;
    /** Output file path (relative to outputDir) */
    outfile: string;
    /** Disable SSR for this entry in an otherwise SSR-enabled application. */
    ssr?: boolean;
}
interface CssEntry {
    /** Glob or array of CSS files to concatenate */
    input: string | string[];
    /** Output file path (relative to outputDir) */
    outfile: string;
    /** If true, run @tailwindcss/cli on the first input file */
    tailwind?: boolean;
}
interface RouteMapping {
    /** Configured entry base names. The first selects the native owner before
     * static SSR removes its browser output; additional entries must be Browser. */
    js: string[];
    /** Base names (without extension) of CSS files for this route */
    css: string[];
    /** Font file names for this route (optional, defaults to all fonts in outputDir) */
    fonts?: string[];
    /**
     * Declare the route a canvas (N2, FORMA-190): a page that hosts many modules
     * and is JavaScript-heavy by design. The size report holds all of its
     * JavaScript to 365 KiB gzip -9 instead of the 75 KiB of other pages; Forma's
     * own code stays under N2's 28,100 B either way. It raises nothing for a
     * route without JavaScript. A heavy page that is not a canvas is trimmed or
     * split, not declared.
     */
    canvas?: boolean;
}
interface BuildConfig {
    /** JavaScript entry points to build */
    entryPoints: BuildEntry[];
    /** Route-to-asset mapping for manifest generation */
    routes: Record<string, RouteMapping>;
    /** CSS entries to generate (tailwind and/or concatenation) */
    cssEntries?: CssEntry[];
    /** Output directory for all built assets */
    outputDir: string;
    /** 3.x migration bridge for 'formajs'; warns once per build() with importing files. Prefer '@getforma/core'. */
    formaAlias?: string;
    /** Bundle one shared ESM graph, preserving import() as separately loaded chunks. */
    codeSplitting?: boolean;
    /** Production worker policy; defaults to on-demand. Only requested, manifest-listed
     * immutable code/style/WASM assets with verified bytes enter its cache. */
    serviceWorker?: 'on-demand';
    /** Application/worker ID: 1–63 lowercase letters, digits or hyphens, starting
     * with a letter or digit. If omitted, a valid nearest Cargo package name is
     * used. Otherwise supply it explicitly; it is always written to manifest2. */
    app?: string;
    /**
     * Copy `@getforma/core`'s preview-frame runtime into the output, for
     * forma-server's preview frame (`PageResponse::frame`, FORMA-125; FORMA-175).
     * Off by default, where the output is what it was.
     *
     * On, the build copies `dist/formajs-frame.global.js` (core 2.1.0 and later;
     * never core's `dist/forma-runtime.js`, the CDN alias of the auto-mounting
     * runtime) from the core the bundles import, byte for byte, as
     * `forma-runtime.<hash>.js`, and names it in the manifest under
     * `aliases["forma-runtime.js"]`, where `PreviewFrame::load` reads it at start
     * and allows it by its SHA-256 hash. Which core: before anything is
     * written, each entry is bundled in memory with the bundles' own esbuild
     * options (so `formaAlias`, resolved from the working directory, and
     * tsconfig `paths` lead where they lead the bundles), and every file it
     * loads is mapped to its package; when the bundles import none, the core
     * installed where the build runs. Watch mode writes it unhashed as
     * `forma-runtime.js`, named the same way in the dev manifest. It is
     * inlined, never fetched: no compressed copies, and no place in the
     * worker's lists. A core without it fails the build with BUILD-805, a
     * runtime forma-server would refuse to inline (empty, not UTF-8,
     * `</script`, `<!--`, a carriage return or a NUL) with BUILD-806, and
     * bundles that import cores whose runtimes differ with BUILD-807, before
     * anything is written.
     */
    previewFrame?: boolean;
    /** Directory containing .woff2 font files to copy */
    fontDir?: string;
    /** Enable SSR IR emission */
    ssr?: boolean;
    /** SSR entry points for IR emission (page name -> entry path) */
    ssrEntryPoints?: Record<string, string>;
    /** Enable watch mode */
    watch?: boolean;
    /** Budget threshold in bytes (Brotli-compressed), default 200_000 */
    budgetThreshold?: number;
    /** Files that should keep unhashed copies (e.g. for server-inlined assets).
     * A zero-JS SSR owner cannot request executable server inlining. */
    serverInlined?: string[];
    /** Legacy compatibility option. SSR publication always requires the 3.0
     * server-renderable subset. False and FORMA_STRICT=0 cannot downgrade errors.
     * Every page is checked; watch retains its previous good product on failure. */
    strict?: boolean;
    /**
     * Also write every SSR diagnostic as JSON to this path (relative to the
     * working directory), whether or not the build fails: the machine-readable
     * report an agent can act on (N12). It must be outside `outputDir`, which
     * the build replaces whole and serves.
     */
    diagnosticsJson?: string;
    /**
     * The previous release's `dist/`, which the application's pipeline keeps as a
     * release artifact (FORMA-186, DR-29, L-25). The build copies that release's
     * own hashed assets into the new `dist/`, listed in the manifest `retained`
     * and `immutable`, so a tab opened before a deploy still loads its chunks
     * from the one `dist/` the application embeds. One generation: what the
     * previous release itself retained is not copied again. A directory other
     * than `outputDir`, which the build replaces whole. Leave it out for a first
     * deploy, or when no release is live. The previous dist is trusted like the source: the check below catches a damaged or
     * altered file, not someone who can also rewrite its manifest. Every copied file is checked against
     * the SHA-384 the previous manifest recorded (`BUILD-821`); a path that is
     * not a usable previous release fails the build (`BUILD-820`). Ignored in
     * watch mode.
     */
    previousDist?: string;
    /**
     * Mark this release as one that fixes a vulnerability (L-16, SR45): nothing is
     * retained from `previousDist`, so no earlier build's asset is served and a
     * revoked build's tabs meet 404s for their lazy chunks. The application's
     * deploy pipeline must mark every such release, a Forma security upgrade
     * included. `FORMA_SECURITY_RELEASE=1` (or `true`, any case) in the environment marks it too; any
     * value other than 1, true, 0, false or empty fails the build (`BUILD-822`); and a
     * `false` here does not unmark it.
     */
    security?: boolean;
}
interface RouteManifest {
    mode: 'ssr' | 'browser';
    js: string[];
    /** Static ESM dependencies only; dynamic import() chunks are intentionally excluded. */
    modulepreload?: string[];
    css: string[];
    fonts: string[];
    total_size_br: number;
    budget_warn_threshold: number;
    ir?: string;
}
interface AssetMetadata {
    /** The build guarantees this exact filename is immutable, independent of its spelling. */
    immutable: boolean;
    /** Full SHA-384 of the final uncompressed file, used by SRI and the worker. */
    integrity: string;
    /** A file of the previous release kept for tabs still open on it (FORMA-186, L-25). */
    retained?: boolean;
}
interface AssetManifest {
    version: 2;
    writer: string;
    app: string;
    /**
     * The deployment identity (FORMA-186, DR-29): 16 lowercase hexadecimal
     * characters computed from the build's own outputs and the writers'
     * versions before anything is named, compiled into every JavaScript bundle
     * (`__FORMA_BUILD__`, which core compares with the page's
     * `data-forma-build`), and the same for identical inputs however often they
     * are built, from any directory when built from the project root (esbuild
     * hashes input paths relative to its working directory). `dev` in watch mode.
     */
    build: string;
    aliases: Record<string, string>;
    assets: Record<string, AssetMetadata>;
    routes: Record<string, RouteManifest>;
}
/** One link of a composition chain, from the page down (BUILD-403). */
interface BuildDiagnosticChainLink {
    component: string;
    file: string;
    line: number;
    column: number;
}
/**
 * One coded SSR diagnostic (FORMA-119). The catalogue, one entry per code
 * with its fix, is `packages/compiler/docs/subset.md`.
 */
interface BuildDiagnostic {
    /** The stable code, e.g. `BUILD-101`. */
    code: string;
    /** Unsupported SSR constructs are always `error`; `note` never fails a build. */
    severity: 'error' | 'warning' | 'note';
    /** The page (entry outfile without `.js`) whose module reported it. */
    page: string;
    /** The file to edit; empty for a build-level diagnostic. */
    file: string;
    /** 1-based line and column in `file`, as authored; 0 when unknown. */
    line: number;
    column: number;
    /** The specific report: the construct, what happens, and its fix. */
    message: string;
    /** The catalogue's why and fix for the code. */
    why: string;
    fix: string;
    /** BUILD-403: the composition chain from the page down. */
    chain?: BuildDiagnosticChainLink[];
}
interface BuildResult {
    manifest: AssetManifest;
    buildHash: string;
    warnings: string[];
    /** Every coded SSR diagnostic the build reported, page by page. */
    diagnostics: BuildDiagnostic[];
    /**
     * The size report's routes (FORMA-021, FORMA-190): per route, its class, what
     * it loads cold in raw, gzip -9 and Brotli bytes, Forma's code
     * (`@getforma/core` and `alien-signals` as bundled) beside the application's,
     * the service worker it registers, and its ceilings. A production build fails
     * with `BUILD-819` before writing its output when a route is over one:
     * Forma's code over N2's `FORMA_CODE_CEILING_GZIP` (28,100 B; decision brief
     * L-64), all of its JavaScript over its class's (none for a route without an
     * interactive region, 75 KiB, 365 KiB for a canvas). Empty in watch mode,
     * whose unminified bytes are not what a route ships. The same objects as
     * `sizeReport.routes`.
     */
    sizes: RouteSize[];
    /**
     * The one report (FORMA-190): `sizes` with the service worker, the preview
     * frame's runtime when `previewFrame` is on (the frame document row), and the
     * build's times by phase. A production build prints it and writes it, without
     * the times (so the output stays the same bytes for the same sources), as
     * `size-report.json` beside `manifest.json`. No routes in watch mode.
     */
    sizeReport: SizeReport;
}

/**
 * @getforma/build — Build Pipeline
 *
 * Parameterized build pipeline extracted from the GateWASM admin build.ts.
 * Handles: esbuild bundling, CSS generation, font copying, SSR IR emission,
 * island registry generation, the preview frame's runtime
 * (previewFrame), content hashing, compression, manifest generation, service
 * worker generation, and budget warnings.
 */

/**
 * Run the Forma build pipeline.
 *
 * Orchestrates: clean, CSS generation, font copying, esbuild bundling
 * (with optional SSR IR emission), island registry generation,
 * content hashing, compression, manifest generation, service worker
 * generation, and budget warnings.
 *
 * A production build writes into a staging directory beside `outputDir` and
 * swaps it in only when every step has succeeded, so a build that fails part
 * way leaves the previous output exactly as it was, and nothing beside it
 * (L-21). The application embeds `outputDir` in its binary, and used to embed
 * half a build after a failure. Watch mode writes in place, as before.
 * Verified by: packages/build/tests/build.test.ts > "leaves the previous build byte-identical when a build fails after writing files"
 * Verified by: packages/build/tests/build.test.ts > "a failed first build leaves no output directory and nothing beside it"
 */
declare function build(userConfig: BuildConfig): Promise<BuildResult>;

export { type AssetManifest, type AssetMetadata, type BuildConfig, type BuildDiagnostic, type BuildDiagnosticChainLink, type BuildEntry, type BuildResult, type BuildTimes, type ByteSizes, type CssEntry, type FileSize, type FrameSize, type RouteCeilings, type RouteClass, type RouteManifest, type RouteMapping, type RouteSize, type RouteSizeFile, type SizeReport, build };
