import { MIN_SEARCH_LENGTH } from '@/mui/constants/treeView';

import { deepClone } from '@/modules/@common/helpers';

export type NodeType = {
  id: string;
  name?: string;
  label: string;
  selected: boolean;
  parentPath: string[];
  searchQuery?: string;
  categories?: string;
  children?: NodeType[];
  highlight?: string;
  childrenIds: string[];
  directChildrenIds: string[];
};

export type TreeMapperType = {
  [key: string]: keyof NodeType;
};

const getChildrenIds = (node: NodeType, treeMapper: TreeMapperType, list: string[], onlyDirect: boolean) => {
  if (Array.isArray(node[treeMapper.children])) {
    const children = node[treeMapper.children] as NodeType[];
    children.forEach((childNode) => {
      list.push(childNode[treeMapper.id] as string);

      if (!onlyDirect) {
        getChildrenIds(childNode, treeMapper, list, onlyDirect);
      }
    });
  }

  return list;
};

export const createFlatTreeMap = (
  initialNode: NodeType | NodeType[],
  userTreeMapper: TreeMapperType = {},
  defaultSelectedIds: string[] = [],
) => {
  const treeMapper = {
    id: userTreeMapper?.id ?? 'id',
    label: userTreeMapper?.label ?? 'label',
    children: userTreeMapper?.children ?? 'children',
    highlight: userTreeMapper?.highlight ?? 'highlight',
    selected: userTreeMapper?.selected ?? 'selected',
  };

  const flatTree = new Map();

  const step = (node: NodeType | NodeType[], parentIds: string[] = []) => {
    if (Array.isArray(node)) {
      node.map((node$) => step(node$, parentIds));
    } else {
      const label = (node[treeMapper.label] as string) ?? '';

      flatTree.set(node[treeMapper.id], {
        initialNode: deepClone(node),
        id: node[treeMapper.id],
        selected: !!node[treeMapper.selected],
        parentPath: parentIds,
        directChildrenIds: getChildrenIds(node, treeMapper, [], true),
        childrenIds: getChildrenIds(node, treeMapper, [], false),
        label,
        searchQuery: label.toLowerCase(),
        highlight: !!node[treeMapper.highlight],
      });

      if (Array.isArray(node[treeMapper.children])) {
        const children = node[treeMapper.children] as NodeType[];
        children.forEach((child) => step(child, [node[treeMapper.id] as string, ...parentIds]));
      }
    }
  };

  step(initialNode);

  defaultSelectedIds.forEach((id) => {
    const node = flatTree.get(id);

    node.selected = true;
    node.childrenIds.forEach((id$: string) => {
      flatTree.get(id$).selected = true;
    });
  });

  return flatTree;
};

export const filterTreeBySearchQuery = (flatTree: Map<string, NodeType>, query: string) => {
  const hiddenNodes = new Set();
  const expandedNodes = new Set();

  const lowerCaseQuery = query.toLowerCase();

  if (query.length >= MIN_SEARCH_LENGTH) {
    flatTree.forEach((currentNode) => {
      if (currentNode.searchQuery?.includes(lowerCaseQuery)) {
        currentNode.parentPath?.forEach((nodeId) => {
          hiddenNodes.delete(nodeId);
          expandedNodes.add(nodeId);
        });
      } else {
        hiddenNodes.add(currentNode.id);
      }
    });
  }

  return [[...hiddenNodes], [...expandedNodes]] as [string[], string[]];
};

export const getAggregateSelectedIds = (
  flatTree: Map<string, NodeType> = new Map(),
  multiSelect = true,
  selectedIds: string[] | undefined = undefined,
) => {
  let selected: string[] = [];

  if (selectedIds !== undefined) {
    selected = [...selectedIds];
  } else {
    flatTree.forEach((node) => {
      if (node.selected) {
        selected.push(node.id);
      }
    });
  }

  if (!multiSelect) {
    return selected.slice(0, 1);
  }

  return selected.reduce((acc: string[], nodeId) => {
    const checkedNode = flatTree.get(nodeId);
    const nodeChildren: NodeType[] = [];

    if (checkedNode) {
      flatTree.forEach((node$) => {
        if (node$.parentPath?.includes(nodeId)) {
          nodeChildren.push(node$);
        }
      });

      const allCheckedChildren = nodeChildren.length
        ? checkedNode.selected || nodeChildren.every((node$) => selected.includes(node$.id))
        : true;

      if (allCheckedChildren && !selected.includes(checkedNode.parentPath?.[0] ?? '')) {
        acc.push(checkedNode.id);
      }
    }

    return acc;
  }, []);
};
