import { KEYS } from './constants';
function flatten(tree, map, acc, parents) {
  tree.forEach(n => {
    const {
      children,
      node
    } = map(n, parents);
    acc.push(node);
    flatten(children, map, acc, [node.id, ...parents]);
  });
}
export function flattenTree(tree, map) {
  const nodes = [];
  flatten(tree, map, nodes, []);
  return nodes;
}
export function getFocusableNodes(nodes) {
  return nodes.filter(({
    isDisabled
  }) => !isDisabled);
}
export function getNodesMap(nodes) {
  const entries = nodes.map(n => [n.id, n]);
  return new Map(entries);
}
export function setNodeState(id, patch, nodes) {
  return nodes.map(node => node.id === id ? {
    ...node,
    ...patch
  } : node);
}
export function setAllNodesState(patch, nodes) {
  return nodes.map(node => ({
    ...node,
    ...patch
  }));
}
export const getKeyActionFnDefault = (key, node) => {
  if (node.isExpandable && (key === KEYS.RIGHT || key === KEYS.ENTER)) {
    return node.isExpanded ? moveFocusToChild : expandNode;
  }
  if (key === KEYS.LEFT) {
    if (node.isExpanded) {
      return collapseNode;
    }
    if (node.parent) {
      return moveFocusToParent;
    }
  }
  if (key === KEYS.DOWN) {
    return moveFocusToNextNode;
  }
  if (key === KEYS.UP) {
    return moveFocusToPrevNode;
  }
  if (key === KEYS.HOME) {
    return moveFocusToFirstNode;
  }
  if (key === KEYS.END) {
    return moveFocusToLastNode;
  }
  if (key === KEYS.ENTER || key === KEYS.SPACE) {
    return toggleSelect;
  }
  return null;
};
export const getNavigationFn = (key, node, context) => {
  if (node.isExpandable && (key === KEYS.RIGHT || key === KEYS.ENTER)) {
    return node.isExpanded ? moveFocusToChild : [expandNode, moveFocusToChild];
  }
  const parent = node.parent;
  if (parent) {
    if (key === KEYS.LEFT || key === KEYS.ESC) {
      const parentNode = context?.nodesMap.get(parent);
      if (parentNode) {
        return [moveFocusToParent, ({
          context: ctx,
          onToggleExpanded,
          onToggleSelected,
          isSelected,
          onCustomButtonKeyDown,
          button
        }) => collapseNode({
          node: parentNode,
          context: ctx,
          onToggleExpanded,
          onToggleSelected,
          isSelected,
          onCustomButtonKeyDown,
          button
        })];
      }
    }
  } else if (key === KEYS.ESC || key === KEYS.TAB) {
    return onCustomButtonPress;
  }
  if (key === KEYS.DOWN) {
    return moveFocusToNextMenuNode;
  }
  if (key === KEYS.UP) {
    return moveFocusToPrevMenuNode;
  }
  if (key === KEYS.HOME) {
    return moveFocusToFirstMenuNode;
  }
  if (key === KEYS.END) {
    return moveFocusToLastMenuNode;
  }
  return null;
};
export const getSelectionFn = key => {
  if (key === KEYS.ENTER || key === KEYS.SPACE) {
    return toggleSelect;
  }
  return null;
};
export const getKeyActionFnMenu = (key, node, context) => {
  return getNavigationFn(key, node, context) || getSelectionFn(key, node);
};
export const getKeyActionFnSearch = (key, node, context) => {
  return getNavigationFn(key, node, context);
};

// action functions start
export function moveFocusToChild({
  node,
  context
}) {
  const child = context.nodes.find(n => n.parent === node.id);
  if (child) {
    context.setFocused(child.id);
  }
}
export function expandNode({
  node,
  context,
  onToggleExpanded
}) {
  context.setNodes(nodes => {
    const withCurrentExpanded = setNodeState(node.id, {
      isExpanded: true
    }, nodes);
    if (context.isNodeExpandedByDefault) {
      return withCurrentExpanded.map(n => {
        return n.parents.includes(node.id) ? {
          ...n,
          isExpanded: true
        } : n;
      });
    }
    return withCurrentExpanded;
  });
  onToggleExpanded(true);
}
export function collapseNode({
  node,
  context,
  onToggleExpanded
}) {
  context.setNodes(nodes => {
    return setNodeState(node.id, {
      isExpanded: false
    }, nodes);
  });
  onToggleExpanded(false);
}
export function moveFocusToParent({
  node,
  context
}) {
  const parent = node.parent;
  if (parent) {
    context.setFocused(parent);
  }
}
export function moveFocusToNextNode({
  node,
  context
}) {
  const targetNode = getNextNode(node, context.nodes, context.nodesMap);
  if (!targetNode) {
    return;
  }
  context.setFocused(targetNode.id);
}
export function moveFocusToNextMenuNode({
  node,
  context
}) {
  const targetNode = getNextMenuNode(node, context.nodes);
  if (!targetNode) {
    return;
  }
  context.setFocused(targetNode.id);
}
export function moveFocusToPrevNode({
  node,
  context
}) {
  const targetNode = getPrevNode(node, context.nodes, context.nodesMap);
  if (!targetNode) {
    return;
  }
  context.setFocused(targetNode.id);
}
export function moveFocusToPrevMenuNode({
  node,
  context
}) {
  const targetNode = getPrevMenuNode(node, context.nodes);
  if (!targetNode) {
    return;
  }
  context.setFocused(targetNode.id);
}
export function moveFocusToFirstNode({
  context
}) {
  const targetNode = context.nodes[0];
  context.setFocused(targetNode.id);
}
export function moveFocusToFirstMenuNode({
  context,
  node
}) {
  const targetNode = context.nodes.filter(n => n.parent === node.parent)[0];
  if (targetNode) {
    context.setFocused(targetNode.id);
  }
}
export function moveFocusToLastNode({
  context
}) {
  const targetNode = context.nodes[context.nodes.length - 1];
  context.setFocused(targetNode.id);
}
export function moveFocusToLastMenuNode({
  context,
  node
}) {
  const targetNode = context.nodes.filter(n => n.parent === node.parent).slice(-1)[0];
  if (targetNode) {
    context.setFocused(targetNode.id);
  }
}
export function toggleSelect({
  onToggleSelected,
  isSelected
}) {
  onToggleSelected(!isSelected);
}
export function onCustomButtonPress({
  onCustomButtonKeyDown,
  button
}) {
  return onCustomButtonKeyDown?.(button);
}

// action functions end

export function isNodeVisible(node, nodesMap) {
  if (!node.parent) {
    return true;
  }
  return node.parents.every(p => !!nodesMap.get(p)?.isExpanded);
}
export function getNextNode(node, nodes, nodesMap) {
  const index = nodes.indexOf(node);
  return nodes.find((n, i) => i > index && isNodeVisible(n, nodesMap)) ?? null;
}
export function getNextMenuNode(node, nodes) {
  const sNodes = nodes.filter(n => n.parent === node.parent);
  const index = sNodes.indexOf(node);
  return sNodes.find((_, i) => i > index) ?? sNodes[0];
}
export function getPrevNode(node, nodes, nodesMap) {
  const index = nodes.indexOf(node);

  // TODO compilerOptions > lib/target update
  // TS2339: Property findLast does not exist on type TreeNode[]
  for (let i = index - 1; i >= 0; i--) {
    if (isNodeVisible(nodes[i], nodesMap)) {
      return nodes[i];
    }
  }
  return null;
  // return (
  //   nodes.findLast((n, i) => i < index && isNodeVisible(n, nodesMap)) ?? null
  // );
}
export function getPrevMenuNode(node, nodes) {
  const sNodes = nodes.filter(n => n.parent === node.parent);
  const index = sNodes.indexOf(node);
  return index > 0 ? sNodes[index - 1] : sNodes.slice(-1)[0];
}
export function getSetSize(node, nodes, nodesMap) {
  const parentNode = nodesMap.get(node.parent);
  if (parentNode) {
    return parentNode.children.length;
  }
  return nodes.filter(({
    parent
  }) => !parent).length;
}
export function getPositionInSet(node, nodes, nodesMap) {
  const parentNode = nodesMap.get(node.parent);
  if (parentNode) {
    return parentNode.children.indexOf(node.id) + 1;
  }
  return nodes.filter(({
    parent
  }) => !parent).findIndex(({
    id
  }) => node.id === id) + 1;
}
export function getDummyNode(id) {
  return {
    id,
    children: [],
    parent: null,
    parents: [],
    label: '',
    isDisabled: false,
    isExpanded: false,
    isExpandable: false,
    isFocused: false,
    isLeaf: false
  };
}