import React, {
  useState,
  useEffect,
  useRef,
  forwardRef,
  useMemo,
  useCallback,
  createElement,
} from "react";
import {
  VariableSizeList,
  ListOnScrollProps,
  ListChildComponentProps,
} from "react-window";
import { ListProps, List, ListItemProps } from "./List";
import { DropdownBox } from "../dropdown";
import { RocketProps } from "../_util/create-rocket";
import { EmptyTip } from "../tips";
import { mergeRefs } from "../_util/merge-refs";
import { StyledProps } from "../_type";
import { noop } from "../_util/noop";
import { useListScroll } from "../_util/use-list-scroll";
import { useIsomorphicLayoutEffect } from "../_util/use-isomorphic-layout-effect";

export interface VirtualizedListProps
  extends Omit<ListProps, "onScrollBottom" | "children"> {
  /**
   * 列表项
   */
  items: VirtualizedListItem[];

  /**
   * 列表高
   */
  listHeight?: number;

  /**
   * 滚动到底部事件
   */
  onScrollBottom?: (props: ListOnScrollProps) => void;

  /**
   * 初始偏移
   */
  initialScrollOffsetIndex?: number;

  /**
   * 虚拟滚动组件 ref
   */
  virtualizedRef?: React.Ref<VariableSizeList>;

  /**
   * 用于参照的列表容器
   * @default DropdownBox
   */
  container?:
    | React.FunctionComponent<StyledProps>
    | React.ComponentClass<StyledProps>
    | string;

  /**
   * 列表容器样式
   */
  containerStyle?: React.CSSProperties;

  /**
   * 列表容器类名
   */
  containerClassName?: string;

  /**
   * 触发虚拟滚动刷新的变量依赖列表
   * @since 2.7.7
   * @default []
   */
  updateDeps?: any[];
}

function Row({ data, index, style }: ListChildComponentProps) {
  const { type, text, props = {} } = data[index];
  if (type === "tips") {
    return (
      <VStatusTip {...props} style={style}>
        {text}
      </VStatusTip>
    );
  }
  if (type === "group") {
    return (
      <VGroupLabel {...props} style={style}>
        {text}
      </VGroupLabel>
    );
  }
  if (type === "divider") {
    return <VDivider {...props} style={style} />;
  }
  return (
    <VItem
      {...props}
      style={style}
      title={!props.tooltip && typeof text === "string" ? text : undefined}
    >
      {text}
    </VItem>
  );
}

export const VirtualizedList = forwardRef(function VirtualizedList(
  { virtual = true, ...props }: VirtualizedListProps & { virtual?: boolean },
  ref: React.Ref<HTMLUListElement>
) {
  if (virtual) {
    return <VList {...props} ref={ref} />;
  }
  return <NormalList {...props} ref={ref} />;
});

VirtualizedList.displayName = "VirtualizedList";

const VList = forwardRef(function VList(
  {
    items = [],
    onScrollBottom,
    virtualizedRef,
    listHeight,
    // ListProps
    type,
    split,
    className,
    style,
    container,
    containerStyle,
    containerClassName,
    initialScrollOffsetIndex,
    updateDeps = [],
  }: VirtualizedListProps,
  ref: React.Ref<HTMLUListElement>
) {
  const vRef = useRef<VariableSizeList>(null);
  const listRef = useRef<HTMLDivElement>(null);
  const [tipsSize, setTipsSize] = useState<number>(undefined);
  const [itemSize, setItemSize] = useState<number>(undefined);
  const [groupSize, setGroupSize] = useState<number>(undefined);
  const [dividerSize, setDividerSize] = useState<number>(undefined);
  const [listSize, setListSize] = useState<number>(listHeight);

  const innerElementType = useMemo(
    () =>
      forwardRef(
        (
          { style: innerStyle = {}, ...innerProps }: ListProps,
          ref: React.Ref<HTMLUListElement>
        ) => (
          <List
            ref={ref}
            type={type}
            split={split}
            className={className}
            {...innerProps}
            style={{
              ...style,
              ...innerStyle,
              maxHeight: "none",
              overflowY: "hidden",
            }}
          />
        )
      ),
    [type, split, className, style]
  );

  const getItemSize = useCallback(
    (index: number) => {
      switch (items[index].type) {
        case "tips":
          return tipsSize || itemSize;
        case "group":
          return groupSize || itemSize;
        case "divider":
          return dividerSize || 9;
        default:
          return itemSize;
      }
    },
    [dividerSize, groupSize, itemSize, items, tipsSize]
  );

  // 强刷
  useIsomorphicLayoutEffect(
    () => () => {
      if (updateDeps?.length && vRef.current?.resetAfterIndex) {
        items.forEach((_, i) => vRef.current.resetAfterIndex(i));
      }
    },
    updateDeps
  );

  // VariableSizeList 默认按 index 缓存 style，需手动触发重置
  useEffect(() => {
    if (vRef.current) {
      items.forEach((_, i) => vRef.current.resetAfterIndex(i, false));
    }
  }, [groupSize, itemSize, items, tipsSize]);

  const itemsSize = useMemo(
    () => items.reduce((p, _, i) => p + getItemSize(i), 0),
    [getItemSize, items]
  );

  function handleScroll(props: ListOnScrollProps) {
    if (itemsSize > listSize && listRef.current && onScrollBottom) {
      const { scrollHeight, scrollTop, clientHeight } = listRef.current;
      if (scrollHeight <= Math.ceil(clientHeight + scrollTop + 1)) {
        onScrollBottom(props);
      }
    }
  }

  const height = Math.min(itemsSize, listSize);

  const initialScrollOffset = useMemo(() => {
    if (!initialScrollOffsetIndex || initialScrollOffsetIndex < 0) {
      return 0;
    }
    let offset = 0;
    for (let i = 0; i < initialScrollOffsetIndex; ++i) {
      offset += getItemSize(i);
    }
    // react-window 在 offset < height 时出现可能出现渲缺失
    return offset + getItemSize(initialScrollOffsetIndex) > height ? offset : 0;
  }, [getItemSize, height, initialScrollOffsetIndex]);

  const option = items.find(i => i.type === "option")?.text;
  const tips = items.find(i => i.type === "tips")?.text;
  const group = items.find(i => i.type === "group")?.text;

  return (
    <>
      {[itemSize, listSize].every(s => typeof s !== "undefined") && (
        <VariableSizeList
          ref={mergeRefs(virtualizedRef, vRef)}
          innerRef={ref}
          outerRef={listRef}
          height={height}
          width="100%"
          itemCount={items.length}
          itemSize={getItemSize}
          innerElementType={innerElementType}
          onScroll={handleScroll}
          itemKey={index => items[index].key}
          itemData={items}
          initialScrollOffset={initialScrollOffset}
        >
          {Row}
        </VariableSizeList>
      )}
      <MirrorList
        container={container}
        containerStyle={containerStyle}
        containerClassName={containerClassName}
        onItemSizeChange={setItemSize}
        onListSizeChange={listHeight ? noop : setListSize}
        onTipsSizeChange={setTipsSize}
        onGroupSizeChange={setGroupSize}
        onDividerSizeChange={setDividerSize}
        tipsReference={tips}
        itemReference={option}
        groupReference={group}
        type={type}
        split={split}
        className={className}
        style={style}
      />
    </>
  );
});

VList.displayName = "List";

function MirrorList({
  itemReference,
  tipsReference,
  groupReference,
  onTipsSizeChange,
  onItemSizeChange,
  onGroupSizeChange,
  onDividerSizeChange,
  onListSizeChange,
  container = DropdownBox,
  containerStyle = {},
  containerClassName,
  ...props
}: ListProps & {
  container: VirtualizedListProps["container"];
  containerClassName: VirtualizedListProps["containerClassName"];
  containerStyle: VirtualizedListProps["containerStyle"];
  tipsReference?: React.ReactNode;
  itemReference?: React.ReactNode;
  groupReference?: React.ReactNode;
  onTipsSizeChange: (size: number) => void;
  onItemSizeChange: (size: number) => void;
  onGroupSizeChange: (size: number) => void;
  onDividerSizeChange: (size: number) => void;
  onListSizeChange: (size: number) => void;
}) {
  const tipsRef = useRef<HTMLUListElement>(null);
  const listRef = useRef<HTMLUListElement>(null);
  const groupRef = useRef<HTMLUListElement>(null);
  const itemRef = useRef<HTMLLIElement>(null);
  const dividerRef = useRef<HTMLLIElement>(null);

  useEffect(() => {
    if (itemRef.current) {
      onItemSizeChange(itemRef.current.clientHeight);
    }
    // tipsRef / groupRef 存在时才取高度
    if (tipsRef.current && tipsReference) {
      onTipsSizeChange(tipsRef.current.clientHeight);
    }
    if (groupRef.current && groupReference) {
      onGroupSizeChange(groupRef.current.clientHeight);
    }
    if (dividerRef.current) {
      onDividerSizeChange(dividerRef.current.clientHeight);
    }
    if (listRef.current) {
      onListSizeChange(listRef.current.clientHeight);
    }
  }, [
    tipsReference,
    itemReference,
    groupReference,
    onTipsSizeChange,
    onItemSizeChange,
    onGroupSizeChange,
    onListSizeChange,
    onDividerSizeChange,
  ]);

  return createElement(
    container,
    {
      style: {
        ...containerStyle,
        position: "absolute",
        top: -9999,
        left: 0,
      },
      className: containerClassName,
    },
    <List {...props} ref={listRef}>
      <List.StatusTip ref={tipsRef}>
        {tipsReference || <EmptyTip />}
      </List.StatusTip>
      <List.GroupLabel ref={groupRef}>
        {groupReference || "MirrorGroup"}
      </List.GroupLabel>
      <List.Item ref={itemRef}>{itemReference || "MirrorItem"}</List.Item>
      <List.Divider ref={dividerRef} />
      <li style={{ height: 3000 }}>Holder</li>
    </List>
  );
}

export type VirtualizedListItemType = "tips" | "option" | "group" | "divider";

/**
 * 虚拟滚动列表项
 */
export interface VirtualizedListItem<T = any> {
  /**
   * 列表项类型
   */
  type: VirtualizedListItemType;

  /**
   * 列表项标识
   */
  key: string;

  /**
   * 列表项展示内容
   */
  text: React.ReactNode;

  /**
   * 列表项附加内容
   */
  props?: ListItemProps;

  /**
   * 列表项属性
   */
  option?: T;
}

function VItem({ style = {}, ...props }: ListItemProps) {
  return (
    <List.Item
      {...props}
      style={{
        ...style,
        boxSizing: "border-box",
      }}
    />
  );
}

function VGroupLabel({ style = {}, ...props }: RocketProps) {
  return (
    <List.GroupLabel
      {...props}
      style={{
        ...style,
        boxSizing: "border-box",
      }}
    />
  );
}

function VStatusTip({ style = {}, ...props }: RocketProps) {
  return (
    <List.StatusTip
      {...props}
      style={{
        ...style,
        boxSizing: "border-box",
      }}
    />
  );
}

function VDivider({ style = {}, ...props }: RocketProps) {
  return (
    <List.Divider
      {...props}
      style={{
        ...style,
        boxSizing: "border-box",
      }}
    />
  );
}

/**
 * 兼容虚拟滚动 List 接口的普通 List
 */
const NormalList = forwardRef(function NormalList(
  {
    items,
    type,
    split,
    onScrollBottom,
    listHeight,
    style = {},
    className,
  }: VirtualizedListProps,
  ref: React.Ref<HTMLUListElement>
) {
  const listRef = useRef<HTMLUListElement>(null);
  const scrollTo = useListScroll(listRef);

  useEffect(() => {
    const currentIndex = items.findIndex(item => item.props?.current);
    scrollTo(currentIndex);
  }, [items, scrollTo]);

  // 普通 List
  return (
    <List
      ref={mergeRefs(ref, listRef)}
      type={type}
      split={split}
      style={listHeight ? { maxHeight: listHeight, ...style } : style}
      onScrollBottom={onScrollBottom as any}
      className={className}
    >
      {items.map(item => {
        switch (item.type) {
          case "tips":
            return (
              <List.StatusTip {...(item.props || {})} key={item.key}>
                {item.text}
              </List.StatusTip>
            );
          case "group":
            return (
              <List.GroupLabel {...(item.props || {})} key={item.key}>
                {item.text}
              </List.GroupLabel>
            );
          case "divider":
            return <List.Divider {...(item.props || {})} key={item.key} />;
        }
        return (
          <List.Item {...(item.props || {})} key={item.key}>
            {item.text}
          </List.Item>
        );
      })}
    </List>
  );
});

NormalList.displayName = "List";
