import React, { ReactNode, useEffect, useState } from 'react';
import { SimpleTreeView as MuiTreeView } from '@mui/x-tree-view';

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

import { filterTreeBySearchQuery, getAggregateSelectedIds, NodeType } from '@/mui/helpers/treeView';

import TreeViewItem from './TreeViewItem';

type Props = {
  searchText?: string;
  multiSelect?: boolean;
  autoSuggest?: boolean;
  flatTree: Map<string, NodeType>;
  onNodeSelect: (
    event: React.MouseEvent<HTMLDivElement, MouseEvent>,
    selectedNodes: string[],
    aggregateSelectedNodes?: string[],
    targetNode?: NodeType,
  ) => void;
};

function TreeView({ searchText, multiSelect, autoSuggest, flatTree, onNodeSelect }: Props) {
  const propsSearchText = searchText ?? '';
  const propsMultiSelect = multiSelect ?? true;
  const propsAutoSuggest = autoSuggest ?? true;
  const [stateSearchText, setSearchText] = useState('');
  const [expanded, setExpanded] = useState<string[]>([]);
  const [hidden, setHidden] = useState<string[]>([]);

  useEffect(() => {
    const delayedModify = setTimeout(() => {
      setSearchText((prevSearchText) => {
        if (prevSearchText !== propsSearchText) {
          const [hiddenNodes, expandedNodes] = filterTreeBySearchQuery(flatTree, propsSearchText);

          setHidden((prev: string[]) => (!prev.length && !hiddenNodes.length ? prev : hiddenNodes));
          setExpanded((prev: string[]) => (!prev.length && !expandedNodes.length ? prev : expandedNodes));
        }

        return propsSearchText;
      });
    }, 200);

    return () => {
      clearTimeout(delayedModify);
    };
  }, [flatTree, propsSearchText]);

  const getSelectedNodes = (targetNode: NodeType) => {
    if (!propsMultiSelect) {
      return targetNode.selected ? [] : [targetNode.id];
    }

    const selected: string[] = [];

    flatTree.forEach((node) => {
      if (node.selected) {
        selected.push(node.id);
      }
    });

    if (!targetNode.selected) {
      const resultNodes = [...selected, targetNode.id, ...targetNode.childrenIds];

      targetNode.parentPath.forEach((nodeId$) => {
        const targetNode$ = flatTree.get(nodeId$) as NodeType;

        if (targetNode$.childrenIds.length && targetNode$.childrenIds.every((id) => resultNodes.includes(id))) {
          resultNodes.push(nodeId$);
        }
      });

      return resultNodes;
    }

    return selected.filter(
      (id) => id !== targetNode.id && !targetNode.childrenIds.includes(id) && !targetNode.parentPath.includes(id),
    );
  };

  const renderTree = (targetNode: NodeType) =>
    !hidden.includes(targetNode.id) && (
      <TreeViewItem
        key={targetNode.id}
        itemId={targetNode.id}
        label={targetNode.label}
        indeterminate={
          propsMultiSelect &&
          !targetNode.selected &&
          targetNode.childrenIds.some((nodeId$) => flatTree.get(nodeId$)?.selected)
        }
        selected={targetNode.selected}
        multiSelect={propsMultiSelect}
        autoSuggest={propsAutoSuggest && stateSearchText.length >= MIN_SEARCH_LENGTH}
        highlight={targetNode.highlight}
        searchText={stateSearchText}
        onNodeExpand={(event, nodeId, isExpand) => {
          event.stopPropagation();

          setExpanded((prev) => (isExpand ? prev.concat(nodeId) : prev.filter((id) => id !== nodeId)));
        }}
        onNodeSelect={(event) => {
          event.stopPropagation();

          const selectedNodes = getSelectedNodes(targetNode);
          const aggregateSelectedNodes = getAggregateSelectedIds(flatTree, propsMultiSelect, selectedNodes);

          onNodeSelect(event, selectedNodes, aggregateSelectedNodes, targetNode);
        }}
      >
        {targetNode.childrenIds.some((nodeId$) => !hidden.includes(nodeId$)) &&
          targetNode.directChildrenIds.map((nodeId$) => renderTree(flatTree.get(nodeId$) as NodeType))}
      </TreeViewItem>
    );

  const nodes: ReactNode[] = [];

  flatTree.forEach((node) => {
    if (!node.parentPath.length) {
      nodes.push(renderTree(node));
    }
  });

  return (
    <MuiTreeView expandedItems={expanded} multiSelect={false}>
      {nodes as ReactNode}
    </MuiTreeView>
  );
}

TreeView.displayName = 'TreeView';

export default TreeView;
