import { App, defineComponent, inject, nextTick, PropType, VNodeTypes, Plugin } from 'vue';
import classNames from '../_util/classNames';
import omit from 'omit.js';
import PropTypes from '../_util/vue-types';
import VcMentions from '../vc-mentions';
import { mentionsProps } from '../vc-mentions/src/mentionsProps';
import Spin from '../spin';
import BaseMixin from '../_util/BaseMixin';
import { defaultConfigProvider } from '../config-provider';
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
import { RenderEmptyHandler } from '../config-provider/renderEmpty';

const { Option } = VcMentions;

interface MentionsConfig {
  prefix?: string | string[];
  split?: string;
}

export interface OptionProps {
  value: string;
  disabled: boolean;
  children: VNodeTypes;
  [key: string]: any;
}

function loadingFilterOption() {
  return true;
}

function getMentions(value = '', config: MentionsConfig) {
  const { prefix = '@', split = ' ' } = config || {};
  const prefixList = Array.isArray(prefix) ? prefix : [prefix];

  return value
    .split(split)
    .map((str = '') => {
      let hitPrefix = null;

      prefixList.some(prefixStr => {
        const startStr = str.slice(0, prefixStr.length);
        if (startStr === prefixStr) {
          hitPrefix = prefixStr;
          return true;
        }
        return false;
      });

      if (hitPrefix !== null) {
        return {
          prefix: hitPrefix,
          value: str.slice(hitPrefix.length),
        };
      }
      return null;
    })
    .filter(entity => !!entity && !!entity.value);
}

const Mentions = defineComponent({
  name: 'AMentions',
  mixins: [BaseMixin],
  inheritAttrs: false,
  Option: { ...Option, name: 'AMentionsOption' },
  getMentions,
  props: {
    ...mentionsProps,
    loading: PropTypes.looseBool,
    onFocus: {
      type: Function as PropType<(e: FocusEvent) => void>,
    },
    onBlur: {
      type: Function as PropType<(e: FocusEvent) => void>,
    },
    onSelect: {
      type: Function as PropType<(option: OptionProps, prefix: string) => void>,
    },
    onChange: {
      type: Function as PropType<(text: string) => void>,
    },
  },
  emits: ['update:value', 'change', 'focus', 'blur', 'select'],
  setup() {
    return {
      configProvider: inject('configProvider', defaultConfigProvider),
    };
  },
  data() {
    return {
      focused: false,
    };
  },
  mounted() {
    nextTick(() => {
      if (process.env.NODE_ENV === 'test') {
        if (this.autofocus) {
          this.focus();
        }
      }
    });
  },
  methods: {
    handleFocus(e: FocusEvent) {
      this.$emit('focus', e);
      this.setState({
        focused: true,
      });
    },
    handleBlur(e: FocusEvent) {
      this.$emit('blur', e);
      this.setState({
        focused: false,
      });
    },
    handleSelect(...args: [OptionProps, string]) {
      this.$emit('select', ...args);
      this.setState({
        focused: true,
      });
    },
    handleChange(val: string) {
      this.$emit('update:value', val);
      this.$emit('change', val);
    },
    getNotFoundContent(renderEmpty: RenderEmptyHandler) {
      const notFoundContent = getComponent(this, 'notFoundContent');
      if (notFoundContent !== undefined) {
        return notFoundContent;
      }

      return renderEmpty('Select');
    },
    getOptions() {
      const { loading } = this.$props;
      const children = getSlot(this);

      if (loading) {
        return (
          <Option value="ANTD_SEARCHING" disabled>
            <Spin size="small" />
          </Option>
        );
      }
      return children;
    },
    getFilterOption() {
      const { filterOption, loading } = this.$props;
      if (loading) {
        return loadingFilterOption;
      }
      return filterOption;
    },
    focus() {
      (this.$refs.vcMentions as HTMLTextAreaElement).focus();
    },
    blur() {
      (this.$refs.vcMentions as HTMLTextAreaElement).blur();
    },
  },
  render() {
    const { focused } = this.$data;
    const { getPrefixCls, renderEmpty } = this.configProvider;
    const {
      prefixCls: customizePrefixCls,
      disabled,
      getPopupContainer,
      ...restProps
    } = getOptionProps(this) as any;
    const { class: className, ...otherAttrs } = this.$attrs;
    const prefixCls = getPrefixCls('mentions', customizePrefixCls);
    const otherProps = omit(restProps, ['loading', 'onUpdate:value']);

    const mergedClassName = classNames(className, {
      [`${prefixCls}-disabled`]: disabled,
      [`${prefixCls}-focused`]: focused,
    });

    const mentionsProps = {
      prefixCls,
      notFoundContent: this.getNotFoundContent(renderEmpty),
      ...otherProps,
      disabled,
      filterOption: this.getFilterOption(),
      getPopupContainer,
      children: this.getOptions(),
      class: mergedClassName,
      rows: 1,
      ...otherAttrs,
      onChange: this.handleChange,
      onSelect: this.handleSelect,
      onFocus: this.handleFocus,
      onBlur: this.handleBlur,
      ref: 'vcMentions',
    };

    return <VcMentions {...mentionsProps} />;
  },
});

/* istanbul ignore next */
Mentions.install = function(app: App) {
  app.component(Mentions.name, Mentions);
  app.component(Mentions.Option.name, Mentions.Option);
  return app;
};

export default Mentions as typeof Mentions &
  Plugin & {
    readonly Option: typeof Option;
  };
