<script>
import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import Autosize from 'autosize';
import { __, sprintf } from '~/locale';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
import Autosave from '../../autosave';
import {
  capitalizeFirstCharacter,
  convertToCamelCase,
  splitCamelCase,
} from '../../lib/utils/text_utility';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state';

export default {
  name: 'CommentForm',
  components: {
    issueWarning,
    noteSignedOutWidget,
    discussionLockedWidget,
    markdownField,
    userAvatarLink,
    loadingButton,
    TimelineEntryItem,
  },
  mixins: [issuableStateMixin],
  props: {
    noteableType: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      note: '',
      noteType: constants.COMMENT,
      isSubmitting: false,
      isSubmitButtonDisabled: true,
    };
  },
  computed: {
    ...mapGetters([
      'getCurrentUserLastNote',
      'getUserData',
      'getNoteableData',
      'getNotesData',
      'openState',
    ]),
    ...mapState(['isToggleStateButtonLoading']),
    noteableDisplayName() {
      return splitCamelCase(this.noteableType).toLowerCase();
    },
    isLoggedIn() {
      return this.getUserData.id;
    },
    commentButtonTitle() {
      return this.noteType === constants.COMMENT ? 'Comment' : 'Start discussion';
    },
    startDiscussionDescription() {
      let text = 'Discuss a specific suggestion or question';
      if (this.getNoteableData.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE) {
        text += ' that needs to be resolved';
      }
      return `${text}.`;
    },
    isOpen() {
      return this.openState === constants.OPENED || this.openState === constants.REOPENED;
    },
    canCreateNote() {
      return this.getNoteableData.current_user.can_create_note;
    },
    issueActionButtonTitle() {
      const openOrClose = this.isOpen ? 'close' : 'reopen';

      if (this.note.length) {
        return sprintf(__('%{actionText} & %{openOrClose} %{noteable}'), {
          actionText: this.commentButtonTitle,
          openOrClose,
          noteable: this.noteableDisplayName,
        });
      }

      return sprintf(__('%{openOrClose} %{noteable}'), {
        openOrClose: capitalizeFirstCharacter(openOrClose),
        noteable: this.noteableDisplayName,
      });
    },
    actionButtonClassNames() {
      return {
        'btn-reopen': !this.isOpen,
        'btn-close': this.isOpen,
        'js-note-target-close': this.isOpen,
        'js-note-target-reopen': !this.isOpen,
      };
    },
    markdownDocsPath() {
      return this.getNotesData.markdownDocsPath;
    },
    quickActionsDocsPath() {
      return this.getNotesData.quickActionsDocsPath;
    },
    markdownPreviewPath() {
      return this.getNoteableData.preview_note_path;
    },
    author() {
      return this.getUserData;
    },
    canUpdateIssue() {
      return this.getNoteableData.current_user.can_update;
    },
    endpoint() {
      return this.getNoteableData.create_note_path;
    },
    issuableTypeTitle() {
      return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
        ? 'merge request'
        : 'issue';
    },
  },
  watch: {
    note(newNote) {
      this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
    },
    isSubmitting(newValue) {
      this.setIsSubmitButtonDisabled(this.note, newValue);
    },
  },
  mounted() {
    // jQuery is needed here because it is a custom event being dispatched with jQuery.
    $(document).on('issuable:change', (e, isClosed) => {
      this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED);
    });

    this.initAutoSave();
  },
  methods: {
    ...mapActions([
      'saveNote',
      'stopPolling',
      'restartPolling',
      'removePlaceholderNotes',
      'closeIssue',
      'reopenIssue',
      'toggleIssueLocalState',
      'toggleStateButtonLoading',
    ]),
    setIsSubmitButtonDisabled(note, isSubmitting) {
      if (!_.isEmpty(note) && !isSubmitting) {
        this.isSubmitButtonDisabled = false;
      } else {
        this.isSubmitButtonDisabled = true;
      }
    },
    handleSave(withIssueAction) {
      this.isSubmitting = true;

      if (this.note.length) {
        const noteData = {
          endpoint: this.endpoint,
          flashContainer: this.$el,
          data: {
            note: {
              noteable_type: this.noteableType,
              noteable_id: this.getNoteableData.id,
              note: this.note,
            },
            merge_request_diff_head_sha: this.getNoteableData.diff_head_sha,
          },
        };

        if (this.noteType === constants.DISCUSSION) {
          noteData.data.note.type = constants.DISCUSSION_NOTE;
        }

        this.note = ''; // Empty textarea while being requested. Repopulate in catch
        this.resizeTextarea();
        this.stopPolling();

        this.saveNote(noteData)
          .then(res => {
            this.enableButton();
            this.restartPolling();

            if (res.errors) {
              if (res.errors.commands_only) {
                this.discard();
              } else {
                Flash(
                  'Something went wrong while adding your comment. Please try again.',
                  'alert',
                  this.$refs.commentForm,
                );
              }
            } else {
              this.discard();
            }

            if (withIssueAction) {
              this.toggleIssueState();
            }
          })
          .catch(() => {
            this.enableButton();
            this.discard(false);
            const msg = `Your comment could not be submitted!
Please check your network connection and try again.`;
            Flash(msg, 'alert', this.$el);
            this.note = noteData.data.note.note; // Restore textarea content.
            this.removePlaceholderNotes();
          });
      } else {
        this.toggleIssueState();
      }
    },
    enableButton() {
      this.isSubmitting = false;
    },
    toggleIssueState() {
      if (this.isOpen) {
        this.closeIssue()
          .then(() => this.enableButton())
          .catch(() => {
            this.enableButton();
            this.toggleStateButtonLoading(false);
            Flash(
              sprintf(
                __('Something went wrong while closing the %{issuable}. Please try again later'),
                { issuable: this.noteableDisplayName },
              ),
            );
          });
      } else {
        this.reopenIssue()
          .then(() => this.enableButton())
          .catch(({ data }) => {
            this.enableButton();
            this.toggleStateButtonLoading(false);
            let errorMessage = sprintf(
              __('Something went wrong while reopening the %{issuable}. Please try again later'),
              { issuable: this.noteableDisplayName },
            );

            if (data) {
              errorMessage = Object.values(data).join('\n');
            }

            Flash(errorMessage);
          });
      }
    },
    discard(shouldClear = true) {
      // `blur` is needed to clear slash commands autocomplete cache if event fired.
      // `focus` is needed to remain cursor in the textarea.
      this.$refs.textarea.blur();
      this.$refs.textarea.focus();

      if (shouldClear) {
        this.note = '';
        this.resizeTextarea();
        this.$refs.markdownField.previewMarkdown = false;
      }

      this.autosave.reset();
    },
    setNoteType(type) {
      this.noteType = type;
    },
    editCurrentUserLastNote() {
      if (this.note === '') {
        const lastNote = this.getCurrentUserLastNote;

        if (lastNote) {
          eventHub.$emit('enterEditMode', {
            noteId: lastNote.id,
          });
        }
      }
    },
    initAutoSave() {
      if (this.isLoggedIn) {
        const noteableType = capitalizeFirstCharacter(convertToCamelCase(this.noteableType));

        this.autosave = new Autosave($(this.$refs.textarea), [
          'Note',
          noteableType,
          this.getNoteableData.id,
        ]);
      }
    },
    resizeTextarea() {
      this.$nextTick(() => {
        Autosize.update(this.$refs.textarea);
      });
    },
  },
};
</script>

<template>
  <div>
    <note-signed-out-widget v-if="!isLoggedIn" />
    <discussion-locked-widget v-else-if="!canCreateNote" :issuable-type="issuableTypeTitle" />
    <ul v-else-if="canCreateNote" class="notes notes-form timeline">
      <timeline-entry-item class="note-form">
        <div class="flash-container error-alert timeline-content"></div>
        <div class="timeline-icon d-none d-sm-none d-md-block">
          <user-avatar-link
            v-if="author"
            :link-href="author.path"
            :img-src="author.avatar_url"
            :img-alt="author.name"
            :img-size="40"
          />
        </div>
        <div class="timeline-content timeline-content-form">
          <form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form">
            <div class="error-alert"></div>

            <issue-warning
              v-if="hasWarning(getNoteableData)"
              :is-locked="isLocked(getNoteableData)"
              :is-confidential="isConfidential(getNoteableData)"
            />

            <markdown-field
              ref="markdownField"
              :markdown-preview-path="markdownPreviewPath"
              :markdown-docs-path="markdownDocsPath"
              :quick-actions-docs-path="quickActionsDocsPath"
              :add-spacing-classes="false"
            >
              <textarea
                id="note-body"
                ref="textarea"
                slot="textarea"
                v-model="note"
                :disabled="isSubmitting"
                name="note[note]"
                class="note-textarea js-vue-comment-form js-note-text
js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
                data-supports-quick-actions="true"
                aria-label="Description"
                placeholder="Write a comment or drag your files here…"
                @keydown.up="editCurrentUserLastNote()"
                @keydown.meta.enter="handleSave()"
                @keydown.ctrl.enter="handleSave()"
              >
              </textarea>
            </markdown-field>
            <div class="note-form-actions">
              <div
                class="float-left btn-group
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
              >
                <button
                  :disabled="isSubmitButtonDisabled"
                  class="btn btn-success js-comment-button js-comment-submit-button
                    qa-comment-button"
                  type="submit"
                  @click.prevent="handleSave()"
                >
                  {{ __(commentButtonTitle) }}
                </button>
                <button
                  :disabled="isSubmitButtonDisabled"
                  name="button"
                  type="button"
                  class="btn btn-success note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
                  data-display="static"
                  data-toggle="dropdown"
                  aria-label="Open comment type dropdown"
                >
                  <i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i>
                </button>

                <ul class="note-type-dropdown dropdown-open-top dropdown-menu">
                  <li :class="{ 'droplab-item-selected': noteType === 'comment' }">
                    <button
                      type="button"
                      class="btn btn-transparent"
                      @click.prevent="setNoteType('comment')"
                    >
                      <i aria-hidden="true" class="fa fa-check icon"> </i>
                      <div class="description">
                        <strong>Comment</strong>
                        <p>Add a general comment to this {{ noteableDisplayName }}.</p>
                      </div>
                    </button>
                  </li>
                  <li class="divider droplab-item-ignore"></li>
                  <li :class="{ 'droplab-item-selected': noteType === 'discussion' }">
                    <button
                      type="button"
                      class="btn btn-transparent qa-discussion-option"
                      @click.prevent="setNoteType('discussion')"
                    >
                      <i aria-hidden="true" class="fa fa-check icon"> </i>
                      <div class="description">
                        <strong>Start discussion</strong>
                        <p>{{ startDiscussionDescription }}</p>
                      </div>
                    </button>
                  </li>
                </ul>
              </div>

              <loading-button
                v-if="canUpdateIssue"
                :loading="isToggleStateButtonLoading"
                :container-class="[
                  actionButtonClassNames,
                  'btn btn-comment btn-comment-and-close js-action-button',
                ]"
                :disabled="isToggleStateButtonLoading || isSubmitting"
                :label="issueActionButtonTitle"
                @click="handleSave(true)"
              />
            </div>
          </form>
        </div>
      </timeline-entry-item>
    </ul>
  </div>
</template>
