import type { TreeDataNode, Key } from './interface';
import type { RefOptionListProps } from '../vc-select/OptionList';
import type { ScrollTo } from '../vc-virtual-list/List';
import { computed, defineComponent, nextTick, ref, shallowRef, toRaw, watch } from 'vue';
import useMemo from '../_util/hooks/useMemo';
import type { EventDataNode } from '../tree';
import KeyCode from '../_util/KeyCode';
import Tree from '../vc-tree/Tree';
import type { TreeProps } from '../vc-tree/props';
import { getAllKeys, isCheckDisabled } from './utils/valueUtil';
import { useBaseProps } from '../vc-select';
import useInjectLegacySelectContext from './LegacyContext';
import useInjectSelectContext from './TreeSelectContext';

const HIDDEN_STYLE = {
  width: 0,
  height: 0,
  display: 'flex',
  overflow: 'hidden',
  opacity: 0,
  border: 0,
  padding: 0,
  margin: 0,
};

interface TreeEventInfo {
  node: { key: Key };
  selected?: boolean;
  checked?: boolean;
}

type ReviseRefOptionListProps = Omit<RefOptionListProps, 'scrollTo'> & { scrollTo: ScrollTo };

export default defineComponent({
  compatConfig: { MODE: 3 },
  name: 'OptionList',
  inheritAttrs: false,
  slots: ['notFoundContent', 'menuItemSelectedIcon'],
  setup(_, { slots, expose }) {
    const baseProps = useBaseProps();
    const legacyContext = useInjectLegacySelectContext();
    const context = useInjectSelectContext();
    const treeRef = ref();
    const memoTreeData = useMemo(
      () => context.treeData,
      [() => baseProps.open, () => context.treeData],
      next => next[0],
    );

    const mergedCheckedKeys = computed(() => {
      const { checkable, halfCheckedKeys, checkedKeys } = legacyContext;
      if (!checkable) {
        return null;
      }

      return {
        checked: checkedKeys,
        halfChecked: halfCheckedKeys,
      };
    });

    watch(
      () => baseProps.open,
      () => {
        nextTick(() => {
          if (baseProps.open && !baseProps.multiple && legacyContext.checkedKeys.length) {
            treeRef.value?.scrollTo({ key: legacyContext.checkedKeys[0] });
          }
        });
      },
      { immediate: true, flush: 'post' },
    );

    // ========================== Search ==========================
    const lowerSearchValue = computed(() => String(baseProps.searchValue).toLowerCase());
    const filterTreeNode = (treeNode: EventDataNode) => {
      if (!lowerSearchValue.value) {
        return false;
      }
      return String(treeNode[legacyContext.treeNodeFilterProp])
        .toLowerCase()
        .includes(lowerSearchValue.value);
    };

    // =========================== Keys ===========================
    const expandedKeys = shallowRef<Key[]>(legacyContext.treeDefaultExpandedKeys);
    const searchExpandedKeys = shallowRef<Key[]>(null);

    watch(
      () => baseProps.searchValue,
      () => {
        if (baseProps.searchValue) {
          searchExpandedKeys.value = getAllKeys(toRaw(context.treeData), toRaw(context.fieldNames));
        }
      },
      {
        immediate: true,
      },
    );
    const mergedExpandedKeys = computed(() => {
      if (legacyContext.treeExpandedKeys) {
        return legacyContext.treeExpandedKeys.slice();
      }
      return baseProps.searchValue ? searchExpandedKeys.value : expandedKeys.value;
    });

    const onInternalExpand = (keys: Key[]) => {
      expandedKeys.value = keys;
      searchExpandedKeys.value = keys;

      legacyContext.onTreeExpand?.(keys);
    };

    // ========================== Events ==========================
    const onListMouseDown = (event: MouseEvent) => {
      event.preventDefault();
    };

    const onInternalSelect = (_: Key[], { node }: TreeEventInfo) => {
      const { checkable, checkedKeys } = legacyContext;
      if (checkable && isCheckDisabled(node)) {
        return;
      }
      context.onSelect?.(node.key, {
        selected: !checkedKeys.includes(node.key),
      });

      if (!baseProps.multiple) {
        baseProps.toggleOpen?.(false);
      }
    };

    // ========================= Keyboard =========================
    const activeKey = ref<Key>(null);
    const activeEntity = computed(() => legacyContext.keyEntities[activeKey.value]);

    const setActiveKey = (key: Key) => {
      activeKey.value = key;
    };
    expose({
      scrollTo: (...args: any[]) => treeRef.value?.scrollTo?.(...args),
      onKeydown: (event: KeyboardEvent) => {
        const { which } = event;
        switch (which) {
          // >>> Arrow keys
          case KeyCode.UP:
          case KeyCode.DOWN:
          case KeyCode.LEFT:
          case KeyCode.RIGHT:
            treeRef.value?.onKeydown(event);
            break;

          // >>> Select item
          case KeyCode.ENTER: {
            if (activeEntity.value) {
              const { selectable, value } = activeEntity.value.node || {};
              if (selectable !== false) {
                onInternalSelect(null, {
                  node: { key: activeKey.value },
                  selected: !legacyContext.checkedKeys.includes(value),
                });
              }
            }

            break;
          }

          // >>> Close
          case KeyCode.ESC: {
            baseProps.toggleOpen(false);
          }
        }
      },
      onKeyup: () => {},
    } as ReviseRefOptionListProps);

    return () => {
      const {
        prefixCls,
        multiple,
        searchValue,
        open,
        notFoundContent = slots.notFoundContent?.(),
      } = baseProps;
      const { listHeight, listItemHeight, virtual } = context;
      const {
        checkable,
        treeDefaultExpandAll,
        treeIcon,
        showTreeIcon,
        switcherIcon,
        treeLine,
        loadData,
        treeLoadedKeys,
        treeMotion,
        onTreeLoad,
        checkedKeys,
      } = legacyContext;
      // ========================== Render ==========================
      if (memoTreeData.value.length === 0) {
        return (
          <div role="listbox" class={`${prefixCls}-empty`} onMousedown={onListMouseDown}>
            {notFoundContent}
          </div>
        );
      }

      const treeProps: Partial<TreeProps> = {
        fieldNames: context.fieldNames,
      };
      if (treeLoadedKeys) {
        treeProps.loadedKeys = treeLoadedKeys;
      }
      if (mergedExpandedKeys.value) {
        treeProps.expandedKeys = mergedExpandedKeys.value;
      }
      return (
        <div onMousedown={onListMouseDown}>
          {activeEntity.value && open && (
            <span style={HIDDEN_STYLE} aria-live="assertive">
              {activeEntity.value.node.value}
            </span>
          )}

          <Tree
            ref={treeRef}
            focusable={false}
            prefixCls={`${prefixCls}-tree`}
            treeData={memoTreeData.value as TreeDataNode[]}
            height={listHeight}
            itemHeight={listItemHeight}
            virtual={virtual}
            multiple={multiple}
            icon={treeIcon}
            showIcon={showTreeIcon}
            switcherIcon={switcherIcon}
            showLine={treeLine}
            loadData={searchValue ? null : (loadData as any)}
            motion={treeMotion}
            activeKey={activeKey.value}
            // We handle keys by out instead tree self
            checkable={checkable}
            checkStrictly
            checkedKeys={mergedCheckedKeys.value}
            selectedKeys={!checkable ? checkedKeys : []}
            defaultExpandAll={treeDefaultExpandAll}
            {...treeProps}
            // Proxy event out
            onActiveChange={setActiveKey}
            onSelect={onInternalSelect}
            onCheck={onInternalSelect as any}
            onExpand={onInternalExpand}
            onLoad={onTreeLoad}
            filterTreeNode={filterTreeNode}
            v-slots={{ ...slots, checkable: legacyContext.customSlots.treeCheckable }}
          />
        </div>
      );
    };
  },
});
