/**
 * Fact-loader descriptor contracts (D0091).
 *
 * The volatile parts of the BigQuery analytics warehouse — which columns
 * exist, which tables exist, and which artifact feeds which table — become
 * declarative `FactTableDescriptor` records. The stable part (read manifest,
 * extract, stage, MERGE) is the generic engine in `./engine.ts`, which knows
 * nothing about specific tables or columns.
 *
 * This module owns only the declarative side: the descriptor shape, the row
 * and column value types, and the pure-extractor context. The two I/O seams
 * (`RunArtifactReader`, `BigQueryFactStore`) are ports under `../ports/`; the
 * orchestration lives in `./engine.ts`. Concrete descriptors and adapters are
 * separate slices (W-fact-table-registry-v1, W-bigquery-fact-store-adapter).
 *
 * Modeled on the artifact registry (`../artifact-registry.ts`): one entry per
 * table, iterated generically, so adding a table or a column is a one-file
 * change.
 *
 * @see docs/decisions/D0091-bigquery-analytics-fact-loader.md
 * @see docs/design-docs/bigquery-analytics-fact-loader.md
 */
import type { z } from "zod";
import type { ArtifactType } from "../artifact-registry.js";
import type { AssociationValues, RunId } from "../types/branded-ids.js";
import type { ISOTimestamp, ReportId } from "../types/index.js";
/**
 * The subset of BigQuery scalar/struct field types the fact loader emits.
 * Deliberately narrow — extend the union (and the adapter's DDL mapping)
 * when a descriptor needs a new type, rather than accepting arbitrary
 * strings. Keeping it closed makes the store adapter's `CREATE TABLE` /
 * `ADD COLUMN` reconciliation total over the type space.
 */
export type BigQueryFieldType = "STRING" | "INT64" | "FLOAT64" | "NUMERIC" | "BOOL" | "TIMESTAMP" | "DATE" | "DATETIME" | "JSON" | "BYTES";
/** BigQuery column cardinality. Defaults to `NULLABLE` when unset. */
export type BigQueryFieldMode = "NULLABLE" | "REQUIRED" | "REPEATED";
/**
 * One column in a fact table's schema. A descriptor's `schema` is the single
 * source of truth for the DDL: the store creates the table from it and
 * reconciles additively (`ADD COLUMN`) on change, so there is no hand-written
 * `CREATE TABLE` that can drift from the extractor.
 */
export interface BigQueryColumn {
    /** Column name (snake_case, matches the row key the extractor emits). */
    readonly name: string;
    /** BigQuery field type. */
    readonly type: BigQueryFieldType;
    /** Cardinality. Absent means `NULLABLE`. */
    readonly mode?: BigQueryFieldMode;
    /** Optional human-readable column description carried into the DDL. */
    readonly description?: string;
}
/**
 * A single parsed artifact payload with the metadata an extractor needs to
 * emit a fact row.
 *
 * `payload` is JSON-deserialized (structural parse) but not yet validated
 * against a domain shape — the extractor projects it into typed rows and the
 * descriptor's `rowSchema` validates that output. For a bulk artifact this is
 * the run's single object; for a per-entry artifact it is one entry.
 *
 * @typeParam TPayload - shape the extractor expects for this artifact type.
 */
export interface ParsedArtifactEntry<TPayload = unknown> {
    /** Per-entry key (e.g. `mode--task--model`); the bulk slug for bulk artifacts. */
    readonly key: string;
    /** Association axis values that identify this entry (`{}` for pure bulk). */
    readonly association: AssociationValues;
    /** JSON-deserialized artifact body. */
    readonly payload: TPayload;
    /** Fully-qualified storage URI, for the `gs_uri` drill-through column. */
    readonly sourceUri: string;
}
/**
 * Read-only accessor over the artifacts fetched for one run, scoped to the
 * descriptor's declared `sourceArtifacts`. An artifact type the run's manifest
 * did not carry resolves to an empty list — so a partial run (e.g. no traces)
 * yields zero rows for the affected tables without the extractor branching.
 */
export interface RunArtifactAccess {
    /** Whether any parsed entries are present for `type` in this run. */
    readonly has: (type: ArtifactType) => boolean;
    /**
     * All parsed entries for `type` (empty when absent). Bulk artifacts return
     * 0 or 1 entries; per-entry artifacts return 0..N. `TPayload` is the caller's
     * asserted payload shape — the reader deserializes to `unknown`, and the
     * descriptor owns the shape of the artifact it declared as a source.
     */
    readonly entries: <TPayload = unknown>(type: ArtifactType) => readonly ParsedArtifactEntry<TPayload>[];
}
/**
 * Everything a pure `extract` receives for one run: run identity (the join
 * keys and partition value every fact table carries) plus the parsed
 * artifacts the descriptor declared. No I/O, no store, no other tables — the
 * extractor is trivially unit-testable with fixtures.
 */
export interface RunExtractionContext {
    /** Run identity — the `run_id` join key. */
    readonly runId: RunId;
    /**
     * The report this run published, when it published one — the `report_id`
     * join key to the existing `reports` / `area_scores` views. Undefined for
     * runs with no report.
     */
    readonly reportId: ReportId | undefined;
    /** Run completion time — the DAY-partition `completed_at` value. */
    readonly completedAt: ISOTimestamp;
    /** Parsed artifacts, restricted to this descriptor's `sourceArtifacts`. */
    readonly artifacts: RunArtifactAccess;
}
/**
 * A BigQuery fact-table row. Rows are flat records keyed by column name; the
 * descriptor's `rowSchema` narrows this to the concrete shape and the engine
 * validates every emitted row against it before load.
 */
export type FactRow = Record<string, unknown>;
/**
 * One declarative fact table. Owns its BigQuery schema, the source artifacts
 * it reads, a pure extractor from parsed artifacts to typed rows, and its
 * MERGE key. The generic engine iterates a registry of these and never names
 * a table or column itself.
 *
 * @typeParam TRow - the row shape this table emits.
 */
export interface FactTableDescriptor<TRow extends FactRow = FactRow> {
    /** Target table name, e.g. `test_case_facts`. */
    readonly table: string;
    /** Target dataset, e.g. `ailf_facts`. */
    readonly dataset: string;
    /** Artifact types the extractor reads. A run must carry all of them to run this table. */
    readonly sourceArtifacts: readonly ArtifactType[];
    /** Column definitions — the single source of truth for the table DDL. */
    readonly schema: readonly BigQueryColumn[];
    /** Validates extractor output before load (the D0045 trust boundary). */
    readonly rowSchema: z.ZodType<TRow>;
    /** DAY-partition column, e.g. `completed_at`. Must name a real column. */
    readonly partitionField: Extract<keyof TRow, string>;
    /** Clustering columns (may be empty). */
    readonly clusterFields: readonly Extract<keyof TRow, string>[];
    /** Natural key for the idempotent MERGE. */
    readonly mergeKey: readonly Extract<keyof TRow, string>[];
    /** Pure projection: parsed artifacts for one run -> typed rows. */
    readonly extract: (ctx: RunExtractionContext) => TRow[];
}
/**
 * A registry of fact-table descriptors. Heterogeneous by design (each entry
 * has its own row type), so it is typed at the widened `FactRow` bound; each
 * descriptor's precise `TRow` is enforced at its definition site.
 */
export type FactTableRegistry = readonly FactTableDescriptor[];
/**
 * The store-facing projection of a descriptor: just what `ensureTable` needs
 * to create and reconcile the physical table. Excludes the runtime concerns
 * (`extract`, `rowSchema`) so the store never depends on the extractor.
 */
export interface FactTableSpec {
    readonly table: string;
    readonly dataset: string;
    readonly schema: readonly BigQueryColumn[];
    readonly partitionField: string;
    readonly clusterFields: readonly string[];
}
/**
 * Author a fact-table descriptor with full row-type checking, then widen it
 * for storage in a heterogeneous {@link FactTableRegistry}.
 *
 * The argument is checked against `FactTableDescriptor<TRow>` (so `mergeKey`,
 * `clusterFields`, and `partitionField` must name real columns and `extract`
 * must return `TRow[]`), while the return type is the widened
 * `FactTableDescriptor` the registry stores. The widening is needed because
 * `rowSchema: z.ZodType<TRow>` is invariant in `TRow`, so a precisely-typed
 * descriptor is not directly assignable to the widened element type. This is
 * the same authoring pattern as `buildDescriptor` / `defineTask`.
 */
export declare function defineFactTable<TRow extends FactRow>(descriptor: FactTableDescriptor<TRow>): FactTableDescriptor;
/** Derive the store-facing table spec from a descriptor. */
export declare function toFactTableSpec(descriptor: FactTableDescriptor): FactTableSpec;
