<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import Mousetrap from 'mousetrap';
import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
import createFlash from '~/flash';
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../notes/event_hub';
import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue';
import NoChanges from './no_changes.vue';
import HiddenFilesWarning from './hidden_files_warning.vue';
import CommitWidget from './commit_widget.vue';
import TreeList from './tree_list.vue';
import {
  TREE_LIST_WIDTH_STORAGE_KEY,
  INITIAL_TREE_WIDTH,
  MIN_TREE_WIDTH,
  MAX_TREE_WIDTH,
  TREE_HIDE_STATS_WIDTH,
  MR_TREE_SHOW_KEY,
  CENTERED_LIMITED_CONTAINER_CLASSES,
} from '../constants';

export default {
  name: 'DiffsApp',
  components: {
    Icon,
    CompareVersions,
    DiffFile,
    NoChanges,
    HiddenFilesWarning,
    CommitWidget,
    TreeList,
    GlLoadingIcon,
    PanelResizer,
  },
  mixins: [glFeatureFlagsMixin()],
  props: {
    endpoint: {
      type: String,
      required: true,
    },
    endpointMetadata: {
      type: String,
      required: true,
    },
    endpointBatch: {
      type: String,
      required: true,
    },
    projectPath: {
      type: String,
      required: true,
    },
    shouldShow: {
      type: Boolean,
      required: false,
      default: false,
    },
    currentUser: {
      type: Object,
      required: true,
    },
    helpPagePath: {
      type: String,
      required: false,
      default: '',
    },
    changesEmptyStateIllustration: {
      type: String,
      required: false,
      default: '',
    },
    isFluidLayout: {
      type: Boolean,
      required: false,
      default: false,
    },
    dismissEndpoint: {
      type: String,
      required: false,
      default: '',
    },
    showSuggestPopover: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  data() {
    const treeWidth =
      parseInt(localStorage.getItem(TREE_LIST_WIDTH_STORAGE_KEY), 10) || INITIAL_TREE_WIDTH;

    return {
      assignedDiscussions: false,
      treeWidth,
    };
  },
  computed: {
    ...mapState({
      isLoading: state => state.diffs.isLoading,
      isBatchLoading: state => state.diffs.isBatchLoading,
      diffFiles: state => state.diffs.diffFiles,
      diffViewType: state => state.diffs.diffViewType,
      mergeRequestDiffs: state => state.diffs.mergeRequestDiffs,
      mergeRequestDiff: state => state.diffs.mergeRequestDiff,
      commit: state => state.diffs.commit,
      targetBranchName: state => state.diffs.targetBranchName,
      renderOverflowWarning: state => state.diffs.renderOverflowWarning,
      numTotalFiles: state => state.diffs.realSize,
      numVisibleFiles: state => state.diffs.size,
      plainDiffPath: state => state.diffs.plainDiffPath,
      emailPatchPath: state => state.diffs.emailPatchPath,
    }),
    ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']),
    ...mapGetters('diffs', ['isParallelView', 'currentDiffIndex']),
    ...mapGetters(['isNotesFetched', 'getNoteableData']),
    targetBranch() {
      return {
        branchName: this.targetBranchName,
        versionIndex: -1,
        path: '',
      };
    },
    canCurrentUserFork() {
      return this.currentUser.can_fork === true && this.currentUser.can_create_merge_request;
    },
    showCompareVersions() {
      return this.mergeRequestDiffs && this.mergeRequestDiff;
    },
    renderDiffFiles() {
      return (
        this.diffFiles.length > 0 ||
        (this.startVersion &&
          this.startVersion.version_index === this.mergeRequestDiff.version_index)
      );
    },
    hideFileStats() {
      return this.treeWidth <= TREE_HIDE_STATS_WIDTH;
    },
    isLimitedContainer() {
      return !this.showTreeList && !this.isParallelView && !this.isFluidLayout;
    },
    shouldSetDiscussions() {
      return this.isNotesFetched && !this.assignedDiscussions && !this.isLoading;
    },
  },
  watch: {
    diffViewType() {
      this.adjustView();
    },
    shouldShow() {
      // When the shouldShow property changed to true, the route is rendered for the first time
      // and if we have the isLoading as true this means we didn't fetch the data
      if (this.isLoading) {
        this.fetchData();
      }

      this.adjustView();
    },
    isLoading: 'adjustView',
    showTreeList: 'adjustView',
    shouldSetDiscussions(newVal) {
      if (newVal) {
        this.setDiscussions();
      }
    },
  },
  mounted() {
    this.setBaseConfig({
      endpoint: this.endpoint,
      endpointMetadata: this.endpointMetadata,
      endpointBatch: this.endpointBatch,
      projectPath: this.projectPath,
      dismissEndpoint: this.dismissEndpoint,
      showSuggestPopover: this.showSuggestPopover,
      useSingleDiffStyle: this.glFeatures.singleMrDiffView,
    });

    if (this.shouldShow) {
      this.fetchData();
    }

    const id = window && window.location && window.location.hash;

    if (id) {
      this.setHighlightedRow(id.slice(1));
    }
  },
  created() {
    this.adjustView();
    eventHub.$once('fetchedNotesData', this.setDiscussions);
    eventHub.$once('fetchDiffData', this.fetchData);
    eventHub.$on('refetchDiffData', this.refetchDiffData);
    this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
  },
  beforeDestroy() {
    eventHub.$off('fetchDiffData', this.fetchData);
    eventHub.$off('refetchDiffData', this.refetchDiffData);
    this.removeEventListeners();
  },
  methods: {
    ...mapActions(['startTaskList']),
    ...mapActions('diffs', [
      'setBaseConfig',
      'fetchDiffFiles',
      'fetchDiffFilesMeta',
      'fetchDiffFilesBatch',
      'startRenderDiffsQueue',
      'assignDiscussionsToDiff',
      'setHighlightedRow',
      'cacheTreeListWidth',
      'scrollToFile',
      'toggleShowTreeList',
    ]),
    refetchDiffData() {
      this.assignedDiscussions = false;
      this.fetchData(false);
    },
    isLatestVersion() {
      return window.location.search.indexOf('diff_id') === -1;
    },
    startDiffRendering() {
      requestIdleCallback(
        () => {
          this.startRenderDiffsQueue();
        },
        { timeout: 1000 },
      );
    },
    fetchData(toggleTree = true) {
      if (this.isLatestVersion() && this.glFeatures.diffsBatchLoad) {
        this.fetchDiffFilesMeta()
          .then(() => {
            if (toggleTree) this.hideTreeListIfJustOneFile();

            this.startDiffRendering();
          })
          .catch(() => {
            createFlash(__('Something went wrong on our end. Please try again!'));
          });

        this.fetchDiffFilesBatch()
          .then(() => this.startDiffRendering())
          .catch(() => {
            createFlash(__('Something went wrong on our end. Please try again!'));
          });
      } else {
        this.fetchDiffFiles()
          .then(() => {
            if (toggleTree) {
              this.hideTreeListIfJustOneFile();
            }

            requestIdleCallback(
              () => {
                this.startRenderDiffsQueue();
              },
              { timeout: 1000 },
            );
          })
          .catch(() => {
            createFlash(__('Something went wrong on our end. Please try again!'));
          });
      }

      if (!this.isNotesFetched) {
        eventHub.$emit('fetchNotesData');
      }
    },
    setDiscussions() {
      if (this.shouldSetDiscussions) {
        this.assignedDiscussions = true;

        requestIdleCallback(
          () =>
            this.assignDiscussionsToDiff()
              .then(this.$nextTick)
              .then(this.startTaskList),
          { timeout: 1000 },
        );
      }
    },
    adjustView() {
      if (this.shouldShow) {
        this.$nextTick(() => {
          this.setEventListeners();
        });
      } else {
        this.removeEventListeners();
      }
    },
    setEventListeners() {
      Mousetrap.bind(['[', 'k', ']', 'j'], (e, combo) => {
        switch (combo) {
          case '[':
          case 'k':
            this.jumpToFile(-1);
            break;
          case ']':
          case 'j':
            this.jumpToFile(+1);
            break;
          default:
            break;
        }
      });
    },
    removeEventListeners() {
      Mousetrap.unbind(['[', 'k', ']', 'j']);
    },
    jumpToFile(step) {
      const targetIndex = this.currentDiffIndex + step;
      if (targetIndex >= 0 && targetIndex < this.diffFiles.length) {
        this.scrollToFile(this.diffFiles[targetIndex].file_path);
      }
    },
    hideTreeListIfJustOneFile() {
      const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);

      if ((storedTreeShow === null && this.diffFiles.length <= 1) || storedTreeShow === 'false') {
        this.toggleShowTreeList(false);
      }
    },
  },
  minTreeWidth: MIN_TREE_WIDTH,
  maxTreeWidth: MAX_TREE_WIDTH,
};
</script>

<template>
  <div v-show="shouldShow">
    <div v-if="isLoading" class="loading"><gl-loading-icon /></div>
    <div v-else id="diffs" :class="{ active: shouldShow }" class="diffs tab-pane">
      <compare-versions
        :merge-request-diffs="mergeRequestDiffs"
        :merge-request-diff="mergeRequestDiff"
        :target-branch="targetBranch"
        :is-limited-container="isLimitedContainer"
      />

      <hidden-files-warning
        v-if="renderOverflowWarning"
        :visible="numVisibleFiles"
        :total="numTotalFiles"
        :plain-diff-path="plainDiffPath"
        :email-patch-path="emailPatchPath"
      />

      <div
        :data-can-create-note="getNoteableData.current_user.can_create_note"
        class="files d-flex prepend-top-default"
      >
        <div
          v-show="showTreeList"
          :style="{ width: `${treeWidth}px` }"
          class="diff-tree-list js-diff-tree-list mr-3"
        >
          <panel-resizer
            :size.sync="treeWidth"
            :start-size="treeWidth"
            :min-size="$options.minTreeWidth"
            :max-size="$options.maxTreeWidth"
            side="right"
            @resize-end="cacheTreeListWidth"
          />
          <tree-list :hide-file-stats="hideFileStats" />
        </div>
        <div
          class="diff-files-holder"
          :class="{
            [CENTERED_LIMITED_CONTAINER_CLASSES]: isLimitedContainer,
          }"
        >
          <commit-widget v-if="commit" :commit="commit" />
          <div v-if="isBatchLoading" class="loading"><gl-loading-icon /></div>
          <template v-else-if="renderDiffFiles">
            <diff-file
              v-for="file in diffFiles"
              :key="file.newPath"
              :file="file"
              :help-page-path="helpPagePath"
              :can-current-user-fork="canCurrentUserFork"
            />
          </template>
          <no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" />
        </div>
      </div>
    </div>
  </div>
</template>