<script>
import { escape, last } from 'lodash';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
import SidebarMediator from '~/sidebar/sidebar_mediator';

const AutoComplete = {
  Issues: 'issues',
  Labels: 'labels',
  Members: 'members',
  MergeRequests: 'mergeRequests',
};

const groupType = 'Group'; // eslint-disable-line @gitlab/require-i18n-strings

function doesCurrentLineStartWith(searchString, fullText, selectionStart) {
  const currentLineNumber = fullText.slice(0, selectionStart).split('\n').length;
  const currentLine = fullText.split('\n')[currentLineNumber - 1];
  return currentLine.startsWith(searchString);
}

const autoCompleteMap = {
  [AutoComplete.Issues]: {
    filterValues() {
      return this[AutoComplete.Issues];
    },
    menuItemTemplate({ original }) {
      return `<small>${original.reference || original.iid}</small> ${escape(original.title)}`;
    },
  },
  [AutoComplete.Labels]: {
    filterValues() {
      const fullText = this.$slots.default?.[0]?.elm?.value;
      const selectionStart = this.$slots.default?.[0]?.elm?.selectionStart;

      if (doesCurrentLineStartWith('/label', fullText, selectionStart)) {
        return this.labels.filter(label => !label.set);
      }

      if (doesCurrentLineStartWith('/unlabel', fullText, selectionStart)) {
        return this.labels.filter(label => label.set);
      }

      return this.labels;
    },
    menuItemTemplate({ original }) {
      return `
        <span class="dropdown-label-box" style="background: ${escape(original.color)};"></span>
        ${escape(original.title)}`;
    },
  },
  [AutoComplete.Members]: {
    filterValues() {
      const fullText = this.$slots.default?.[0]?.elm?.value;
      const selectionStart = this.$slots.default?.[0]?.elm?.selectionStart;

      // Need to check whether sidebar store assignees has been updated
      // in the case where the assignees AJAX response comes after the user does @ autocomplete
      const isAssigneesLengthSame =
        this.assignees?.length === SidebarMediator.singleton?.store?.assignees?.length;

      if (!this.assignees || !isAssigneesLengthSame) {
        this.assignees =
          SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
      }

      if (doesCurrentLineStartWith('/assign', fullText, selectionStart)) {
        return this.members.filter(member => !this.assignees.includes(member.username));
      }

      if (doesCurrentLineStartWith('/unassign', fullText, selectionStart)) {
        return this.members.filter(member => this.assignees.includes(member.username));
      }

      return this.members;
    },
    menuItemTemplate({ original }) {
      const commonClasses = 'gl-avatar gl-avatar-s24 gl-flex-shrink-0';
      const noAvatarClasses = `${commonClasses} gl-rounded-small
        gl-display-flex gl-align-items-center gl-justify-content-center`;

      const avatar = original.avatar_url
        ? `<img class="${commonClasses} gl-avatar-circle" src="${original.avatar_url}" alt="" />`
        : `<div class="${noAvatarClasses}" aria-hidden="true">
            ${original.username.charAt(0).toUpperCase()}</div>`;

      let displayName = original.name;
      let parentGroupOrUsername = `@${original.username}`;

      if (original.type === groupType) {
        const splitName = original.name.split(' / ');
        displayName = splitName.pop();
        parentGroupOrUsername = splitName.pop();
      }

      const count = original.count && !original.mentionsDisabled ? ` (${original.count})` : '';

      const disabledMentionsIcon = original.mentionsDisabled
        ? spriteIcon('notifications-off', 's16 gl-ml-3')
        : '';

      return `
        <div class="gl-display-flex gl-align-items-center">
          ${avatar}
          <div class="gl-font-sm gl-line-height-normal gl-ml-3">
            <div>${escape(displayName)}${count}</div>
            <div class="gl-text-gray-700">${escape(parentGroupOrUsername)}</div>
          </div>
          ${disabledMentionsIcon}
        </div>
      `;
    },
  },
  [AutoComplete.MergeRequests]: {
    filterValues() {
      return this[AutoComplete.MergeRequests];
    },
    menuItemTemplate({ original }) {
      return `<small>${original.reference || original.iid}</small> ${escape(original.title)}`;
    },
  },
};

export default {
  name: 'GlMentions',
  props: {
    dataSources: {
      type: Object,
      required: false,
      default: () => gl.GfmAutoComplete?.dataSources || {},
    },
  },
  mounted() {
    const NON_WORD_OR_INTEGER = /\W|^\d+$/;

    this.tribute = new Tribute({
      collection: [
        {
          trigger: '#',
          lookup: value => value.iid + value.title,
          menuItemTemplate: autoCompleteMap[AutoComplete.Issues].menuItemTemplate,
          selectTemplate: ({ original }) => original.reference || `#${original.iid}`,
          values: this.getValues(AutoComplete.Issues),
        },
        {
          trigger: '@',
          fillAttr: 'username',
          lookup: value =>
            value.type === groupType ? last(value.name.split(' / ')) : value.name + value.username,
          menuItemTemplate: autoCompleteMap[AutoComplete.Members].menuItemTemplate,
          values: this.getValues(AutoComplete.Members),
        },
        {
          trigger: '~',
          lookup: 'title',
          menuItemTemplate: autoCompleteMap[AutoComplete.Labels].menuItemTemplate,
          selectTemplate: ({ original }) =>
            NON_WORD_OR_INTEGER.test(original.title)
              ? `~"${original.title}"`
              : `~${original.title}`,
          values: this.getValues(AutoComplete.Labels),
        },
        {
          trigger: '!',
          lookup: value => value.iid + value.title,
          menuItemTemplate: autoCompleteMap[AutoComplete.MergeRequests].menuItemTemplate,
          selectTemplate: ({ original }) => original.reference || `!${original.iid}`,
          values: this.getValues(AutoComplete.MergeRequests),
        },
      ],
    });

    const input = this.$slots.default?.[0]?.elm;
    this.tribute.attach(input);
  },
  beforeDestroy() {
    const input = this.$slots.default?.[0]?.elm;
    this.tribute.detach(input);
  },
  methods: {
    getValues(autoCompleteType) {
      return (inputText, processValues) => {
        if (this[autoCompleteType]) {
          const filteredValues = autoCompleteMap[autoCompleteType].filterValues.call(this);
          processValues(filteredValues);
        } else if (this.dataSources[autoCompleteType]) {
          axios
            .get(this.dataSources[autoCompleteType])
            .then(response => {
              this[autoCompleteType] = response.data;
              const filteredValues = autoCompleteMap[autoCompleteType].filterValues.call(this);
              processValues(filteredValues);
            })
            .catch(() => {});
        } else {
          processValues([]);
        }
      };
    },
  },
  render(createElement) {
    return createElement('div', this.$slots.default);
  },
};
</script>
