import { Children, cloneElement, useEffect, useRef, useLayoutEffect, isValidElement } from 'react';
import { ClickAwayListener, type SxProps } from '@mui/material';
import clsx from 'clsx';
import {
  MoreHoriz as MoreHorizIcon,
  ExpandMore as ExpandMoreIcon,
  Menu as MenuIcon,
  ArrowForward as ArrowForwardIcon,
} from '@mui/icons-material';
import { useCreation, useMemoizedFn, useReactive, useSize, useThrottleFn } from 'ahooks';
import noop from 'lodash/noop';
import { NavMenuProvider, useNavMenuContext } from './nav-menu-context';
import { NavMenuRoot, NavMenuItem, NavMenuSub, NavMenuSubList, NavMenuStyled } from './style';
import { SubContainer } from './sub-container';
import { useTheme } from '../Theme';

// 过滤 children 中的 Item/Sub, 忽略其它类型的 element
function filterItems(children: React.ReactNode) {
  if (children) {
    // eslint-disable-next-line no-use-before-define
    return Children.toArray(children).filter((child) => {
      if (!isValidElement(child)) {
        return false;
      }
      return child.type === Item || child.type === Sub;
    }) as React.ReactElement<any>[];
  }
  return null;
}

function useUniqueId(id?: string) {
  const _id = useRef(id || `navmenu-item-id-${Math.random().toString(36).slice(2)}`);
  return _id.current;
}

export type ItemOptions = {
  id?: string;
  icon?: React.ReactNode;
  label?: React.ReactNode;
  description?: React.ReactNode;
  active?: boolean;
  variant?: 'default' | 'panel';
  children?: ItemOptions[];
};

export interface NavMenuProps extends React.HTMLAttributes<HTMLElement> {
  items?: ItemOptions[];
  mode?: 'horizontal' | 'vertical' | 'inline';
  children?: React.ReactNode;
  activeId?: string | null;
  textColor?: string;
  activeTextColor?: string;
  bgColor?: string;
  onSelected?: (id: string) => void;
  sx?: SxProps;
}

/**
 * NavMenu, 导航组件, 可用于 header/footer/sidebar
 */
function NavMenu({
  items = [],
  mode = 'horizontal',
  children: _childs = null,
  activeId = null,
  // eslint-disable-next-line react/require-default-props
  textColor,
  // eslint-disable-next-line react/require-default-props
  activeTextColor,
  // eslint-disable-next-line react/require-default-props
  bgColor,
  onSelected = undefined,
  ...rest
}: NavMenuProps) {
  const theme = useTheme();
  const $textColor = useCreation(() => theme.palette.text.secondary, [theme]);
  const $activeTextColor = useCreation(() => theme.palette.text.primary, [theme]);
  const $bgColor = useCreation(() => theme.palette.background.default, [theme]);
  // eslint-disable-next-line no-param-reassign
  const children = filterItems(_childs);
  if (!items?.length && !children?.length) {
    throw new Error("One of 'items' or 'children' is required by NavMenu component.");
  }
  const currentState = useReactive({
    activeId,
    openedIds: [] as string[],
    hiddenItemCount: 0,
  });

  const activate = useMemoizedFn((id) => {
    currentState.activeId = id;
    onSelected?.(id);
  });
  const open = useMemoizedFn((id: string) => {
    currentState.openedIds.push(id);
  });
  const close = useMemoizedFn((id) => {
    currentState.openedIds = currentState.openedIds.filter((item) => item !== id);
  });

  const contextValue = useCreation(() => {
    return {
      ...currentState,
      mode,
      textColor: $textColor,
      activeTextColor: $activeTextColor,
      bgColor: $bgColor,
      activate,
      open,
      close,
    };
  }, [
    currentState.activeId,
    currentState.hiddenItemCount,
    currentState.openedIds,
    mode,
    $textColor,
    $activeTextColor,
    $bgColor,
    activate,
    open,
    close,
  ]);

  const navMenuRef = useRef<HTMLUListElement | null>(null);
  const itemRefs = useRef<HTMLElement[]>([]);
  const moreIconRef = useRef<HTMLLIElement | null>(null);
  const containerWidth = useRef(0);
  const isAllItemsHidden = currentState.hiddenItemCount === itemRefs.current?.length;
  const style = isAllItemsHidden ? { marginLeft: '0px' } : undefined;

  const icon = useCreation(() => {
    return isAllItemsHidden ? <MenuIcon /> : <MoreHorizIcon />;
  }, [isAllItemsHidden]);

  const renderChildrenWithRef = (childrenElement: React.ReactElement<any>[]) => {
    return Children.map(childrenElement, (child, index) => {
      return cloneElement(child, {
        ref: (el: HTMLElement) => {
          itemRefs.current[index] = el;
        },
      });
    });
  };

  const containerSize = useSize(navMenuRef.current);

  const { run: checkItemsFit } = useThrottleFn(
    useMemoizedFn(() => {
      let totalWidthUsed = 0;
      let newHiddenCount = 0;
      let leftAllHidden = false;
      const moreIconWidth = moreIconRef.current
        ? moreIconRef.current.offsetWidth + parseFloat(window.getComputedStyle(moreIconRef.current).marginLeft)
        : 0;

      itemRefs.current.forEach((item, index) => {
        if (item) {
          item.style.display = 'flex';
          const marginLeft = index > 0 ? parseFloat(window.getComputedStyle(item).marginLeft) : 0;
          const currentItemWidth = item.offsetWidth + marginLeft;

          if (containerWidth.current - moreIconWidth >= totalWidthUsed + currentItemWidth && !leftAllHidden) {
            totalWidthUsed += currentItemWidth;
          } else {
            item.style.display = 'none';
            leftAllHidden = true;
            newHiddenCount++;
          }
        }
      });

      if (newHiddenCount !== currentState.hiddenItemCount) {
        currentState.hiddenItemCount = newHiddenCount;
      }
    }),
    { wait: 100 }
  );

  useLayoutEffect(() => {
    containerWidth.current = containerSize?.width || navMenuRef.current?.clientWidth || 0;
    if (mode === 'horizontal') {
      checkItemsFit();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [containerSize?.width, mode]);

  useEffect(() => {
    if (mode === 'horizontal') {
      checkItemsFit();
      window.addEventListener('resize', checkItemsFit);

      return () => {
        window.removeEventListener('resize', checkItemsFit);
      };
    }
    return undefined;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mode, items]);

  useEffect(() => {
    // NavMenu#activeId 和 Item#active prop 都可以用来控制激活状态 (一般不会混用这两种方式)
    // 如果未传入 NavMenu#activeId, 应该避免设置一个空值的 activeId 状态 (会与 Item#active 冲突)
    if (activeId !== undefined && activeId !== null) {
      currentState.activeId = activeId;
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeId]);

  const classes = clsx('navmenu', `navmenu--${mode}`, rest.className);

  const renderItem = (item: ItemOptions, index: number, level = 0) => {
    const isTopLevel = level === 0;

    if (item?.children) {
      // 只渲染两级子菜单
      if (level > 0) {
        return null;
      }

      // 对于 Sub 组件，如果它是顶级组件，则包含 ref
      return (
        <Sub
          key={item.id}
          id={item.id}
          icon={item.icon}
          label={item.label}
          variant={isTopLevel ? 'default' : 'panel'}
          ref={
            isTopLevel
              ? (el: HTMLElement) => {
                  itemRefs.current[index] = el!;
                }
              : undefined
          }>
          {typeof item.children === 'function'
            ? item.children
            : item.children.map((childItem, childIndex: number) =>
                renderItem({ ...childItem, variant: 'panel' }, childIndex, level + 1)
              )}
        </Sub>
      );
    }

    // 顶级 Item 组件总是包含 ref
    return (
      <Item
        key={item.id}
        id={item.id}
        icon={item.icon}
        label={item.label}
        description={item.description}
        active={item.active}
        variant={item.variant}
        ref={
          isTopLevel
            ? (el: HTMLElement) => {
                itemRefs.current[index] = el!;
              }
            : undefined
        }
      />
    );
  };

  const content = items
    ? items?.slice(-currentState.hiddenItemCount).map((item, index) => renderItem(item, index, 1))
    : children?.slice(-currentState.hiddenItemCount);

  // 当前展开的子菜单
  // const openedId = currentState.openedIds[0];

  return (
    <NavMenuProvider value={contextValue}>
      <NavMenuStyled {...rest} className={classes} $textColor={$textColor} $bgColor={$bgColor}>
        <NavMenuRoot className={clsx('navmenu-root', `navmenu-root--${mode}`)} ref={navMenuRef}>
          {items ? items.map((item, index) => renderItem(item, index)) : renderChildrenWithRef(children || [])}
          {currentState.hiddenItemCount > 0 && (
            <Sub expandIcon={false} icon={icon} label="" ref={moreIconRef} style={style}>
              {content}
            </Sub>
          )}
        </NavMenuRoot>
      </NavMenuStyled>
    </NavMenuProvider>
  );
}

export interface ItemProps extends React.HTMLAttributes<HTMLLIElement> {
  id?: string;
  icon?: React.ReactNode;
  label?: React.ReactNode;
  description?: React.ReactNode;
  active?: boolean;
  variant?: 'default' | 'panel';
  onClick?: () => void;
}

export function Item({
  ref = undefined,
  id: _id = '',
  icon = null,
  label = '',
  description = undefined,
  active = false,
  variant = 'default',
  onClick = noop,
  ...rest
}: ItemProps & {
  ref?: React.Ref<unknown>;
}) {
  const id = useUniqueId(_id);
  const { mode, activeId, activate, activeTextColor } = useNavMenuContext();
  const classes = clsx(
    'navmenu-item',
    { 'navmenu-item--active': activeId === id },
    `navmenu-item--${mode}`,
    `navmenu-item--${variant}`,
    rest.className
  );
  useEffect(() => {
    if (active) {
      activate?.(id);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [active]);

  const handleClick = () => {
    onClick?.();
    activate?.(id);
  };

  return (
    // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
    <NavMenuItem
      {...rest}
      className={classes}
      ref={ref as React.Ref<HTMLLIElement>}
      $activeTextColor={activeTextColor}
      onClick={handleClick}>
      {icon && <span className="navmenu-item__icon">{icon}</span>}
      <div className="navmenu-item__content">
        <span className="navmenu-item__label">
          {label} <ArrowForwardIcon className="navmenu-item__label-arrow" />
        </span>
        {description && <span className="navmenu-item__desc">{description}</span>}
      </div>
    </NavMenuItem>
  );
}
Item.displayName = 'NavMenu.Item';

export interface SubProps extends Omit<ItemProps, 'children' | 'active'> {
  children?: Array<React.ReactElement<any> | null> | ((props: { isOpen: boolean }) => React.ReactElement<any> | null);
  expandIcon?: React.ReactNode | ((props: { isOpen: boolean }) => React.ReactNode);
}

export function Sub({
  ref = undefined,
  id: _id = '',
  icon = null,
  label = null,
  description,
  variant = 'default',
  children = undefined,

  expandIcon = ({ isOpen }) => (
    <ExpandMoreIcon
      style={{
        transform: `rotate(${isOpen ? 180 : 0}deg)`,
      }}
    />
  ),

  ...rest
}: SubProps & {
  ref?: React.Ref<unknown>;
}) {
  const id = useUniqueId(_id);
  const { openedIds, open, close, mode, activeTextColor } = useNavMenuContext();
  const isOpen = openedIds?.includes(id) ?? false;
  const isInlineMode = mode === 'inline';
  const classes = clsx(
    'navmenu-sub',
    'navmenu-item',
    { 'navmenu-item--panel': variant === 'panel' },
    { 'navmenu-sub--opened': isOpen, 'navmenu-item--inline': isInlineMode },
    rest.className
  );

  // 统一使用 click 事件控制收缩/伸展子菜单
  const props = {
    onClick: () => {
      if (openedIds?.includes(id)) {
        close?.(id);
      } else {
        open?.(id);
      }
    },
  };
  // inline mode, 避免点击子菜单项时触发父菜单的 open/close
  const containerProps = isInlineMode ? { onClick: (e: React.MouseEvent) => e.stopPropagation() } : {};
  const menu = (
    <NavMenuSub
      {...rest}
      className={classes}
      {...props}
      ref={ref as React.Ref<HTMLLIElement>}
      $activeTextColor={activeTextColor}>
      {icon && <span className="navmenu-item__icon">{icon}</span>}
      <div className="navmenu-item__content">
        <span className="navmenu-item__label">{label}</span>
        {description && <span className="navmenu-item__desc">{description}</span>}
      </div>
      {expandIcon && (
        <span className="navmenu-sub__expand-icon">
          {typeof expandIcon === 'function' ? expandIcon({ isOpen }) : expandIcon}
        </span>
      )}
      <SubContainer inline={isInlineMode} {...containerProps}>
        {typeof children === 'function' ? (
          children({ isOpen }) // 自定义渲染
        ) : (
          <NavMenuSubList className="navmenu-sub__list">{filterItems(children)}</NavMenuSubList>
        )}
      </SubContainer>
    </NavMenuSub>
  );

  if (isInlineMode) {
    return menu;
  }

  return <ClickAwayListener onClickAway={() => close?.(id)}>{menu}</ClickAwayListener>;
}
Sub.displayName = 'NavMenu.Sub';

NavMenu.Item = Item;
NavMenu.Sub = Sub;

export default NavMenu;
