<script>
import { GlIcon } from '@gitlab/ui';
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import { debounce, unescape } from 'lodash';
import createFlash from '~/flash';
import GLForm from '~/gl_form';
import axios from '~/lib/utils/axios_utils';
import { stripHtml } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MarkdownHeader from './header.vue';
import MarkdownToolbar from './toolbar.vue';

function cleanUpLine(content) {
  return unescape(stripHtml(content).replace(/\\n/g, '%br').replace(/\n/g, ''));
}

export default {
  components: {
    MarkdownHeader,
    MarkdownToolbar,
    GlIcon,
    Suggestions,
  },
  mixins: [glFeatureFlagsMixin()],
  props: {
    /**
     * This prop should be bound to the value of the `<textarea>` element
     * that is rendered as a child of this component (in the `textarea` slot)
     */
    textareaValue: {
      type: String,
      required: true,
    },
    markdownDocsPath: {
      type: String,
      required: true,
    },
    isSubmitting: {
      type: Boolean,
      required: false,
      default: false,
    },
    markdownPreviewPath: {
      type: String,
      required: false,
      default: '',
    },
    enablePreview: {
      type: Boolean,
      required: false,
      default: true,
    },
    addSpacingClasses: {
      type: Boolean,
      required: false,
      default: true,
    },
    quickActionsDocsPath: {
      type: String,
      required: false,
      default: '',
    },
    canAttachFile: {
      type: Boolean,
      required: false,
      default: true,
    },
    uploadsPath: {
      type: String,
      required: false,
      default: '',
    },
    enableAutocomplete: {
      type: Boolean,
      required: false,
      default: true,
    },
    line: {
      type: Object,
      required: false,
      default: null,
    },
    lines: {
      type: Array,
      required: false,
      default: () => [],
    },
    note: {
      type: Object,
      required: false,
      default: () => ({}),
    },
    canSuggest: {
      type: Boolean,
      required: false,
      default: false,
    },
    helpPagePath: {
      type: String,
      required: false,
      default: '',
    },
    showSuggestPopover: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  data() {
    return {
      markdownPreview: '',
      referencedCommands: '',
      referencedUsers: [],
      hasSuggestion: false,
      markdownPreviewLoading: false,
      previewMarkdown: false,
      suggestions: this.note.suggestions || [],
      debouncedFetchMarkdownLoading: false,
    };
  },
  computed: {
    shouldShowReferencedUsers() {
      const referencedUsersThreshold = 10;
      return this.referencedUsers.length >= referencedUsersThreshold;
    },
    lineContent() {
      if (this.lines.length) {
        return this.lines
          .map((line) => {
            const { rich_text: richText, text } = line;

            if (text) {
              return text;
            }

            return cleanUpLine(richText);
          })
          .join('\\n');
      }

      if (this.line) {
        const { rich_text: richText, text } = this.line;

        if (text) {
          return text;
        }

        return cleanUpLine(richText);
      }

      return '';
    },
    lineNumber() {
      let lineNumber;
      if (this.line) {
        const { new_line: newLine, old_line: oldLine } = this.line;
        lineNumber = newLine || oldLine;
      }
      return lineNumber;
    },
    lineType() {
      return this.line ? this.line.type : '';
    },
    addMultipleToDiscussionWarning() {
      return sprintf(
        __(
          'You are about to add %{usersTag} people to the discussion. They will all receive a notification.',
        ),
        {
          usersTag: `<strong><span class="js-referenced-users-count">${this.referencedUsers.length}</span></strong>`,
        },
        false,
      );
    },
    suggestionsStartIndex() {
      return Math.max(this.lines.length - 1, 0);
    },
  },
  watch: {
    isSubmitting(isSubmitting) {
      if (!isSubmitting || !this.$refs['markdown-preview'].querySelectorAll) {
        return;
      }
      const mediaInPreview = this.$refs['markdown-preview'].querySelectorAll('video, audio');

      if (mediaInPreview) {
        mediaInPreview.forEach((media) => {
          media.pause();
        });
      }
    },

    textareaValue: {
      immediate: true,
      handler(textareaValue, oldVal) {
        const all = /@all([^\w._-]|$)/;
        const hasAll = all.test(textareaValue);
        const hadAll = all.test(oldVal);

        const justAddedAll = !hadAll && hasAll;
        const justRemovedAll = hadAll && !hasAll;

        if (justAddedAll) {
          this.debouncedFetchMarkdownLoading = false;
          this.debouncedFetchMarkdown();
        } else if (justRemovedAll) {
          this.debouncedFetchMarkdownLoading = true;
          this.referencedUsers = [];
        }
      },
    },
    enablePreview: {
      immediate: true,
      handler(newVal) {
        if (!newVal) {
          this.showWriteTab();
        }
      },
    },
  },
  mounted() {
    // GLForm class handles all the toolbar buttons
    return new GLForm(
      $(this.$refs['gl-form']),
      {
        emojis: this.enableAutocomplete,
        members: this.enableAutocomplete,
        issues: this.enableAutocomplete,
        mergeRequests: this.enableAutocomplete,
        epics: this.enableAutocomplete,
        milestones: this.enableAutocomplete,
        labels: this.enableAutocomplete,
        snippets: this.enableAutocomplete,
        vulnerabilities: this.enableAutocomplete,
        contacts: this.enableAutocomplete && this.glFeatures.contactsAutocomplete,
      },
      true,
    );
  },
  beforeDestroy() {
    const glForm = $(this.$refs['gl-form']).data('glForm');
    if (glForm) {
      glForm.destroy();
    }
  },
  methods: {
    showPreviewTab() {
      if (this.previewMarkdown) return;

      this.previewMarkdown = true;

      if (this.textareaValue) {
        this.markdownPreviewLoading = true;
        this.markdownPreview = __('Loading…');

        this.fetchMarkdown()
          .then((data) => this.renderMarkdown(data))
          .catch(() =>
            createFlash({
              message: __('Error loading markdown preview'),
            }),
          );
      } else {
        this.renderMarkdown();
      }
    },
    showWriteTab() {
      this.markdownPreview = '';
      this.previewMarkdown = false;
    },

    fetchMarkdown() {
      return axios.post(this.markdownPreviewPath, { text: this.textareaValue }).then(({ data }) => {
        const { references } = data;
        if (references) {
          this.referencedCommands = references.commands;
          this.referencedUsers = references.users;
          this.hasSuggestion = references.suggestions?.length > 0;
          this.suggestions = references.suggestions;
        }

        return data;
      });
    },

    debouncedFetchMarkdown: debounce(function debouncedFetchMarkdown() {
      return this.fetchMarkdown().then(() => {
        if (this.debouncedFetchMarkdownLoading) {
          this.referencedUsers = [];
          this.debouncedFetchMarkdownLoading = false;
        }
      });
    }, 400),

    renderMarkdown(data = {}) {
      this.markdownPreviewLoading = false;
      this.markdownPreview = data.body || __('Nothing to preview.');

      this.$nextTick()
        .then(() => $(this.$refs['markdown-preview']).renderGFM())
        .catch(() =>
          createFlash({
            message: __('Error rendering Markdown preview'),
          }),
        );
    },
  },
};
</script>

<template>
  <div
    ref="gl-form"
    :class="{ 'gl-mt-3 gl-mb-3': addSpacingClasses }"
    class="js-vue-markdown-field md-area position-relative gfm-form"
    :data-uploads-path="uploadsPath"
  >
    <markdown-header
      :preview-markdown="previewMarkdown"
      :line-content="lineContent"
      :can-suggest="canSuggest"
      :enable-preview="enablePreview"
      :show-suggest-popover="showSuggestPopover"
      :suggestion-start-index="suggestionsStartIndex"
      data-testid="markdownHeader"
      @preview-markdown="showPreviewTab"
      @write-markdown="showWriteTab"
      @handleSuggestDismissed="() => $emit('handleSuggestDismissed')"
    />
    <div v-show="!previewMarkdown" class="md-write-holder">
      <div class="zen-backdrop">
        <slot name="textarea"></slot>
        <a
          class="zen-control zen-control-leave js-zen-leave gl-text-gray-500"
          href="#"
          :aria-label="__('Leave zen mode')"
        >
          <gl-icon :size="16" name="minimize" />
        </a>
        <markdown-toolbar
          :markdown-docs-path="markdownDocsPath"
          :quick-actions-docs-path="quickActionsDocsPath"
          :can-attach-file="canAttachFile"
        />
      </div>
    </div>
    <template v-if="hasSuggestion">
      <div
        v-show="previewMarkdown"
        ref="markdown-preview"
        class="js-vue-md-preview md-preview-holder"
      >
        <suggestions
          v-if="hasSuggestion"
          :note-html="markdownPreview"
          :from-line="lineNumber"
          :from-content="lineContent"
          :line-type="lineType"
          :disabled="true"
          :suggestions="suggestions"
          :help-page-path="helpPagePath"
        />
      </div>
    </template>
    <template v-else>
      <div
        v-show="previewMarkdown"
        ref="markdown-preview"
        class="js-vue-md-preview md md-preview-holder"
        v-html="markdownPreview /* eslint-disable-line vue/no-v-html */"
      ></div>
    </template>
    <div
      v-if="referencedCommands && previewMarkdown && !markdownPreviewLoading"
      class="referenced-commands"
      v-html="referencedCommands /* eslint-disable-line vue/no-v-html */"
    ></div>
    <div v-if="shouldShowReferencedUsers" class="referenced-users">
      <gl-icon name="warning-solid" />
      <span v-html="addMultipleToDiscussionWarning /* eslint-disable-line vue/no-v-html */"></span>
    </div>
  </div>
</template>
