import type { Entity } from "./types";
import { type MatchHost } from "./query-match";
/**
 * Host interface that QueryCache uses to read EntityManager state.
 * Decouples QueryCache from EntityManager's private fields.
 */
export interface QueryCacheHost<ComponentTypes> extends MatchHost<ComponentTypes> {
    getChildren(parentId: number): readonly number[];
    allEntities(): IterableIterator<Entity<ComponentTypes>>;
    componentIndex(component: keyof ComponentTypes): Set<number> | undefined;
}
/**
 * Maintains incrementally-updated Maps of entity id → Entity matching the
 * static portion of registered query shapes (with / without / parentHas).
 * EntityManager calls the on* hooks on component add/remove, entity
 * removal, and parent change. The `changed` filter is applied as a
 * post-pass by the caller, since its threshold advances each tick.
 */
export default class QueryCache<ComponentTypes> {
    private readonly host;
    private readonly caches;
    private readonly byComp;
    private readonly byParentComp;
    constructor(host: QueryCacheHost<ComponentTypes>);
    /**
     * Returns the Map of entity id → Entity matching the (with, without,
     * parentHas) shape. Caches are interned by canonical shape — identical
     * shapes share a single Map across systems. Cold-start populates by
     * iterating the smallest matching component index.
     */
    getOrCreate(withC: ReadonlyArray<keyof ComponentTypes>, withoutC: ReadonlyArray<keyof ComponentTypes>, parentHas: ReadonlyArray<keyof ComponentTypes>): Map<number, Entity<ComponentTypes>>;
    /** Test-only: returns the number of distinct interned shapes. */
    get cacheCount(): number;
    private populate;
    private matches;
    private reeval;
    onComponentChanged(entityId: number, componentName: keyof ComponentTypes): void;
    onParentChanged(childId: number): void;
    /**
     * Drops the entity from every cache. For parentHas caches, also drops
     * direct children — once this entity is gone, the parent-link severs
     * and children stop matching. Cascade-removed children fire their own
     * beforeRemoved first, so the extra delete is a harmless no-op there.
     */
    onEntityRemoved(entityId: number): void;
}
