/**
 * Copyright IBM Corp. 2020, 2026
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

//#region ../ibm-products-utilities/src/utils/add-select/add-select-data.ts
/**
* AddSelectData - A lightweight, framework-agnostic utility for managing hierarchical data
*
* This utility provides standard APIs for common operations such as setting, selecting,
* traversing, searching, and sorting hierarchical data structures. It encapsulates data
* logic separate from UI, allowing both React and Web Components to reuse the same
* data management functions.
*/
var AddSelectData = class {
	constructor() {
		this.items = [];
		this.itemMap = /* @__PURE__ */ new Map();
		this.parentMap = /* @__PURE__ */ new Map();
		this.selectedIds = /* @__PURE__ */ new Set();
		this.depthCache = /* @__PURE__ */ new Map();
		this.selectedItemsCache = null;
	}
	/**
	* Initialize or replace the hierarchical data
	* @param items - Array of hierarchical items
	*/
	setItems(items) {
		this.items = items;
		this._invalidateCaches();
		this._buildMaps(items);
	}
	/**
	* Get the full list of items
	* @returns Array of all items
	*/
	getItems() {
		return this.items;
	}
	/**
	* Retrieve a single item by its id
	* @param id - The item id
	* @returns The item or undefined if not found
	*/
	getItem(id) {
		return this.itemMap.get(id);
	}
	/**
	* Update a given item with new properties
	* @param id - The item id
	* @param newProperties - Properties to update
	* @returns true if item was found and updated, false otherwise
	*/
	setItem(id, newProperties) {
		const item = this.itemMap.get(id);
		if (!item) return false;
		Object.assign(item, newProperties);
		if ("selected" in newProperties || "status" in newProperties) this._invalidateSelectionCache();
		return true;
	}
	/**
	* Returns an array of items marked as selected (memoized)
	* @returns Array of selected items
	*/
	getSelectedItems() {
		if (this.selectedItemsCache !== null) return this.selectedItemsCache;
		const selected = [];
		this.selectedIds.forEach((id) => {
			const item = this.itemMap.get(id);
			if (item) selected.push(item);
		});
		this.selectedItemsCache = selected;
		return selected;
	}
	/**
	* Mark one or more items (by id) as selected
	* @param ids - Single id or array of ids to select
	* @param exclusive - If true, deselect all other items (default: false)
	*/
	setSelectedItems(ids, exclusive = false) {
		const idArray = Array.isArray(ids) ? ids : [ids];
		if (exclusive) {
			this.selectedIds.forEach((id) => {
				const item = this.itemMap.get(id);
				if (item) {
					item.selected = false;
					item.status = "unchecked";
				}
			});
			this.selectedIds.clear();
		}
		idArray.forEach((id) => {
			const item = this.itemMap.get(id);
			if (item) {
				item.selected = true;
				item.status = "checked";
				this.selectedIds.add(id);
			}
		});
		this._invalidateSelectionCache();
	}
	/**
	* Get direct children of a node
	* @param id - The parent item id
	* @returns Array of child items or empty array if no children
	*/
	getItemChildren(id) {
		const item = this.itemMap.get(id);
		if (!item) return [];
		return item.children?.entries ?? [];
	}
	/**
	* Get the parent of a node
	* @param id - The child item id
	* @returns The parent item or undefined if no parent (root level)
	*/
	getItemParent(id) {
		const parentId = this.parentMap.get(id);
		return parentId ? this.itemMap.get(parentId) : void 0;
	}
	/**
	* Get all ancestors (parents up to the root) of a node
	* @param id - The item id
	* @returns Array of ancestor items from immediate parent to root
	*/
	getItemParents(id) {
		const parents = [];
		let currentId = id;
		while (currentId) {
			const parentId = this.parentMap.get(currentId);
			if (!parentId) break;
			const parent = this.itemMap.get(parentId);
			if (parent) parents.push(parent);
			currentId = parentId;
		}
		return parents;
	}
	/**
	* Retrieve the selected state status of an item
	* @param id - The item id
	* @returns The status or undefined if item not found
	*/
	getItemStatus(id) {
		return this.itemMap.get(id)?.status;
	}
	/**
	* Set or update the status of an item
	* @param id - The item id
	* @param status - The new status
	* @returns true if item was found and updated, false otherwise
	*/
	setItemStatus(id, status) {
		const item = this.itemMap.get(id);
		if (!item) return false;
		const wasSelected = item.selected;
		item.status = status;
		item.selected = status === "checked";
		if (item.selected) this.selectedIds.add(id);
		else this.selectedIds.delete(id);
		if (wasSelected !== item.selected) this._invalidateSelectionCache();
		return true;
	}
	/**
	* Check whether an item is selected (O(1) lookup)
	* @param id - The item id
	* @returns true if selected, false otherwise
	*/
	isSelected(id) {
		return this.selectedIds.has(id);
	}
	/**
	* Search items based on a query and return matching items
	* @param query - The search query string
	* @param options - Search options
	* @returns Array of matching items
	*/
	search(query, options = {}) {
		if (!query) return [];
		const { caseSensitive = false, searchFields = ["title", "value"], maxResults } = options;
		const searchTerm = caseSensitive ? query : query.toLowerCase();
		const results = [];
		const shouldContinue = () => {
			return !maxResults || results.length < maxResults;
		};
		this._traverseItems(this.items, (item) => {
			if (!shouldContinue()) return;
			for (const field of searchFields) {
				const fieldValue = item[field];
				if (fieldValue) {
					if ((caseSensitive ? String(fieldValue) : String(fieldValue).toLowerCase()).includes(searchTerm)) {
						results.push(item);
						break;
					}
				}
			}
		});
		return results;
	}
	/**
	* Sort items based on a comparator function
	* @param compareFn - Comparison function for sorting
	* @param recursive - If true, sort children recursively (default: false)
	*/
	sort(compareFn, recursive = false) {
		if (recursive) this._sortRecursive(this.items, compareFn);
		this.items.sort(compareFn);
		this._invalidateCaches();
		this._buildMaps(this.items);
	}
	/**
	* Clear all selections (optimized with Set)
	*/
	clearSelections() {
		this.selectedIds.forEach((id) => {
			const item = this.itemMap.get(id);
			if (item) {
				item.selected = false;
				item.status = "unchecked";
			}
		});
		this.selectedIds.clear();
		this._invalidateSelectionCache();
	}
	/**
	* Get the depth/level of an item in the hierarchy (cached)
	* @param id - The item id
	* @returns The depth (0 for root level items) or -1 if not found
	*/
	getItemDepth(id) {
		const cachedDepth = this.depthCache.get(id);
		if (cachedDepth !== void 0) return cachedDepth;
		if (!this.itemMap.has(id)) return -1;
		let depth = 0;
		let currentId = id;
		while (currentId) {
			const parentId = this.parentMap.get(currentId);
			if (!parentId) break;
			depth++;
			currentId = parentId;
		}
		this.depthCache.set(id, depth);
		return depth;
	}
	/**
	* Check if an item has children
	* @param id - The item id
	* @returns true if item has children, false otherwise
	*/
	hasChildren(id) {
		return !!this.itemMap.get(id)?.children?.entries?.length;
	}
	/**
	* Get all descendant items of a node
	* @param id - The parent item id
	* @returns Array of all descendant items
	*/
	getItemDescendants(id) {
		const item = this.itemMap.get(id);
		if (!item?.children?.entries) return [];
		const descendants = [];
		this._traverseItems(item.children.entries, (child) => {
			descendants.push(child);
		});
		return descendants;
	}
	/**
	* Check if an item has any selected descendants (optimized)
	* @param id - The item id
	* @param selectedIds - Set of selected item IDs (defaults to internal selectedIds)
	* @returns true if any descendant is selected, false otherwise
	*/
	hasSelectedDescendants(id, selectedIds = this.selectedIds) {
		const children = this.getItemChildren(id);
		if (!children.length) return false;
		for (const child of children) if (selectedIds.has(child.id) || this.hasSelectedDescendants(child.id, selectedIds)) return true;
		return false;
	}
	/**
	* Check if all descendants of an item are selected (optimized)
	* @param id - The item id
	* @param selectedIds - Set of selected item IDs (defaults to internal selectedIds)
	* @returns true if all descendants are selected, false otherwise
	*/
	allDescendantsSelected(id, selectedIds = this.selectedIds) {
		const item = this.itemMap.get(id);
		if (!item) return false;
		const children = this.getItemChildren(id);
		if (!children.length) return selectedIds.has(item.id);
		for (const child of children) if (!selectedIds.has(child.id) || !this.allDescendantsSelected(child.id, selectedIds)) return false;
		return true;
	}
	/**
	* Get all descendant IDs from an item (including the item itself)
	* @param id - The item id
	* @returns Array of all descendant IDs including the item itself
	*/
	getAllDescendantIds(id) {
		if (!this.itemMap.get(id)) return [];
		const ids = [id];
		const children = this.getItemChildren(id);
		for (const child of children) ids.push(...this.getAllDescendantIds(child.id));
		return ids;
	}
	/**
	* Get only top-level selected items (items without selected ancestors)
	* @param selectedIds - Set of selected item IDs (defaults to internal selectedIds)
	* @returns Array of top-level selected items
	*/
	getTopLevelSelectedItems(selectedIds = this.selectedIds) {
		const topLevelItems = [];
		const processedIds = /* @__PURE__ */ new Set();
		const hasSelectedAncestor = (itemId) => {
			return this.getItemParents(itemId).some((parent) => selectedIds.has(parent.id));
		};
		selectedIds.forEach((id) => {
			if (!processedIds.has(id) && !hasSelectedAncestor(id)) {
				const item = this.itemMap.get(id);
				if (item) {
					topLevelItems.push(item);
					this.getAllDescendantIds(id).forEach((descId) => processedIds.add(descId));
				}
			}
		});
		return topLevelItems;
	}
	/**
	* Invalidate all caches
	* @private
	*/
	_invalidateCaches() {
		this.selectedItemsCache = null;
		this.depthCache.clear();
	}
	/**
	* Invalidate selection cache only
	* @private
	*/
	_invalidateSelectionCache() {
		this.selectedItemsCache = null;
	}
	/**
	* Build internal maps for efficient lookups (optimized with depth caching)
	* @private
	*/
	_buildMaps(items, parentId, depth = 0) {
		if (!parentId) {
			this.itemMap.clear();
			this.parentMap.clear();
			this.selectedIds.clear();
			this.depthCache.clear();
		}
		items.forEach((item) => {
			this.itemMap.set(item.id, item);
			this.depthCache.set(item.id, depth);
			if (parentId) this.parentMap.set(item.id, parentId);
			if (item.selected) this.selectedIds.add(item.id);
			if (item.children?.entries) this._buildMaps(item.children.entries, item.id, depth + 1);
		});
	}
	/**
	* Traverse all items and apply a callback function
	* @private
	*/
	_traverseItems(items, callback) {
		for (const item of items) {
			callback(item);
			if (item.children?.entries) this._traverseItems(item.children.entries, callback);
		}
	}
	/**
	* Sort items recursively
	* @private
	*/
	_sortRecursive(items, compareFn) {
		for (const item of items) if (item.children?.entries) {
			item.children.entries.sort(compareFn);
			this._sortRecursive(item.children.entries, compareFn);
		}
	}
};
//#endregion
exports.AddSelectData = AddSelectData;
