import { BuildResult, Plugin } from 'esbuild';

/** One successfully compiled native/browser entry, before caller publication. */
interface SsrEntryProduct {
    readonly page: string;
    readonly mode: 'ssr';
    readonly sourceEntry: string;
    readonly browserEntry: string;
    readonly irFile: string;
    readonly irHash: string;
    readonly browserRequired: boolean;
    readonly islands: readonly Readonly<{
        name: string;
        mode: 'ssr' | 'browserOnly';
    }>[];
}
/**
 * Read this exact successful esbuild result's compiler products. Call after
 * `await build()`/`rebuild()`, or from the caller's final guarded onEnd publisher.
 * An earlier onEnd callback is not a successful publication boundary: later
 * plugins may still refuse the build. No callback or mutable onStart state is
 * exposed. The caller commits these records with its output graph/manifest.
 *
 * Browser-only builds have no native products. Callers must independently match
 * every configured SSR entry; absence must never imply static or Browser mode.
 */
declare function getSsrEntryProducts(result: BuildResult): readonly SsrEntryProduct[];

type SchemaType = {
    kind: 'boolean' | 'string' | 'number' | 'date';
} | {
    kind: 'enum';
    variants: readonly string[];
} | {
    kind: 'array';
    element: SchemaType;
} | {
    kind: 'optional';
    inner: SchemaType;
} | {
    kind: 'object';
    object: number;
};
interface SchemaField {
    name: string;
    file: string;
    line: number;
    column: number;
    type: SchemaType;
}
type PropsSchema = readonly (readonly SchemaField[])[];
interface KeyPath {
    listOffset: number;
    segments: readonly string[];
}
interface SourcePosition {
    opcodeOffset: number;
    file: string;
    line: number;
    column: number;
    component: string;
}

interface CanonicalField {
    readonly name: string;
    readonly type: SchemaType;
}
type CanonicalSchema = readonly (readonly CanonicalField[])[];
type UnaryBindingOperator = '!' | '+' | '-';
type BinaryBindingOperator = '+' | '-' | '*' | '/' | '%' | '**' | '===' | '!==' | '<' | '<=' | '>' | '>=' | '&&' | '||';
type BindingValue = {
    kind: 'constant';
    value: unknown;
} | {
    kind: 'prop';
    path: readonly string[];
} | {
    kind: 'signal' | 'row' | 'capture';
    id: number;
    path: readonly string[];
} | {
    kind: 'nullish';
    source: BindingValue;
    fallback: unknown;
} | {
    kind: 'unary';
    operator: UnaryBindingOperator;
    value: BindingValue;
} | {
    kind: 'binary';
    operator: BinaryBindingOperator;
    left: BindingValue;
    right: BindingValue;
};
type BindingRecord = {
    kind: 'signal' | 'text';
    value: BindingValue;
} | {
    kind: 'attribute';
    name: string;
    value: BindingValue;
} | {
    kind: 'show';
    value: BindingValue;
    then: readonly BindingRecord[];
    else: readonly BindingRecord[];
} | {
    kind: 'list';
    value: BindingValue;
    key: readonly string[];
    body: readonly BindingRecord[];
} | {
    kind: 'child';
    name: string;
    fields: readonly {
        name: string;
        value: BindingValue;
    }[];
} | {
    kind: 'capture';
    value: unknown;
};
type IslandRenderMode = 'ssr' | 'browserOnly';

/**
 * Forma Compiler - FMIR Binary Buffer
 *
 * IrEmitContext accumulates FMIR v3 bytecode, strings, slots, islands,
 * props schema and optional key paths/source positions — and serializes them into the binary format the
 * Rust-side walker reads to produce HTML.
 *
 * It is a buffer, not a compiler: the AST → opcode translation lives entirely
 * in ir-walk.ts (walkHTree / walkCallExpression), which is what the SSR plugin
 * drives. Only Static and Dynamic subtrees reach the bytecode — Island
 * subtrees stay as client-side JS behind ISLAND_START/ISLAND_END markers.
 */

declare class IrEmitContext {
    /** String interning map: string -> index */
    private stringMap;
    /** Interned strings in order */
    private strings;
    /** Look up an already-emitted name without interning new bytes. */
    stringIndex(value: string): number | undefined;
    /** Slot tracking */
    private slots;
    private nextSlotId;
    /** Opcode buffer */
    private opcodes;
    /** Island tracking */
    private islands;
    private propsSchema;
    private readonly islandBindings;
    private readonly constants;
    private readonly closedProjections;
    private keyPaths;
    private sourcePositions;
    /** Declared page object zero and its nested object types. */
    setPropsSchema(schema: PropsSchema): void;
    getPropsSchema(): PropsSchema;
    /** Append a declared component schema without changing the page's root object. */
    appendPropsSchema(schema: PropsSchema): number;
    /** Metadata belongs to one declared component, while input slots belong to its call. */
    setIslandContract(islandId: number, mode: IslandRenderMode, bindings: readonly BindingRecord[]): void;
    private contracts;
    /** FORMA-181 may attach a path without changing LIST operands. */
    addKeyPath(listOffset: number, segments: readonly string[]): void;
    /** Optional diagnostics, using 1-based UTF-16 columns. */
    setSourcePositions(positions: readonly SourcePosition[]): void;
    setIslandSchemaObject(islandId: number, schemaObject: number): void;
    /** Lossless canonical diagnostic dump, shared with forma-ir. */
    dump(): string;
    /** Stack of slot-id capture sets for island subtree walks.
     *  Every set on the stack records each referenced slot, so an outer
     *  island's span also captures slots of islands nested inside it. */
    private slotCaptureStack;
    /** DYN_TEXT marker counter */
    private nextMarkerId;
    /** Island ID counter */
    private nextIslandCounter;
    /** Intern a string, return its index. Deduplicates. */
    addString(s: string): number;
    /**
     * Register a new slot, return its id.
     *
     * `reference` says whether creating the slot also counts as USING it inside
     * the island subtree currently being captured. It is true for every slot
     * minted because an opcode needs one, and false for a slot merely DECLARED —
     * a signal the walk found in scope but that no binding on the page reads.
     * Capture bookkeeping supports the bytecode walk only. Island input lists
     * are independently constructed from ordered declared fields; local signals
     * and captured defaults never enter the state envelope as handler props.
     * Verified by: packages/compiler/tests/island-registry.test.ts > "local signal defaults and list scratch values stay outside declared inputs"
     */
    addSlot(name: string, typeHint: number, source?: number, defaultBytes?: Uint8Array, reference?: boolean): number;
    /** A private projection/list scratch slot. Its label has no binding semantics. */
    addTemporarySlot(typeHint: number): number;
    /** Explicit closed constants distinguish empty text from an absent legacy default. */
    addConstantSlot(value: unknown, typeHint: number): number;
    /** Closed provenance is distinct from a schema hint, including literal null. */
    constantSlot(id: number): {
        value: unknown;
    } | undefined;
    /** Compiler-known PROP result; still independently validated by native flow. */
    recordClosedProjection(source: number, key: string, target: number): void;
    /** Begin capturing slot ids referenced while walking an island subtree. */
    beginSlotCapture(): void;
    /** End the innermost capture; returns the captured slot ids sorted ascending. */
    endSlotCapture(): number[];
    /** Record a slot-id reference into every active capture set.
     *  Called by addSlot for slots minted because an opcode needs them; call it
     *  directly when an opcode reuses a pre-existing slot (an in-scope signal, a
     *  list item binding) without addSlot. */
    recordSlotRef(id: number): void;
    /** Ordered declared inputs may repeat a slot, including a current LIST row.
     * The native walker snapshots them at ISLAND_START before the row advances. */
    setIslandSlotIds(islandId: number, slotIds: number[]): void;
    /** Get a fresh marker id for DYN_TEXT. */
    nextMarker(): number;
    /** Peek at the next island id without incrementing the counter. */
    peekNextIslandId(): number;
    /** Register a new island entry. */
    addIsland(name: string, trigger: number, slotIds: number[], byteOffset: number, schemaObject?: number): number;
    /** Emit a raw byte. */
    emit(byte: number): void;
    /** Emit u16 little-endian. */
    emitU16(val: number): void;
    /** Emit u32 little-endian. */
    emitU32(val: number): void;
    /** Get current opcode buffer length (for offset calculations). */
    opcodeLen(): number;
    /** Patch a u32 at a previous position (for back-patching body lengths). */
    patchU32(pos: number, val: number): void;
    /** Build the complete canonical FMIR v3 binary. */
    toBinary(): Uint8Array;
    private validateConstants;
    /** Encode the string table section. */
    private encodeStringTable;
    /** Encode the slot table section (v2 format). */
    private encodeSlotTable;
    /** Get registered island entries (for build.ts to generate client registry). */
    getIslands(): Array<{
        id: number;
        name: string;
        trigger: number;
        slotIds: number[];
        contract?: {
            mode: IslandRenderMode;
            digest: string;
            schema: CanonicalSchema;
        };
    }>;
    /** Declared ordered inputs and the paired browser/native binding identity. */
    private encodeIslandTable;
}

/**
 * Forma Compiler - Coded build diagnostics (FORMA-119; DR-1, L-3, N12)
 *
 * Every construct the compiler cannot represent in a server-rendered module is
 * reported here, with a stable code, the file, the line and the column, one
 * line of why and one of fix. The catalogue below is the single list of codes;
 * `packages/compiler/docs/subset.md` documents each one, and a test keeps the two equal.
 *
 * Codes are numbered by family (L-3). BUILD-303, BUILD-408, BUILD-504, BUILD-706 and BUILD-708
 * are retired and reserved after the declared-state migration. Current
 * nonfatal build notes are explicitly marked with strict=false.
 *
 * Codes are numbered by family (L-3):
 *
 *   BUILD-1xx  an unsupported native subtree
 *   BUILD-2xx  an attribute or prop dropped or left empty
 *   BUILD-3xx  an unresolved text, conditional or list expression
 *   BUILD-4xx  a component the compiler does not inline
 *   BUILD-5xx  a signal the compiler cannot name
 *   BUILD-6xx  the page module and its closed native contract
 *   BUILD-7xx  a construct adoption does not handle; `browserOnly()` is the way out
 *   BUILD-8xx  the build package (reported by @getforma/build, catalogued here)
 *   BUILD-9xx  the compiler's own deprecated entry points (logged when called)
 *
 * A code never changes meaning. A new construct gets a new code; a retired one
 * stays reserved.
 *
 * What a report does, in order:
 *
 * 1. It prints a source-located reason diagnostic with its stable code. Native
 *    expressions that cannot form a declared contract also throw BUILD-610,
 *    independent of strict mode; historical warning-only behavior is archived.
 * 2. It hands the diagnostic, with its location, to the collector the caller
 *    opened with {@link collectDiagnostics}, if any. The SSR plugin opens one
 *    per page; `@getforma/build` gathers them all, writes them as JSON on
 *    request, and always fails publication when any error remains.
 *
 * Verified by: packages/compiler/tests/diagnostics-conformance.test.ts
 */
interface DiagnosticEntry {
    /** The stable code, `BUILD-<family><nn>`. */
    code: string;
    /** What the code names, in a few words. */
    title: string;
    /** One line: what happens to the page. */
    why: string;
    /** One line: what to write instead. */
    fix: string;
    /**
     * Whether this is a publication error. False only for explicitly nonfatal build notes.
     */
    strict: boolean;
}
/**
 * Every code the compiler and the build can report, by family. The order is
 * the documentation's order.
 */
declare const DIAGNOSTIC_CATALOGUE: readonly DiagnosticEntry[];
/** The catalogue entry for a code. Throws on a code the catalogue lacks. */
declare function diagnosticEntry(code: string): DiagnosticEntry;
/** One link of a composition chain, from the page down (BUILD-403). */
interface DiagnosticChainLink {
    /** The component's name where it is called (the page root first). */
    component: string;
    /** The file the call is in (for the root: the page component's file). */
    file: string;
    line: number;
    column: number;
}
/** One report, as a build gathers it. */
interface Diagnostic {
    /** The stable code, e.g. `BUILD-101`. */
    code: string;
    /** The file to edit, as the compiler read it (absolute in a real build). */
    file: string;
    /** 1-based line in that file, as authored (JSX mapped back); 0 when unknown. */
    line: number;
    /** 1-based column; 0 when unknown. */
    column: number;
    /** The specific report: the construct, what happens, and its fix. */
    message: string;
    /** The catalogue's why for the code. */
    why: string;
    /** The catalogue's fix for the code. */
    fix: string;
    /** Whether `--strict` fails the build on it (false only for notes). */
    strict: boolean;
    /** BUILD-403: the composition chain, from the page down to the component refused. */
    chain?: DiagnosticChainLink[];
}
/**
 * Run `fn` and return what it returns together with every diagnostic reported
 * while it ran. Synchronous only: the compiler's walk is, and a module-level
 * stack is what lets the signal pass, the analyzer and the walker report into
 * one list without threading a sink through every signature. A report goes to
 * every open collection, so a nested one sees its own part and the outer one
 * sees everything.
 *
 * Reports still print their line as at compiler 0.3.6; collecting adds to it.
 */
declare function collectDiagnostics<T>(fn: () => T): {
    result: T;
    diagnostics: Diagnostic[];
};
/**
 * Make a diagnostic from a code and an already-located position, without
 * printing it: for a caller that prints its own line (`@getforma/build`).
 */
declare function makeDiagnostic(code: string, file: string, line: number, column: number, message: string): Diagnostic;
/**
 * One diagnostic as a block of text for a failing `--strict` build:
 * `file:line:column  CODE  message`, then its why and fix.
 */
declare function formatDiagnostic(d: Diagnostic): string;

/**
 * Forma Compiler - esbuild SSR Plugin
 *
 * Captures one source graph for native IR and browser registration metadata,
 * returning both outputs without publishing partial builds.
 */

interface SsrPluginOptions {
    /** Page name (e.g., 'platform-login') -- used for the output .ir filename */
    page: string;
    /** Output directory for .ir files */
    outDir: string;
    /** Entry point path (e.g., 'src/platform/onboarding/app.ts') for real IR emission */
    entryPoint?: string;
    /** Browser entry whose literal registry receives the paired metadata. */
    clientEntryPoint?: string;
    /** Private entry aliases supplied by the shared build graph for this same
     * physical client entry in explicit Browser mode. Dependencies remain shared. */
    browserEntryPoints?: readonly {
        path: string;
        entryPoint: string;
    }[];
    /** Legacy compatibility option. All unsupported native constructs fail in 3.0;
     * false cannot restore warning-only publication. */
    strict?: boolean;
    /** Receives the page's diagnostics after each emission (`@getforma/build`
     *  gathers them for its result, its JSON report and aggregate failure). */
    onDiagnostics?: (diagnostics: Diagnostic[], page: string) => void;
}
interface IrResult {
    binary: Uint8Array;
    islands: ReturnType<IrEmitContext['getIslands']>;
}
/**
 * Generate real IR by parsing the entry point, resolving the component,
 * extracting its h() tree, and walking it to produce FMIR binary.
 *
 * Returns the FMIR binary and island info, or null if any step fails (publication is refused).
 */
declare function generateRealIr(entryPointPath: string): IrResult | null;
/**
 * Compile a paired browser/native product. Requires esbuild write:false: the
 * caller publishes outputFiles only after the complete build succeeds.
 */
declare function formaSsrPlugin(options: SsrPluginOptions): Plugin;

type WriterVersion = readonly [number, number, number];
declare function baseWriterVersion(version: string): WriterVersion;
declare const COMPILER_WRITER_VERSION: WriterVersion;
/** CRC-32/ISO-HDLC, streaming chunks without inserting boundary or skipped bytes. */
declare function crc32(...chunks: readonly Uint8Array[]): number;
interface FmirSection {
    id: number;
    flags: number;
    bytes: Uint8Array;
}
interface FmirDescriptor extends FmirSection {
    offset: number;
    length: number;
}
interface FmirContainer {
    writer: WriterVersion;
    bodyLength: number;
    checksum: number;
    sections: FmirDescriptor[];
}
declare class FmirContainerError extends Error {
    readonly code: string;
    constructor(code: string, message: string);
}
/** Pack sections in canonical ascending order; flags and unknown payloads are preserved. */
declare function encodeFmirSections(sections: readonly FmirSection[], writer?: WriterVersion): Uint8Array;
/** Header/table reader used by diagnostics and compiler conformance tests. */
declare function readFmirContainer(bytes: Uint8Array, reader?: WriterVersion): FmirContainer;
/** Stable lossless representation shared with forma-ir::dump_ir. */
declare function dumpFmir(bytes: Uint8Array): string;

export { COMPILER_WRITER_VERSION, DIAGNOSTIC_CATALOGUE, type Diagnostic, type DiagnosticChainLink, type DiagnosticEntry, FmirContainerError, type FmirSection, IrEmitContext, type IrResult, type KeyPath, type PropsSchema, type SchemaField, type SchemaType, type SourcePosition, type SsrEntryProduct, type SsrPluginOptions, type WriterVersion, baseWriterVersion, collectDiagnostics, crc32, diagnosticEntry, dumpFmir, encodeFmirSections, formaSsrPlugin, formatDiagnostic, generateRealIr, getSsrEntryProducts, makeDiagnostic, readFmirContainer };
