/**
 * Crossfilter: coordinated filtering across any number of views.
 *
 * One shared record set, and per participating view a DIMENSION (row -> key), a
 * REDUCTION (rows -> scalar) and a FILTER (a Set of selected categorical keys,
 * or a numeric `[min, max]` range).
 *
 * The defining rule is that a view's aggregation is computed over the records
 * that pass EVERY OTHER view's filter, never its own, so each view keeps
 * showing "what else is available" rather than collapsing to its own selection.
 * A view's own filter only marks which of its buckets are selected, which the
 * glue layer uses to self-dim. Getting that backwards is the usual bug in a
 * hand-rolled version.
 *
 * Nothing here knows what a view IS. `dimension` is an arbitrary
 * `(row) => key`, so a map region, a tree node's subtree, a sankey link, a grid
 * column or a chart's category are all the same thing to the engine: a rule for
 * which rows a mark stands for. That is why this lives in commons rather than
 * inside a chart library. A tree, a map and a grid can coordinate with no chart
 * present at all.
 *
 * DOM-free apart from the optional {@link Crossfilter.dataTable} helper, which
 * touches only the element handed to it, so the engine stays SSR-safe and
 * testable without a browser.
 */
/** A record in the shared dataset. Shape is entirely the caller's business. */
export type CrossfilterRow = any;
/** Maps a row to this view's key. Return `null`/`undefined` to exclude the row. */
export type DimensionAccessor = (row: CrossfilterRow) => unknown;
/** Reduces the rows in one bucket to the number the view plots. */
export type Reducer = (rows: CrossfilterRow[]) => number;
/** `'count'`, a field aggregate, or your own reducer. */
export type ReduceSpec = 'count' | {
    sum?: string;
    avg?: string;
    min?: string;
    max?: string;
} | Reducer | undefined;
/** Key ordering for a categorical dimension. Default is data order. */
export type DimensionOrder = 'first-seen' | 'asc' | 'desc' | ((a: never, b: never) => number);
/** Bin selection for a range dimension: explicit edges, a width, or a count. */
export interface BinSpec {
    width?: number;
    count?: number;
    thresholds?: number[];
}
export type DimensionType = 'category' | 'range' | 'matrix';
export interface DimensionSpec {
    /** Row to key. For a `matrix` dimension, return `[xKey, yKey]`. */
    dimension: DimensionAccessor;
    reduce?: ReduceSpec;
    /** Inferred as `range` when `bins` is present, otherwise `category`. */
    type?: DimensionType;
    bins?: BinSpec;
    order?: DimensionOrder;
    /** Seed the view's filter at registration time. */
    filter?: FilterInput;
}
/** Categorical keys, a `[min, max]` range, or `null` to clear. */
export type FilterInput = unknown[] | Set<unknown> | [number, number] | null | undefined;
export interface CategoryAggregate {
    type: 'category';
    labels: unknown[];
    values: number[];
    keys: unknown[];
}
export interface RangeAggregate {
    type: 'range';
    /** Bin CENTRES, so a bar plotted here spans its bin. */
    labels: number[];
    values: number[];
    keys: Array<[number, number]>;
    edges: number[];
}
export interface MatrixAggregate {
    type: 'matrix';
    xLabels: unknown[];
    yLabels: unknown[];
    matrix: number[][];
}
export type Aggregate = CategoryAggregate | RangeAggregate | MatrixAggregate;
export interface CrossfilterState {
    /** Active filters by view id: selected keys, or a `[min, max]` tuple. */
    filters: Record<string, unknown[] | [number, number]>;
    filteredCount: number;
    total: number;
}
export type CrossfilterEvent = 'change' | 'records';
export type CrossfilterListener = (state: CrossfilterState) => void;
export interface DataTableColumn {
    field: string;
    label?: string;
    format?: (value: unknown, row: CrossfilterRow) => unknown;
}
export interface DataTableOptions {
    columns?: Array<string | DataTableColumn>;
    page?: number;
    pageSize?: number;
}
export interface DataTableHandle {
    refresh(): void;
    destroy(): void;
}
export interface CrossfilterOptions {
    id: string;
    records?: CrossfilterRow[];
}
export declare class Crossfilter {
    readonly id: string;
    records: CrossfilterRow[];
    private readonly dims;
    private readonly listeners;
    constructor(id: string, records?: CrossfilterRow[]);
    private static store;
    /**
     * Get-or-create a coordinator by id. Passing `records` for an id that already
     * exists swaps that coordinator's dataset instead of making a second one.
     */
    static getOrCreate(opts: CrossfilterOptions): Crossfilter;
    /** An existing coordinator, or null. */
    static get(id: string): Crossfilter | null;
    /**
     * Swap the shared dataset and recompute every domain. Filters survive where
     * they still make sense; categorical keys that vanished are pruned.
     */
    setRecords(records: CrossfilterRow[]): this;
    /** Register (or replace) a view's dimension and reduction. */
    registerDimension(viewId: string, spec: DimensionSpec): this;
    hasDimension(viewId: string): boolean;
    /**
     * Drop a view's dimension. If it held an active filter the siblings were
     * showing a filtered aggregation, so this broadcasts: a destroyed view must
     * not leave its filter stuck on everyone else.
     */
    removeDimension(viewId: string): this;
    private recomputeDomain;
    /** Set (replace) a view's filter. Pass null to clear it. */
    filter(viewId: string, filter: FilterInput): this;
    /** Toggle one categorical key in a view's filter set (multi-select, OR). */
    toggleKey(viewId: string, key: unknown): this;
    private setFilterOn;
    /** Clear one view's filter. */
    clear(viewId: string): this;
    /** Clear every filter. */
    reset(): this;
    private hasFilter;
    private passes;
    /**
     * Records passing every active filter EXCEPT the named view's. Pass null to
     * apply all of them.
     */
    filteredRecords(exceptViewId?: string | null): CrossfilterRow[];
    /** Rows passing ALL active filters: the fully filtered set. */
    filteredRows(): CrossfilterRow[];
    /**
     * One view's aggregation: reduce over the records passing every OTHER view's
     * filter, bucketed by this view's dimension.
     */
    aggregateFor(viewId: string): Aggregate;
    /** Aggregate every registered view. */
    aggregateAll(): Record<string, Aggregate>;
    /** A serialisable snapshot: active filters, filtered and total counts. */
    state(): CrossfilterState;
    /** A copy of one view's current filter, or null. */
    filterOf(viewId: string): Set<unknown> | [number, number] | null;
    /** Subscribe to `'change'` or `'records'`. Returns an unsubscribe function. */
    on(event: CrossfilterEvent, cb: CrossfilterListener): () => void;
    off(event: CrossfilterEvent, cb: CrossfilterListener): this;
    private emit;
    private static esc;
    private resolveColumns;
    private tableHTML;
    /**
     * Render the filtered rows into `el` and keep them in sync on every change.
     * Only the element handed in is touched, so the engine keeps its no-globals
     * promise. A real data grid is the better target; this exists so a filtered
     * row list is one line away when you do not have one.
     */
    dataTable(el: HTMLElement, opts?: DataTableOptions): DataTableHandle;
    /** Drop this coordinator from the registry and release its state. */
    destroy(): void;
}
//# sourceMappingURL=Crossfilter.d.ts.map