import { DataSourceCollection, EntityCollection, ImageryLayerCollection, PrimitiveCollection, DataSource, Entity, ImageryLayer, Billboard, Cesium3DTileset, GroundPrimitive, Label, PointPrimitive, Polyline, Primitive } from 'cesium';

type CesiumCollection = DataSourceCollection | EntityCollection | ImageryLayerCollection | PrimitiveCollection;
type Primitives = Billboard | Cesium3DTileset | GroundPrimitive | Label | PointPrimitive | Polyline | Primitive;
type CesiumCollectionItem = DataSource | Entity | ImageryLayer | Primitives;
type Tag = string | number;
interface WithTag {
    [key: symbol]: Tag;
}
/**
 * Collection event types
 */
type CollectionEventType = "add" | "remove" | "update" | "clear";
/**
 * Event handler function type
 */
type EventHandler<I> = (event: {
    type: CollectionEventType;
    items?: I[];
    tag?: Tag;
}) => void;
type NonFunction<T> = {
    [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

/**
 * @class
 * A wrapper class that enhances Cesium collection objects with tagging functionality.
 * This class provides a consistent API for working with different types of Cesium collections
 * and allows grouping and manipulating collection items by custom tags.
 *
 * @template C - The type of Cesium collection (e.g., EntityCollection, PrimitiveCollection)
 * @template I - The type of items in the collection (e.g., Entity, Primitive)
 *
 * @example
 * // Example 1: Managing Complex Scene with Multiple Object Types
 * class SceneOrganizer {
 *   private entities: Collection<EntityCollection, Entity>;
 *   private billboards: Collection<BillboardCollection, Billboard>;
 *   private primitives: Collection<PrimitiveCollection, Primitive>;
 *
 *   constructor(viewer: Viewer) {
 *     this.entities = new Collection({ collection: viewer.entities });
 *     this.billboards = new Collection({
 *       collection: viewer.scene.primitives.add(new BillboardCollection())
 *     });
 *     this.primitives = new Collection({
 *       collection: viewer.scene.primitives
 *     });
 *   }
 *
 *   // Unified API across different collection types!
 *   showLayer(layerName: string) {
 *     this.entities.show(layerName);
 *     this.billboards.show(layerName);
 *     this.primitives.show(layerName);
 *   }
 *
 *   hideLayer(layerName: string) {
 *     this.entities.hide(layerName);
 *     this.billboards.hide(layerName);
 *     this.primitives.hide(layerName);
 *   }
 *
 *   removeLayer(layerName: string) {
 *     this.entities.remove(layerName);
 *     this.billboards.remove(layerName);
 *     this.primitives.remove(layerName);
 *   }
 * }
 *
 * // Example 2: Extend the class for Domain-Specific Needs
 * class BuildingCollection extends Collection<EntityCollection, Entity> {
 *   constructor(viewer: Viewer) {
 *     super({ collection: viewer.entities, tag: 'buildings' });
 *   }
 *
 *   addBuilding(options: {
 *     position: Cartesian3;
 *     height: number;
 *     floors: number;
 *     type: 'residential' | 'commercial' | 'industrial';
 *   }): Entity {
 *     const building = new Entity({
 *       position: options.position,
 *       box: {
 *         dimensions: new Cartesian3(50, 50, options.height),
 *         material: this.getMaterialForType(options.type)
 *       }
 *     });
 *
 *     // Tag by type AND by floor count
 *     this.add(building, options.type);
 *     this.add(building, `floors-${options.floors}`);
 *
 *     return building;
 *   }
 *
 *   getByFloorRange(min: number, max: number): Entity[] {
 *     const results: Entity[] = [];
 *     for (let i = min; i <= max; i++) {
 *       results.push(...this.get(`floors-${i}`));
 *     }
 *     return results;
 *   }
 *
 *   private getMaterialForType(type: string): Material {
 *     const colors = {
 *       residential: Color.GREEN,
 *       commercial: Color.BLUE,
 *       industrial: Color.YELLOW
 *     };
 *     return new ColorMaterialProperty(colors[type] || Color.WHITE);
 *   }
 * }
 */
declare class Collection<C extends CesiumCollection, I extends CesiumCollectionItem> {
    /**
     * Symbol used as a property key to store tags on collection items.
     * Using a Symbol ensures no property naming conflicts with the item's own properties.
     * @readonly
     */
    static readonly symbol: unique symbol;
    /**
     * Default tag used when adding items without specifying a tag.
     * @protected
     */
    protected tag: Tag;
    /**
     * The underlying Cesium collection being wrapped.
     * @protected
     */
    protected collection: C;
    /**
     * Cache for values array to improve performance
     * @private
     */
    private _valuesCache;
    /**
     * Tag to items map for faster lookups
     * @private
     */
    private _tagMap;
    /**
     * Event listeners
     * @private
     */
    private _eventListeners;
    /**
     * For cleaning up the instances
     * @private
     */
    private _eventCleanupFunctions;
    /**
     * Creates a new Collection instance.
     *
     * @param options - Configuration options
     * @param options.collection - The Cesium collection to wrap
     * @param options.tag - The default tag to use for items (defaults to 'default')
     */
    constructor({ collection, tag }: {
        collection: C;
        tag?: Tag;
    });
    /**
     * Makes the collection directly iterable, allowing it to be used in `for...of` loops
     * and with spread operators.
     *
     * @returns An iterator for the items in the collection
     *
     * @example
     * // Iterate through all items in the collection
     * for (const entity of collection) {
     *   console.log(entity.id);
     * }
     *
     * // Convert collection to array using spread syntax
     * const entitiesArray = [...collection];
     */
    [Symbol.iterator](): Iterator<I>;
    /**
     * Emits an event to all registered listeners.
     *
     * @private
     * @param type - The event type
     * @param data - Additional event data
     */
    private _emit;
    /**
     * Adds an item to the internal tag map for quick lookups.
     *
     * @private
     * @param item - The item to add
     * @param tag - The tag to associate with the item
     */
    private _addToTagMap;
    /**
     * Removes an item from the internal tag map.
     *
     * @private
     * @param item - The item to remove
     */
    private _removeFromTagMap;
    /**
     * Invalidates the values cache when collection changes.
     *
     * @private
     */
    private _invalidateCache;
    /**
     * Sets up automatic cache invalidation by registering event listeners on the underlying Cesium collection.
     *
     * @private
     * @param collection - The Cesium collection to monitor for changes
     *
     * @see {@link destroy} For cleanup of event listeners
     * @see {@link _invalidateCache} For the actual cache invalidation logic
     */
    private _setupCacheInvalidator;
    /**
     * Registers an event listener for collection events.
     *
     * @param type - The event type to listen for
     * @param handler - The callback function
     * @returns The collection instance for method chaining
     */
    addEventListener(type: CollectionEventType, handler: EventHandler<I>): this;
    /**
     * Removes an event listener.
     *
     * @param type - The event type
     * @param handler - The callback function to remove
     * @returns The collection instance for method chaining
     */
    removeEventListener(type: CollectionEventType, handler: EventHandler<I>): this;
    /**
     * Adds a single item with a tag to the collection.
     *
     * @param item - The item to add to the collection
     * @param tag - Tag to associate with this item (defaults to the collection's default tag)
     * @param index - The index to add the item at (if supported by the collection)
     * @returns The added item for chaining
     *
     * @example
     * const entity = collection.add(new Entity({ ... }), 'landmarks');
     */
    add(item: I, tag?: Tag, index?: number): I;
    /**
     * Adds multiple items with the same tag to the collection.
     *
     * @param items - The array of items to add to the collection
     * @param tag - Tag to associate with this item (defaults to the collection's default tag)
     * @returns The array of added items
     *
     * @example
     * // Add multiple entities with the same tag
     * const entities = [new Entity({ ... }), new Entity({ ... })];
     * const addedEntities = collection.add(entities, 'buildings');
     */
    add(items: I[], tag?: Tag): I[];
    /**
     * Returns true if the provided item is in this collection, false otherwise.
     *
     * @param item - The item instance to check for
     * @returns True if the item is in the collection, false otherwise
     */
    contains(item: I): boolean;
    /**
     * Checks if the collection has any items with the specified tag.
     *
     * @param tag - The tag to check for
     * @returns True if items with the tag exist, false otherwise
     *
     * @example
     * if (collection.contains('temporary')) {
     *   console.log('Temporary items exist');
     * }
     */
    contains(tag: Tag): boolean;
    /**
     * Removes an item from the collection.
     *
     * @param item - The item to remove
     * @returns True if the item was removed, false if it wasn't found
     */
    remove(item: I): boolean;
    /**
     * Removes all items with the specified tag from the collection.
     *
     * @param by - The tag identifying which items to remove
     * @returns True if the item was removed, false if it wasn't found
     */
    remove(by: Tag): boolean;
    /**
     * Removes all items with the array of tags from the collection.
     *
     * @param by - The tags identifying which items to remove
     * @returns True if the item was removed, false if it wasn't found
     */
    remove(by: Tag[]): boolean;
    /**
     * Removes all items from the collection.
     */
    removeAll(): void;
    /**
     * Permanently destroys this Collection instance.
     * Removes all event listeners and clears internal state.
     * The Collection instance should not be used after calling this method.
     */
    destroy(): void;
    /**
     * Gets all item instances in the collection.
     * This array should not be modified directly.
     *
     * @returns An array of all items in the collection
     */
    get values(): I[];
    /**
     * Gets the number of items in the collection.
     *
     * @returns The item count
     */
    get length(): number;
    /**
     * Gets all items with the specified tag from the collection.
     * Uses an optimized internal map for faster lookups.
     *
     * @param by - The tag to filter by
     * @returns An array of items with the specified tag, or an empty array if none found
     *
     * @example
     * // Get all buildings
     * const buildings = collection.get('buildings');
     */
    get(by: Tag): I[];
    /**
     * Gets the first item matching the tag. More efficient than `get` when
     * you only need one item, especially for large collections.
     *
     * @param by - The tag to search for
     * @returns The first matching item or undefined if none found
     *
     * @example
     * // Get the first building
     * const firstBuilding = collection.first('buildings');
     */
    first(by: Tag): I | undefined;
    /**
     * Gets all unique tags currently in use in the collection.
     *
     * @returns An array of all unique tags
     *
     * @example
     * // Get all tags
     * const tags = collection.tags;
     * console.log(`Collection has these tags: ${tags.join(', ')}`);
     */
    get tags(): Tag[];
    /**
     * Updates the tag for all items with the specified tag.
     *
     * @param from - The tag to replace
     * @param to - The new tag to assign
     * @returns The number of items updated
     *
     * @example
     * // Rename a tag
     * const count = collection.update('temp', 'temporary');
     * console.log(`Updated ${count} items`);
     */
    update(from: Tag, to: Tag): number;
    /**
     * Makes all items with the specified tag visible.
     * Only affects items that have a 'show' property.
     *
     * @param by - The tag identifying which items to show
     * @returns The collection itself.
     *
     * @example
     * // Show all buildings
     * collection.show('buildings');
     */
    show(by: Tag): this;
    /**
     * Hides all items with the specified tag.
     * Only affects items that have a 'show' property.
     *
     * @param by - The tag identifying which items to hide
     * @returns The collection itself.
     *
     * @example
     * // Hide all buildings
     * collection.hide('buildings');
     */
    hide(by: Tag): this;
    /**
     * Toggles visibility of all items with the specified tag.
     * Only affects items that have a 'show' property.
     *
     * @param by - The tag identifying which items to toggle
     * @returns The collection itself.
     *
     * @example
     * // Toggle visibility of all buildings
     * collection.toggle('buildings');
     */
    toggle(by: Tag): this;
    /**
     * Sets a property value on all items with the specified tag.
     *
     * @param by - The tag identifying which items to update
     * @param property - The property name to set
     * @param value - The value to set
     * @returns The number of items updated
     *
     * @example
     * // Change color of all buildings to red
     * collection.setProperty('buildings', 'color', Color.RED);
     */
    setProperty<K extends NonFunction<I>>(property: K, value: I[K], by?: Tag): this;
    /**
     * Filters items in the collection based on a predicate function.
     * Optionally only filters items with a specific tag.
     *
     * @param predicate - Function that tests each item
     * @param by - Optional tag to filter by before applying the predicate
     * @returns Array of items that pass the test
     *
     * @example
     * // Get all buildings taller than 100 meters
     * const tallBuildings = collection.filter(
     *   entity => entity.properties?.height?.getValue() > 100,
     *   'buildings'
     * );
     */
    filter(predicate: (item: I) => boolean, by?: Tag): I[];
    /**
     * Executes a callback function for each item in the collection.
     * Optionally filters items by tag before execution.
     *
     * @param callback - Function to execute for each item
     * @param by - Optional tag to filter items (if not provided, processes all items)
     *
     * @example
     * // Highlight all buildings
     * collection.forEach((entity) => {
     *   if (entity.polygon) {
     *     entity.polygon.material = new ColorMaterialProperty(Color.YELLOW);
     *   }
     * }, 'buildings');
     */
    forEach(callback: (value: I, index: number) => void, by?: Tag): void;
    /**
     * Creates a new array with the results of calling a provided function on every element
     * in the collection. Optionally filters by tag before mapping.
     *
     * @param callbackfn - Function that produces an element of the new array
     * @param by - Optional tag to filter items by before mapping
     * @returns A new array with each element being the result of the callback function
     *
     * @example
     * // Get all entity IDs
     * const entityIds = collection.map(entity => entity.id);
     *
     * // Get positions of all buildings
     * const buildingPositions = collection.map(
     *   entity => entity.position.getValue(Cesium.JulianDate.now()),
     *   'buildings'
     * );
     */
    map<R>(callbackfn: (value: I, index: number) => R, by?: Tag): R[];
    /**
     * Returns the first element in the collection that satisfies the provided testing function.
     * Optionally filters by tag before searching.
     *
     * @param predicate - Function to test each element
     * @param by - Optional tag to filter items by before searching
     * @returns The first element that passes the test, or undefined if no elements pass
     *
     * @example
     * // Find the first entity with a specific name
     * const namedEntity = collection.find(entity => entity.name === 'Target');
     *
     * // Find the first building taller than 100 meters
     * const tallBuilding = collection.find(
     *   entity => entity.properties?.height?.getValue() > 100,
     *   'buildings'
     * );
     */
    find(predicate: (value: I) => boolean, by?: Tag): I | undefined;
}

export { type CesiumCollection, type CesiumCollectionItem, Collection, type CollectionEventType, type EventHandler, type NonFunction, type Tag, type WithTag };
