// biome-ignore lint/suspicious/noExplicitAny: ... because it's a helper file
type BasicNode = Record<string, any>
interface SearchTreeParameters<NodeType extends BasicNode = BasicNode> {
  subItemsField: keyof NodeType
  searchItemField: keyof NodeType
type SearchTreeListParameters<NodeType extends BasicNode = BasicNode> = Omit<
  SearchTreeParameters<NodeType>,
 * @description Searches a tree of nodes for a node that matches the given search term.
 * This function takes a node and a search term, and searches the node and its sub-nodes for a node where `node[searchItemField]` equals `searchTerm`.
 * It returns the first matching node, or `undefined` if no such node is found.
 * This function uses the {@link searchTreeList} function to search a list of nodes.
 * @template NodeType A type that extends `BasicNode`. This is the type of the nodes in the tree.
 * @param {SearchTreeParameters<NodeType>} params The parameters for the search. This is an object that contains:
 * - `node`: The root node of the tree to search.
 * - `searchTerm`: The term to search for.
 * - `subItemsField`: The name of the field that contains the sub-nodes of a node.
 * - `searchItemField`: The name of the field to compare with the search term.
 * @returns {undefined | NodeType} The first node where `node[searchItemField]` equals `searchTerm`, or `undefined` if no such node is found.
 * const node = { id: 1, name: 'Node 1', children: [{ id: 2, name: 'Node 2' }] };
 * const params = { node, searchTerm: 'Node 2', subItemsField: 'children', searchItemField: 'name' };
 * const foundNode = searchTree(params); // { id: 2, name: 'Node 2' }
export const searchTree = <NodeType extends BasicNode = BasicNode>(
  params: SearchTreeParameters<NodeType>,
): undefined | NodeType => {
  const { node, searchTerm, subItemsField, searchItemField } = params
  if (!(subItemsField in node)) {
    throw new TypeError(`${subItemsField.toString()} is missing from NodeType`)
  if (!(searchItemField in node)) {
      `${searchItemField.toString()} is missing from NodeType`,
  if (node[searchItemField] === searchTerm) return node
  if (node[subItemsField] != null && node[subItemsField]?.length > 0) {
      nodeList: node[subItemsField],
 * @description Searches a list of nodes for a node that matches the given search term.
 * This function iterates over a list of nodes and uses the `searchTree` function to search each node and its sub-nodes.
 * It returns the first node where `node[searchItemField]` equals `searchTerm`, or `undefined` if no such node is found.
 * This function uses the {@link searchTree} function to search each node.
 * @template NodeType A type that extends `BasicNode`. This is the type of the nodes in the list.
 * @param {SearchTreeListParameters<NodeType>} params The parameters for the search. This is an object that contains:
 * - `nodeList`: The list of nodes to search.
 * - `searchTerm`: The term to search for.
 * - `subItemsField`: The name of the field that contains the sub-nodes of a node.
 * - `searchItemField`: The name of the field to compare with the search term.
 * @returns {undefined | NodeType} The first node where `node[searchItemField]` equals `searchTerm`, or `undefined` if no such node is found.
 * const nodes = [{ id: 1, name: 'Node 1', children: [{ id: 2, name: 'Node 2' }] }];
 * const params = { nodeList: nodes, searchTerm: 'Node 2', subItemsField: 'children', searchItemField: 'name' };
 * const foundNode = searchTreeList(params); // { id: 2, name: 'Node 2' }
export function searchTreeList<NodeType extends BasicNode = BasicNode>(
  params: SearchTreeListParameters<NodeType>,
): undefined | NodeType {
  const { nodeList, searchTerm, subItemsField, searchItemField } = params
  let foundNode: undefined | NodeType
  for (const node of nodeList) {
    if (foundNode) return foundNode