/* eslint-disable no-case-declarations */
import { useRef, useState, useCallback, useMemo } from 'react';

export interface UseTogglableNavInterface {
  toggleProperty: string;
  singleOpen?: boolean;
  toggleOpenClose?: boolean;
  toggleParentOpen?: boolean;
  onToggleProperty: string;
  initial?: Record<string, boolean>;
  firstOpen?: boolean;
  ariaNavigation?: boolean;
  toggleHook?: (id: string) => void;
}

export interface UseTogglableNavReturn {
  registerButton: (el: string) => any;
  registerLink: (el: string) => any;
  registerNavItem: (item: any) => any;
  registered: { current: Record<string, { el: HTMLElement; type: string }> };
  opened: Record<string, boolean>;
  toggle: (id: string) => void;
  setOpened: (
    opened:
      | ((curOpened: Record<string, boolean>) => Record<string, boolean>)
      | Record<string, boolean>
  ) => void;
}

export function useTogglableNav({
  toggleProperty,
  toggleOpenClose,
  toggleParentOpen,
  onToggleProperty,
  initial,
  ariaNavigation,
  toggleHook,
}: UseTogglableNavInterface): UseTogglableNavReturn {
  const [opened, setOpened] = useState(initial || {});
  const registered = useRef<Record<string, { el: HTMLElement; type: string }>>(
    {}
  );
  const openedKeys = useRef<string[]>([]);

  const toggle = useCallback((e) => {
    const key = e.currentTarget.getAttribute('aria-controls') as string;
    const selected = ((e.currentTarget.getAttribute(
      'aria-selected'
    ) as string) === 'true') as boolean;
    if (toggleHook) {
      toggleHook(key);
    }
    e.preventDefault();
    if (toggleOpenClose) {
      if (toggleParentOpen) {
        const items = key.split('__');
        const currentKey = items.slice(0, items.length).join('__');
        const totalKeys = items.map((_, index) => {
          return items.slice(0, index + 1).join('__');
        });

        if (!selected && !openedKeys.current.includes(currentKey)) {
          openedKeys.current.push(currentKey);
        }
        if (selected && openedKeys.current.includes(currentKey)) {
          openedKeys.current = openedKeys.current.filter(
            (k) => k !== currentKey
          );
        }

        setOpened(() => ({
          ...totalKeys.reduce((acc, cur) => {
            acc[cur] = cur === currentKey ? !selected : true;
            return acc;
          }, {}),
        }));
      } else {
        setOpened({ [key]: !selected });
      }
    } else {
      setOpened({ [key]: true });
    }
  }, []);

  const getMenuItems = (element) => {
    if (element) {
      if (element?.role === 'menuitem') {
        return element;
      } else {
        const childrenArray = Array.from(element?.children);
        const menuItemsArray: any[] = [];
        childrenArray.forEach((child) => {
          const menuItem = getMenuItems(child);
          if (menuItem && !menuItemsArray.includes(menuItem)) {
            menuItemsArray.push(menuItem);
          }
        });
        return menuItemsArray.flatMap((x) => x);
      }
    }
    return null;
  };

  const findChildren = (parent, items) => {
    return items
      ? items.filter(
          (child) => child.slice(0, child.lastIndexOf('__')) === parent
        )
      : items;
  };

  const findParent = (child, items) => {
    const parent = child.slice(0, child.lastIndexOf('__'));
    return items.find((item) => item === parent) ? parent : null;
  };

  const findSiblings = (key, items) => {
    const parent = key.slice(0, key.lastIndexOf('__'));
    if (key.lastIndexOf('__') > 0) {
      return items.filter(
        (child) => child.slice(0, child.lastIndexOf('__')) === parent
      );
    }
    return items.filter((child) => !child.includes('__'));
  };

  const findLeftSibling = (key, items) => {
    const siblings = findSiblings(key, items);
    if (siblings.length > 0) {
      const keyIndex = siblings.findIndex((item) => item === key);
      return keyIndex > 0 ? siblings[keyIndex - 1] : null;
    }
    return null;
  };

  const findRightSibling = (key, items) => {
    const siblings = findSiblings(key, items);
    if (siblings.length > 0) {
      const keyIndex = siblings.findIndex((item) => item === key);
      return keyIndex < siblings.length - 1 ? siblings[keyIndex + 1] : null;
    }
    return null;
  };

  const getOpenItemChildren = (currentItem, items) => {
    const itemChildrenArray: any[] = [];
    const itemChildren = findChildren(currentItem, items);
    itemChildren.forEach((item) => {
      if (openedKeys.current.includes(item)) {
        itemChildrenArray.push(item);
        const internChildren = getOpenItemChildren(item, items);
        if (internChildren.length > 0) {
          itemChildrenArray.push(internChildren);
        }
      } else {
        itemChildrenArray.push(item);
      }
    });
    return itemChildrenArray.flatMap((x) => x);
  };

  const onKeyDown = useCallback((e) => {
    const orientation = document
      .querySelector(`[aria-orientation]`)
      ?.getAttribute('aria-orientation');
    const key = e.target.getAttribute('aria-controls');
    const keys = Object.entries(registered.current).map(
      ([entryKey]) => entryKey
    );

    const handleArrowRight = () => {
      const arrowRightItem = findRightSibling(key, keys);
      if (arrowRightItem) {
        registered.current[arrowRightItem].el.focus();
      }
    };

    const handleArrowLeft = () => {
      const arrowLeftItem = findLeftSibling(key, keys);
      if (arrowLeftItem) {
        registered.current[arrowLeftItem].el.focus();
      }
    };

    switch (e.key) {
      case 'ArrowRight':
        handleArrowRight();
        break;
      case 'ArrowDown':
        const openItem = getOpenItemChildren(key, keys);
        const arrowDownItems = findChildren(key, openItem);
        const menuItems = getMenuItems(document.getElementById(key));

        if (arrowDownItems.length > 0) {
          registered.current[arrowDownItems[0]].el.focus();
        } else {
          if (orientation === 'vertical') {
            if (
              document.activeElement?.getAttribute('aria-selected') ===
                'true' &&
              menuItems &&
              menuItems.length > 0
            ) {
              menuItems[0].focus();
            } else {
              handleArrowRight();
            }
          } else {
            if (menuItems && menuItems.length > 0) {
              menuItems[0].focus();
            }
          }
        }

        break;
      case 'ArrowLeft':
        handleArrowLeft();
        break;
      case 'ArrowUp':
        if (orientation === 'vertical') {
          handleArrowLeft();
        } else {
          const arrowUpItem = findParent(key, keys);
          if (arrowUpItem) {
            registered.current[arrowUpItem].el.focus();
          }
        }
        break;
      case ' ':
      case 'Enter':
        if (registered.current[key].type === 'link') {
          registered.current[key].el.click();
        } else {
          toggle(e);
        }
        break;
    }
  }, []);
  const registerButtonRef = useCallback((el) => {
    if (!el) return;
    const key = el.getAttribute('aria-controls') as string;
    if (registered.current[key]) return;
    registered.current[key] = {
      el,
      type: 'button',
    };
  }, []);

  const registerLinkRef = useCallback((el) => {
    if (!el) return;
    const key = el.getAttribute('aria-controls') as string;
    if (registered.current[key]) return;
    registered.current[key] = {
      el,
      type: 'link',
    };
  }, []);
  const registerButton = useCallback(
    (key) => {
      const isOpen = !!opened[key];
      return {
        ref: registerButtonRef,
        [toggleProperty]: isOpen,
        arrow: true,
        [onToggleProperty]: toggle,
        'aria-controls': `${key}`,
        ...(ariaNavigation && { onKeyDown }),
      };
    },
    [opened]
  );

  const registerLink = useCallback(
    (key) => {
      return {
        ref: registerLinkRef,
        'aria-controls': `${key}`,
        ...(ariaNavigation && { onKeyDown }),
      };
    },
    [opened]
  );
  const registerNavItem = useCallback(
    (item) => {
      if (item.subMenu || item.menu) {
        return { ...registerButton(item.name), role: 'button' };
      } else if (item.href) {
        return { ...registerLink(item.name), href: item.href };
      } else {
        console.error(item);
        throw new Error('Invalid nav item. Must be either button or link');
      }
    },
    [opened]
  );
  return useMemo(
    () => ({
      registerNavItem,
      registerButton,
      registerLink,
      registered,
      opened,
      toggle,
      setOpened,
    }),
    [opened]
  );
}
