import type { App, ExtractPropTypes, PropType } from 'vue';
import { computed, ref, watchEffect, defineComponent } from 'vue';
import VcTreeSelect, {
  TreeNode,
  SHOW_ALL,
  SHOW_PARENT,
  SHOW_CHILD,
  treeSelectProps as vcTreeSelectProps,
} from '../vc-tree-select';
import classNames from '../_util/classNames';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import type { SizeType } from '../config-provider';
import type { FieldNames, Key } from '../vc-tree-select/interface';
import omit from '../_util/omit';
import PropTypes from '../_util/vue-types';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import devWarning from '../vc-util/devWarning';
import getIcons from '../select/utils/iconUtil';
import type { SwitcherIconProps } from '../tree/utils/iconUtil';
import renderSwitcherIcon from '../tree/utils/iconUtil';
import { warning } from '../vc-util/warning';
import { flattenChildren } from '../_util/props-util';
import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext';
import type { BaseSelectRef } from '../vc-select';
import type { BaseOptionType, DefaultOptionType } from '../vc-tree-select/TreeSelect';
import type { TreeProps } from '../tree';
import type { SelectCommonPlacement } from '../_util/transition';
import { getTransitionDirection } from '../_util/transition';
import type { InputStatus } from '../_util/statusUtils';
import { getStatusClassNames, getMergedStatus } from '../_util/statusUtils';

const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
  if (transitionName !== undefined) {
    return transitionName;
  }
  return `${rootPrefixCls}-${motion}`;
};

type RawValue = string | number;

export interface LabeledValue {
  key?: string;
  value: RawValue;
  label?: any;
}

export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[];

export type RefTreeSelectProps = BaseSelectRef;

export function treeSelectProps<
  ValueType = any,
  OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
>() {
  return {
    ...omit(vcTreeSelectProps<ValueType, OptionType>(), [
      'showTreeIcon',
      'treeMotion',
      'inputIcon',
      'getInputElement',
      'treeLine',
      'customSlots',
    ]),
    suffixIcon: PropTypes.any,
    size: { type: String as PropType<SizeType> },
    bordered: { type: Boolean, default: undefined },
    treeLine: { type: [Boolean, Object] as PropType<TreeProps['showLine']>, default: undefined },
    replaceFields: { type: Object as PropType<FieldNames> },
    placement: String as PropType<SelectCommonPlacement>,
    status: String as PropType<InputStatus>,
    'onUpdate:value': { type: Function as PropType<(value: any) => void> },
    'onUpdate:treeExpandedKeys': { type: Function as PropType<(keys: Key[]) => void> },
    'onUpdate:searchValue': { type: Function as PropType<(value: string) => void> },
  };
}
export type TreeSelectProps = Partial<ExtractPropTypes<ReturnType<typeof treeSelectProps>>>;

const TreeSelect = defineComponent({
  compatConfig: { MODE: 3 },
  name: 'ATreeSelect',
  inheritAttrs: false,
  props: initDefaultProps(treeSelectProps(), {
    choiceTransitionName: '',
    listHeight: 256,
    treeIcon: false,
    listItemHeight: 26,
    bordered: true,
  }),
  slots: [
    'title',
    'titleRender',
    'placeholder',
    'maxTagPlaceholder',
    'treeIcon',
    'switcherIcon',
    'notFoundContent',
  ],
  setup(props, { attrs, slots, expose, emit }) {
    warning(
      !(props.treeData === undefined && slots.default),
      '`children` of TreeSelect is deprecated. Please use `treeData` instead.',
    );
    watchEffect(() => {
      devWarning(
        props.multiple !== false || !props.treeCheckable,
        'TreeSelect',
        '`multiple` will always be `true` when `treeCheckable` is true',
      );
      devWarning(
        props.replaceFields === undefined,
        'TreeSelect',
        '`replaceFields` is deprecated, please use fieldNames instead',
      );
    });

    const formItemContext = useInjectFormItemContext();
    const formItemInputContext = FormItemInputContext.useInject();
    const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status));
    const {
      prefixCls,
      renderEmpty,
      direction,
      virtual,
      dropdownMatchSelectWidth,
      size,
      getPopupContainer,
      getPrefixCls,
    } = useConfigInject('select', props);
    const rootPrefixCls = computed(() => getPrefixCls());
    // ===================== Placement =====================
    const placement = computed(() => {
      if (props.placement !== undefined) {
        return props.placement;
      }
      return direction.value === 'rtl'
        ? ('bottomRight' as SelectCommonPlacement)
        : ('bottomLeft' as SelectCommonPlacement);
    });
    const transitionName = computed(() =>
      getTransitionName(
        rootPrefixCls.value,
        getTransitionDirection(placement.value),
        props.transitionName,
      ),
    );
    const choiceTransitionName = computed(() =>
      getTransitionName(rootPrefixCls.value, '', props.choiceTransitionName),
    );
    const treePrefixCls = computed(() => getPrefixCls('select-tree', props.prefixCls));
    const treeSelectPrefixCls = computed(() => getPrefixCls('tree-select', props.prefixCls));

    const mergedDropdownClassName = computed(() =>
      classNames(props.dropdownClassName, `${treeSelectPrefixCls.value}-dropdown`, {
        [`${treeSelectPrefixCls.value}-dropdown-rtl`]: direction.value === 'rtl',
      }),
    );

    const isMultiple = computed(() => !!(props.treeCheckable || props.multiple));
    const mergedShowArrow = computed(() =>
      props.showArrow !== undefined ? props.showArrow : props.loading || !isMultiple.value,
    );

    const treeSelectRef = ref();
    expose({
      focus() {
        treeSelectRef.value.focus?.();
      },

      blur() {
        treeSelectRef.value.blur?.();
      },
    });

    const handleChange: TreeSelectProps['onChange'] = (...args: any[]) => {
      emit('update:value', args[0]);
      emit('change', ...args);
      formItemContext.onFieldChange();
    };
    const handleTreeExpand: TreeSelectProps['onTreeExpand'] = (keys: Key[]) => {
      emit('update:treeExpandedKeys', keys);
      emit('treeExpand', keys);
    };
    const handleSearch: TreeSelectProps['onSearch'] = (value: string) => {
      emit('update:searchValue', value);
      emit('search', value);
    };
    const handleBlur = (e: FocusEvent) => {
      emit('blur', e);
      formItemContext.onFieldBlur();
    };
    return () => {
      const {
        notFoundContent = slots.notFoundContent?.(),
        prefixCls: customizePrefixCls,
        bordered,
        listHeight,
        listItemHeight,
        multiple,
        treeIcon,
        treeLine,
        showArrow,
        switcherIcon = slots.switcherIcon?.(),
        fieldNames = props.replaceFields,
        id = formItemContext.id.value,
      } = props;
      const { isFormItemInput, hasFeedback, feedbackIcon } = formItemInputContext;
      // ===================== Icons =====================
      const { suffixIcon, removeIcon, clearIcon } = getIcons(
        {
          ...props,
          multiple: isMultiple.value,
          showArrow: mergedShowArrow.value,
          hasFeedback,
          feedbackIcon,
          prefixCls: prefixCls.value,
        },
        slots,
      );

      // ===================== Empty =====================
      let mergedNotFound;
      if (notFoundContent !== undefined) {
        mergedNotFound = notFoundContent;
      } else {
        mergedNotFound = renderEmpty('Select');
      }
      // ==================== Render =====================
      const selectProps = omit(props as typeof props & { itemIcon: any; switcherIcon: any }, [
        'suffixIcon',
        'itemIcon',
        'removeIcon',
        'clearIcon',
        'switcherIcon',
        'bordered',
        'status',
        'onUpdate:value',
        'onUpdate:treeExpandedKeys',
        'onUpdate:searchValue',
      ]);

      const mergedClassName = classNames(
        !customizePrefixCls && treeSelectPrefixCls.value,
        {
          [`${prefixCls.value}-lg`]: size.value === 'large',
          [`${prefixCls.value}-sm`]: size.value === 'small',
          [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
          [`${prefixCls.value}-borderless`]: !bordered,
          [`${prefixCls.value}-in-form-item`]: isFormItemInput,
        },
        getStatusClassNames(prefixCls.value, mergedStatus.value, hasFeedback),
        attrs.class,
      );
      const otherProps: any = {};
      if (props.treeData === undefined && slots.default) {
        otherProps.children = flattenChildren(slots.default());
      }
      return (
        <VcTreeSelect
          {...attrs}
          {...selectProps}
          virtual={virtual.value}
          dropdownMatchSelectWidth={dropdownMatchSelectWidth.value}
          id={id}
          fieldNames={fieldNames}
          ref={treeSelectRef}
          prefixCls={prefixCls.value}
          class={mergedClassName}
          listHeight={listHeight}
          listItemHeight={listItemHeight}
          treeLine={!!treeLine}
          inputIcon={suffixIcon}
          multiple={multiple}
          removeIcon={removeIcon}
          clearIcon={clearIcon}
          switcherIcon={(nodeProps: SwitcherIconProps) =>
            renderSwitcherIcon(treePrefixCls.value, switcherIcon, treeLine, nodeProps)
          }
          showTreeIcon={treeIcon as any}
          notFoundContent={mergedNotFound}
          getPopupContainer={getPopupContainer.value}
          treeMotion={null}
          dropdownClassName={mergedDropdownClassName.value}
          choiceTransitionName={choiceTransitionName.value}
          onChange={handleChange}
          onBlur={handleBlur}
          onSearch={handleSearch}
          onTreeExpand={handleTreeExpand}
          v-slots={{
            ...slots,
            treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />,
          }}
          {...otherProps}
          transitionName={transitionName.value}
          customSlots={{
            ...slots,
            treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />,
          }}
          maxTagPlaceholder={props.maxTagPlaceholder || slots.maxTagPlaceholder}
          placement={placement.value}
          showArrow={hasFeedback || showArrow}
        />
      );
    };
  },
});

/* istanbul ignore next */
export const TreeSelectNode = TreeNode;
export default Object.assign(TreeSelect, {
  TreeNode,
  SHOW_ALL: SHOW_ALL as typeof SHOW_ALL,
  SHOW_PARENT: SHOW_PARENT as typeof SHOW_PARENT,
  SHOW_CHILD: SHOW_CHILD as typeof SHOW_CHILD,
  install: (app: App) => {
    app.component(TreeSelect.name, TreeSelect);
    app.component(TreeSelectNode.displayName, TreeSelectNode);
    return app;
  },
});