/**
 * Cursor rule:
 * 1. Only `showSearch` enabled
 * 2. Only `open` is `true`
 * 3. When typing, set `open` to `true` which hit rule of 2
 *
 * Accessibility:
 * - https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
 */

import KeyCode from '../../_util/KeyCode';
import MultipleSelector from './MultipleSelector';
import SingleSelector from './SingleSelector';
import type { CustomTagProps, DisplayValueType, Mode, RenderNode } from '../BaseSelect';
import { isValidateOpenKey } from '../utils/keyUtil';
import useLock from '../hooks/useLock';
import type { PropType } from 'vue';
import { defineComponent, ref, computed } from 'vue';
import createRef from '../../_util/createRef';
import PropTypes from '../../_util/vue-types';
import type { VueNode } from '../../_util/type';
import type { EventHandler } from '../../_util/EventInterface';
import type { ScrollTo } from '../../vc-virtual-list/List';

export interface SelectorProps {
  id: string;
  prefixCls: string;
  showSearch?: boolean;
  open: boolean;
  values: DisplayValueType[];
  multiple?: boolean;
  mode: Mode;
  searchValue: string;
  activeValue: string;
  inputElement: VueNode;

  autofocus?: boolean;
  activeDescendantId?: string;
  tabindex?: number | string;
  disabled?: boolean;
  placeholder?: VueNode;
  removeIcon?: RenderNode;

  // Tags
  maxTagCount?: number | 'responsive';
  maxTagTextLength?: number;
  maxTagPlaceholder?: VueNode | ((omittedValues: DisplayValueType[]) => VueNode);
  tagRender?: (props: CustomTagProps) => VueNode;
  optionLabelRender?: (props: Record<string, any>) => VueNode;

  /** Check if `tokenSeparators` contains `\n` or `\r\n` */
  tokenWithEnter?: boolean;

  // Motion
  choiceTransitionName?: string;

  onToggleOpen: (open?: boolean) => void | any;
  /** `onSearch` returns go next step boolean to check if need do toggle open */
  onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean;
  onSearchSubmit: (searchText: string) => void;
  onRemove: (value: DisplayValueType) => void;
  onInputKeyDown?: (e: KeyboardEvent) => void;
  compositionStatus?: boolean;
  onCompositionStart?: () => void;
  onCompositionEnd?: () => void;

  /**
   * @private get real dom for trigger align.
   * This may be removed after React provides replacement of `findDOMNode`
   */
  domRef: () => HTMLDivElement;
}
export interface RefSelectorProps {
  focus: () => void;
  blur: () => void;
  scrollTo?: ScrollTo;
}

const Selector = defineComponent<SelectorProps>({
  name: 'Selector',
  inheritAttrs: false,
  props: {
    id: String,
    prefixCls: String,
    showSearch: { type: Boolean, default: undefined },
    open: { type: Boolean, default: undefined },
    /** Display in the Selector value, it's not same as `value` prop */
    values: PropTypes.array,
    multiple: { type: Boolean, default: undefined },
    mode: String,
    searchValue: String,
    activeValue: String,
    inputElement: PropTypes.any,

    autofocus: { type: Boolean, default: undefined },
    activeDescendantId: String,
    tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    disabled: { type: Boolean, default: undefined },
    placeholder: PropTypes.any,
    removeIcon: PropTypes.any,

    // Tags
    maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    maxTagTextLength: Number,
    maxTagPlaceholder: PropTypes.any,
    tagRender: Function,
    optionLabelRender: Function,

    /** Check if `tokenSeparators` contains `\n` or `\r\n` */
    tokenWithEnter: { type: Boolean, default: undefined },

    // Motion
    choiceTransitionName: String,

    onToggleOpen: { type: Function as PropType<(open?: boolean) => void> },
    /** `onSearch` returns go next step boolean to check if need do toggle open */
    onSearch: Function,
    onSearchSubmit: Function,
    onRemove: Function,
    onInputKeyDown: { type: Function as PropType<EventHandler> },
    compositionStatus: { type: Boolean, default: false },
    onCompositionStart: { type: Function as PropType<() => void> },
    onCompositionEnd: { type: Function as PropType<() => void> },

    /**
     * @private get real dom for trigger align.
     * This may be removed after React provides replacement of `findDOMNode`
     */
    domRef: Function,
  } as any,
  setup(props, { expose }) {
    const inputRef = createRef();
    const compositionStatus = computed(() => props.compositionStatus || false);

    // ====================== Input ======================
    const [getInputMouseDown, setInputMouseDown] = useLock(0);

    const onInternalInputKeyDown = (event: KeyboardEvent) => {
      const { which } = event;

      if (which === KeyCode.UP || which === KeyCode.DOWN) {
        event.preventDefault();
      }

      if (props.onInputKeyDown) {
        props.onInputKeyDown(event);
      }

      if (
        which === KeyCode.ENTER &&
        props.mode === 'tags' &&
        !compositionStatus.value &&
        !props.open
      ) {
        // When menu isn't open, OptionList won't trigger a value change
        // So when enter is pressed, the tag's input value should be emitted here to let selector know
        props.onSearchSubmit((event.target as HTMLInputElement).value);
      }

      if (isValidateOpenKey(which)) {
        props.onToggleOpen(true);
      }
    };

    /**
     * We can not use `findDOMNode` sine it will get warning,
     * have to use timer to check if is input element.
     */
    const onInternalInputMouseDown = () => {
      setInputMouseDown(true);
    };

    // When paste come, ignore next onChange
    let pastedText = null;

    const triggerOnSearch = (value: string) => {
      if (props.onSearch(value, true, compositionStatus.value) !== false) {
        props.onToggleOpen(true);
      }
    };

    const onInputCompositionStart = () => {
      // compositionStatus.value = true;
      props.onCompositionStart();
    };

    const onInputCompositionEnd = (e: InputEvent) => {
      // compositionStatus.value = false;
      props.onCompositionEnd();
      // Trigger search again to support `tokenSeparators` with typewriting
      if (props.mode !== 'combobox') {
        triggerOnSearch((e.target as HTMLInputElement).value);
      }
    };

    const onInputChange = (event: { target: { value: any } }) => {
      let {
        target: { value },
      } = event;

      // Pasted text should replace back to origin content
      if (props.tokenWithEnter && pastedText && /[\r\n]/.test(pastedText)) {
        // CRLF will be treated as a single space for input element
        const replacedText = pastedText
          .replace(/[\r\n]+$/, '')
          .replace(/\r\n/g, ' ')
          .replace(/[\r\n]/g, ' ');
        value = value.replace(replacedText, pastedText);
      }

      pastedText = null;

      triggerOnSearch(value);
    };

    const onInputPaste = (e: ClipboardEvent) => {
      const { clipboardData } = e;
      const value = clipboardData.getData('text');

      pastedText = value;
    };

    const onClick = ({ target }) => {
      if (target !== inputRef.current) {
        // Should focus input if click the selector
        const isIE = (document.body.style as any).msTouchAction !== undefined;
        if (isIE) {
          setTimeout(() => {
            inputRef.current.focus();
          });
        } else {
          inputRef.current.focus();
        }
      }
    };

    const onMousedown = (event: MouseEvent) => {
      const inputMouseDown = getInputMouseDown();
      if (event.target !== inputRef.current && !inputMouseDown) {
        event.preventDefault();
      }

      if ((props.mode !== 'combobox' && (!props.showSearch || !inputMouseDown)) || !props.open) {
        if (props.open) {
          props.onSearch('', true, false);
        }
        props.onToggleOpen();
      }
    };
    expose({
      focus: () => {
        inputRef.current.focus();
      },
      blur: () => {
        console.log('onBlurrrrrrrr1111111');
        props.onCompositionEnd();
        inputRef.current.blur();
      },
    });

    return () => {
      const { prefixCls, domRef, mode } = props as SelectorProps;
      const sharedProps = {
        inputRef,
        onInputKeyDown: onInternalInputKeyDown,
        onInputMouseDown: onInternalInputMouseDown,
        onInputChange,
        onInputPaste,
        compositionStatus: compositionStatus.value,
        onInputCompositionStart,
        onInputCompositionEnd,
      };
      const selectNode =
        mode === 'multiple' || mode === 'tags' ? (
          <MultipleSelector {...props} {...sharedProps} />
        ) : (
          <SingleSelector {...props} {...sharedProps} />
        );
      return (
        <div
          ref={domRef}
          class={`${prefixCls}-selector`}
          onClick={onClick}
          onMousedown={onMousedown}
        >
          {selectNode}
        </div>
      );
    };
  },
});

export default Selector;
