<script>
import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import Autosize from 'autosize';
import { __, sprintf } from '~/locale';
import Flash from '../../flash';
import Autosave from '../../autosave';
import TaskList from '../../task_list';
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,
  },
  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();
    this.initTaskList();
  },
  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(() => {
            this.enableButton();
            this.toggleStateButtonLoading(false);
            Flash(
              sprintf(
                __('Something went wrong while reopening the %{issuable}. Please try again later'),
                { issuable: this.noteableDisplayName },
              ),
            );
          });
      }
    },
    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,
        ]);
      }
    },
    initTaskList() {
      return new TaskList({
        dataType: 'note',
        fieldName: 'note',
        selector: '.notes',
      });
    },
    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">
      <li class="timeline-entry">
        <div class="timeline-entry-inner">
          <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"
                  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-create comment-btn js-comment-button js-comment-submit-button"
                    type="submit"
                    @click.prevent="handleSave()">
                    {{ __(commentButtonTitle) }}
                  </button>
                  <button
                    :disabled="isSubmitButtonDisabled"
                    name="button"
                    type="button"
                    class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle"
                    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"
                        @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)"
                />

                <button
                  v-if="note.length"
                  type="button"
                  class="btn btn-cancel js-note-discard"
                  @click="discard">
                  Discard draft
                </button>
              </div>
            </form>
          </div>
        </div>
      </li>
    </ul>
  </div>
</template>
