import type { ChartAxis } from '../chartAxis';
import type { DataModel } from '../data/dataModel';
import type { ProcessedData, ScopeProvider } from '../data/dataModelTypes';
import { type AggregationFilterBase, type AggregationManager } from './aggregationManager';
import type { BucketLookupFeature, DatumRangeReader } from './seriesTypes';
export type { BucketLookupFeature } from './seriesTypes';
interface ExtremesFilter extends AggregationFilterBase {
    indexData: Uint32Array;
}
interface SplitFilter extends AggregationFilterBase {
    positiveIndexData: Uint32Array;
    negativeIndexData: Uint32Array;
}
interface BucketLookupManagerOpts<TFilter extends AggregationFilterBase> {
    series: ScopeProvider;
    /** Resolved at lookup time — accessor pattern lets the series mutate axis/data references freely. */
    getXAxis: () => ChartAxis | undefined;
    getDataModel: () => DataModel<any, any, any> | undefined;
    getProcessedData: () => ProcessedData<any> | undefined;
    aggregationManager: AggregationManager<TFilter>;
    /** `'value'` for series whose xValue column is the X coordinate (line); `'key'` for keyed series (bar/area/ohlc/range-*). */
    domainKey: 'value' | 'key';
    getSelection: () => Uint8Array | undefined;
    /**
     * AGGREGATION_INDEX_* slots whose stored datum index is treated as the
     * canonical "selected" representative for its bucket. When set, the
     * selection predicate additionally requires `datumIndex` to match one of
     * the configured slots — preventing the visual multiplication where a
     * single selected datum surfaces as up to four styled extrema markers per
     * bucket. When undefined, falls back to any-membership semantics
     * (composite-node series like OHLC/range-bar that already render one node
     * per bucket).
     */
    canonicalExtremaSlots?: readonly number[];
}
interface BucketingInputs {
    xValues: any[];
    d0: number;
    d1: number;
    xNeedsValueOf: boolean;
}
/**
 * Per-render-frame reader cache shared by both extremes and split managers.
 * Holds both the bucket-selected hot-path reader and the range reader keyed
 * on (`processedData`, filter) — both are invalidated together because both
 * close over the same resolved bucketing context.
 */
declare class LookupCache<TFilter> {
    processedData?: ProcessedData<any>;
    filter?: TFilter;
    selectedReader?: (datumIndex: number) => boolean;
    rangeReader?: DatumRangeReader;
    has(processedData: ProcessedData<any>, filter: TFilter): boolean;
    set(processedData: ProcessedData<any>, filter: TFilter, selectedReader: (datumIndex: number) => boolean, rangeReader: DatumRangeReader): void;
    clear(): void;
}
/**
 * Per-filter epoch tracking: filters are populated for a given selection
 * epoch exactly once, and skipped on subsequent visits unless their epoch
 * tag is stale. Combined with WeakMap-keyed identity, this lets us
 * distinguish "selection changed, repopulate every level" from "filter set
 * grew (zoom demand), only populate the new entries" without an explicit
 * callback split on `AggregationManager`.
 *
 * The sparse selection list is rebuilt once per selection-change and reused
 * across every level whose epoch tag is behind. Cost per level is
 * `O(|selection|)` — typical user selections have Hamming weight ≪ N, so
 * pan/zoom adding a finer aggregation level is cheap regardless of dataset
 * size.
 */
declare abstract class AbstractBucketLookupManager<TFilter extends AggregationFilterBase> {
    protected readonly opts: BucketLookupManagerOpts<TFilter>;
    private selectionEpoch;
    private sparseSelection?;
    private readonly populatedEpochs;
    protected readonly cache: LookupCache<TFilter>;
    constructor(opts: BucketLookupManagerOpts<TFilter>);
    /** Render-pass entrypoint — series pushes the resolved filter directly, skipping the lazy axis-poll path. */
    setActiveFilter(processedData: ProcessedData<any>, filter: AggregationFilterBase | undefined): void;
    /**
     * Selection-change entrypoint (called from `Series` on
     * `data-selection-change`). Bumps the epoch so every existing filter
     * becomes stale, rebuilds the sparse selection list from the live
     * bitset, then walks the filter list populating any whose epoch tag
     * doesn't match the new value.
     */
    refresh(): void;
    /**
     * Filter-set-mutation entrypoint (subscribed to
     * `aggregationManager.events.on('filtersChanged', …)`). Doesn't bump
     * the epoch — existing filters' SELECTED slots are still valid for the
     * current selection — but newly-added filter objects aren't yet in
     * `populatedEpochs`, so they get populated on this pass.
     */
    private populateStaleFilters;
    isBucketSelected(datumIndex: number): boolean | undefined;
    getRangeReader(): DatumRangeReader | undefined;
    getIndexSet(_datumIndex: number): Iterable<number> | undefined;
    /** Lazy fallback for callers that haven't primed the cache via {@link setActiveFilter} (e.g. drag-select). */
    protected ensureReaders(): LookupCache<TFilter> | undefined;
    protected invalidateCache(): void;
    protected abstract populateCache(filter: TFilter, dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>, xAxis: ChartAxis): LookupCache<TFilter> | undefined;
    protected abstract clearSelectedSlot(filter: TFilter): void;
    protected abstract populateFilter(filter: TFilter, sparse: Uint32Array, inputs: BucketingInputs): void;
}
/**
 * Bucket lookup roll-up for series whose aggregation filter exposes a single
 * `indexData` array (line, area, OHLC/candlestick, range-bar, range-area).
 */
export declare class BucketLookupManager<TFilter extends ExtremesFilter> extends AbstractBucketLookupManager<TFilter> implements BucketLookupFeature {
    protected clearSelectedSlot(filter: TFilter): void;
    protected populateFilter(filter: TFilter, sparse: Uint32Array, inputs: BucketingInputs): void;
    protected populateCache(filter: TFilter, dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>, xAxis: ChartAxis): LookupCache<TFilter>;
}
interface SplitBucketLookupManagerOpts<TFilter extends SplitFilter> extends BucketLookupManagerOpts<TFilter> {
    /**
     * Returns the data-model column id resolving to the y-end values used to
     * discriminate positive/negative arms. Bar's column varies (`yValue-end`
     * vs `yValue-raw` depending on stacking), so it's a getter.
     */
    getYColumnId: (dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>) => string;
}
/**
 * Bucket lookup roll-up for bar-style series whose aggregation filter splits
 * each bucket into positive and negative arms. Selection lookup is sign-aware
 * (reads only the matching arm) and the range reader matches the picked
 * representative against the extrema indices in either arm to determine which
 * bucket bounds to return.
 */
export declare class SplitBucketLookupManager<TFilter extends SplitFilter> extends AbstractBucketLookupManager<TFilter> implements BucketLookupFeature {
    private readonly splitOpts;
    constructor(splitOpts: SplitBucketLookupManagerOpts<TFilter>);
    protected clearSelectedSlot(filter: TFilter): void;
    protected populateFilter(filter: TFilter, sparse: Uint32Array, inputs: BucketingInputs): void;
    protected populateCache(filter: TFilter, dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>, xAxis: ChartAxis): LookupCache<TFilter>;
}
interface IndexSetBucketLookupManagerOpts {
    /** Cluster-keyed map of representative datum index to underlying datum indices. */
    getIndexSetMap: () => Map<number, number[]> | undefined;
    getSelection: () => Uint8Array | undefined;
}
/**
 * Bucket lookup roll-up for cluster-based aggregation (bubble/scatter). Each
 * rendered marker stands in for an arbitrary group of datums whose underlying
 * indices are non-contiguous, so neither the extremes-based range reader nor
 * the per-bucket SELECTED slot model from {@link BucketLookupManager} apply
 * here — the selection bit per cluster is read by walking the cluster's
 * index list against the per-series selection bitset.
 *
 * No precomputed roll-up: clusters are typically small (a few datums each)
 * and each marker render performs at most one lookup per cluster, so an
 * O(cluster-size) scan beats maintaining a parallel cache that has to be
 * invalidated on every selection change.
 */
export declare class IndexSetBucketLookupManager implements BucketLookupFeature {
    private readonly opts;
    constructor(opts: IndexSetBucketLookupManagerOpts);
    isBucketSelected(datumIndex: number): boolean | undefined;
    getRangeReader(): DatumRangeReader | undefined;
    getIndexSet(datumIndex: number): Iterable<number> | undefined;
    refresh(): void;
    setActiveFilter(): void;
}
