<script>
import {
  GlLink,
  GlDropdown,
  GlDropdownItem,
  GlDropdownText,
  GlSearchBoxByType,
  GlDropdownDivider,
  GlLoadingIcon,
  GlIcon,
  GlTooltipDirective,
} from '@gitlab/ui';
import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { IssuableType } from '~/issue_show/constants';
import { timeFor } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import {
  Tracking,
  IssuableAttributeState,
  IssuableAttributeType,
  issuableAttributesQueries,
  noAttributeId,
  defaultEpicSort,
  epicIidPattern,
} from '~/sidebar/constants';

export default {
  noAttributeId,
  IssuableAttributeState,
  issuableAttributesQueries,
  i18n: {
    [IssuableAttributeType.Milestone]: __('Milestone'),
    expired: __('(expired)'),
    none: __('None'),
  },
  directives: {
    GlTooltip: GlTooltipDirective,
  },
  components: {
    SidebarEditableItem,
    GlLink,
    GlDropdown,
    GlDropdownItem,
    GlDropdownText,
    GlDropdownDivider,
    GlSearchBoxByType,
    GlIcon,
    GlLoadingIcon,
  },
  inject: {
    isClassicSidebar: {
      default: false,
    },
  },
  props: {
    issuableAttribute: {
      type: String,
      required: true,
      validator(value) {
        return [IssuableAttributeType.Milestone].includes(value);
      },
    },
    workspacePath: {
      required: true,
      type: String,
    },
    iid: {
      required: true,
      type: String,
    },
    attrWorkspacePath: {
      required: true,
      type: String,
    },
    issuableType: {
      type: String,
      required: true,
      validator(value) {
        return [IssuableType.Issue, IssuableType.MergeRequest].includes(value);
      },
    },
    icon: {
      type: String,
      required: false,
      default: undefined,
    },
  },
  apollo: {
    currentAttribute: {
      query() {
        const { current } = this.issuableAttributeQuery;
        const { query } = current[this.issuableType];

        return query;
      },
      variables() {
        return {
          fullPath: this.workspacePath,
          iid: this.iid,
        };
      },
      update(data) {
        return data?.workspace?.issuable.attribute;
      },
      error(error) {
        createFlash({
          message: this.i18n.currentFetchError,
          captureError: true,
          error,
        });
      },
    },
    attributesList: {
      query() {
        const { list } = this.issuableAttributeQuery;
        const { query } = list[this.issuableType];

        return query;
      },
      skip() {
        if (this.isEpic && this.searchTerm.startsWith('&') && this.searchTerm.length < 2) {
          return true;
        }

        return !this.editing;
      },
      debounce: 250,
      variables() {
        if (!this.isEpic) {
          return {
            fullPath: this.attrWorkspacePath,
            title: this.searchTerm,
            state: this.$options.IssuableAttributeState[this.issuableAttribute],
          };
        }

        const variables = {
          fullPath: this.attrWorkspacePath,
          state: this.$options.IssuableAttributeState[this.issuableAttribute],
          sort: defaultEpicSort,
        };

        if (epicIidPattern.test(this.searchTerm)) {
          const matches = this.searchTerm.match(epicIidPattern);
          variables.iidStartsWith = matches.groups.iid;
        } else if (this.searchTerm !== '') {
          variables.in = 'TITLE';
          variables.title = this.searchTerm;
        }

        return variables;
      },
      update(data) {
        if (data?.workspace) {
          return data?.workspace?.attributes.nodes;
        }
        return [];
      },
      error(error) {
        createFlash({ message: this.i18n.listFetchError, captureError: true, error });
      },
    },
  },
  data() {
    return {
      searchTerm: '',
      editing: false,
      updating: false,
      selectedTitle: null,
      currentAttribute: null,
      attributesList: [],
      tracking: {
        event: Tracking.editEvent,
        label: Tracking.rightSidebarLabel,
        property: this.issuableAttribute,
      },
    };
  },
  computed: {
    issuableAttributeQuery() {
      return this.$options.issuableAttributesQueries[this.issuableAttribute];
    },
    attributeTitle() {
      return this.currentAttribute?.title || this.i18n.noAttribute;
    },
    attributeUrl() {
      return this.currentAttribute?.webUrl;
    },
    dropdownText() {
      return this.currentAttribute
        ? this.currentAttribute?.title
        : this.$options.i18n[this.issuableAttribute];
    },
    loading() {
      return this.$apollo.queries.currentAttribute.loading;
    },
    emptyPropsList() {
      return this.attributesList.length === 0;
    },
    attributeTypeTitle() {
      return this.$options.i18n[this.issuableAttribute];
    },
    attributeTypeIcon() {
      return this.icon || this.issuableAttribute;
    },
    tooltipText() {
      return timeFor(this.currentAttribute?.dueDate);
    },
    i18n() {
      return {
        noAttribute: sprintf(s__('DropdownWidget|No %{issuableAttribute}'), {
          issuableAttribute: this.issuableAttribute,
        }),
        assignAttribute: sprintf(s__('DropdownWidget|Assign %{issuableAttribute}'), {
          issuableAttribute: this.issuableAttribute,
        }),
        noAttributesFound: sprintf(s__('DropdownWidget|No %{issuableAttribute} found'), {
          issuableAttribute: this.issuableAttribute,
        }),
        updateError: sprintf(
          s__(
            'DropdownWidget|Failed to set %{issuableAttribute} on this %{issuableType}. Please try again.',
          ),
          { issuableAttribute: this.issuableAttribute, issuableType: this.issuableType },
        ),
        listFetchError: sprintf(
          s__(
            'DropdownWidget|Failed to fetch the %{issuableAttribute} for this %{issuableType}. Please try again.',
          ),
          { issuableAttribute: this.issuableAttribute, issuableType: this.issuableType },
        ),
        currentFetchError: sprintf(
          s__(
            'DropdownWidget|An error occurred while fetching the assigned %{issuableAttribute} of the selected %{issuableType}.',
          ),
          { issuableAttribute: this.issuableAttribute, issuableType: this.issuableType },
        ),
      };
    },
    isEpic() {
      return this.issuableAttribute === IssuableType.Epic;
    },
  },
  methods: {
    updateAttribute(attributeId) {
      if (this.currentAttribute === null && attributeId === null) return;
      if (attributeId === this.currentAttribute?.id) return;

      this.updating = true;

      const selectedAttribute =
        Boolean(attributeId) && this.attributesList.find((p) => p.id === attributeId);
      this.selectedTitle = selectedAttribute ? selectedAttribute.title : this.$options.i18n.none;

      const { current } = this.issuableAttributeQuery;
      const { mutation } = current[this.issuableType];

      this.$apollo
        .mutate({
          mutation,
          variables: {
            fullPath: this.workspacePath,
            attributeId:
              this.issuableAttribute === IssuableAttributeType.Milestone &&
              this.issuableType === IssuableType.Issue
                ? getIdFromGraphQLId(attributeId)
                : attributeId,
            iid: this.iid,
          },
        })
        .then(({ data }) => {
          if (data.issuableSetAttribute?.errors?.length) {
            createFlash({
              message: data.issuableSetAttribute.errors[0],
              captureError: true,
              error: data.issuableSetAttribute.errors[0],
            });
          } else {
            this.$emit('attribute-updated', data);
          }
        })
        .catch((error) => {
          createFlash({ message: this.i18n.updateError, captureError: true, error });
        })
        .finally(() => {
          this.updating = false;
          this.searchTerm = '';
          this.selectedTitle = null;
        });
    },
    isAttributeChecked(attributeId = undefined) {
      return (
        attributeId === this.currentAttribute?.id || (!this.currentAttribute?.id && !attributeId)
      );
    },
    isAttributeOverdue(attribute) {
      return this.issuableAttribute === IssuableAttributeType.Milestone
        ? attribute?.expired
        : false;
    },
    showDropdown() {
      this.$refs.newDropdown.show();
    },
    handleOpen() {
      this.editing = true;
      this.showDropdown();
    },
    handleClose() {
      this.editing = false;
    },
    setFocus() {
      this.$refs.search.focusInput();
    },
  },
};
</script>

<template>
  <sidebar-editable-item
    ref="editable"
    :title="attributeTypeTitle"
    :data-testid="`${issuableAttribute}-edit`"
    :tracking="tracking"
    :loading="updating || loading"
    @open="handleOpen"
    @close="handleClose"
  >
    <template #collapsed>
      <div v-if="isClassicSidebar" v-gl-tooltip class="sidebar-collapsed-icon">
        <gl-icon :size="16" :aria-label="attributeTypeTitle" :name="attributeTypeIcon" />
        <span class="collapse-truncated-title">
          {{ attributeTitle }}
        </span>
      </div>
      <div
        :data-testid="`select-${issuableAttribute}`"
        :class="isClassicSidebar ? 'hide-collapsed' : 'gl-mt-3'"
      >
        <span v-if="updating" class="gl-font-weight-bold">{{ selectedTitle }}</span>
        <span v-else-if="!currentAttribute" class="gl-text-gray-500">
          {{ $options.i18n.none }}
        </span>
        <slot
          v-else
          name="value"
          :attributeTitle="attributeTitle"
          :attributeUrl="attributeUrl"
          :currentAttribute="currentAttribute"
        >
          <gl-link
            v-gl-tooltip="tooltipText"
            class="gl-text-gray-900! gl-font-weight-bold"
            :href="attributeUrl"
            :data-qa-selector="`${issuableAttribute}_link`"
          >
            {{ attributeTitle }}
            <span v-if="isAttributeOverdue(currentAttribute)">{{ $options.i18n.expired }}</span>
          </gl-link>
        </slot>
      </div>
    </template>
    <template #default>
      <gl-dropdown
        ref="newDropdown"
        lazy
        :header-text="i18n.assignAttribute"
        :text="dropdownText"
        :loading="loading"
        class="gl-w-full"
        toggle-class="gl-max-w-100"
        @shown="setFocus"
      >
        <gl-search-box-by-type ref="search" v-model="searchTerm" />
        <gl-dropdown-item
          :data-testid="`no-${issuableAttribute}-item`"
          :is-check-item="true"
          :is-checked="isAttributeChecked($options.noAttributeId)"
          @click="updateAttribute($options.noAttributeId)"
        >
          {{ i18n.noAttribute }}
        </gl-dropdown-item>
        <gl-dropdown-divider />
        <gl-loading-icon
          v-if="$apollo.queries.attributesList.loading"
          size="sm"
          class="gl-py-4"
          data-testid="loading-icon-dropdown"
        />
        <template v-else>
          <gl-dropdown-text v-if="emptyPropsList">
            {{ i18n.noAttributesFound }}
          </gl-dropdown-text>
          <slot
            v-else
            name="list"
            :attributesList="attributesList"
            :isAttributeChecked="isAttributeChecked"
            :updateAttribute="updateAttribute"
          >
            <gl-dropdown-item
              v-for="attrItem in attributesList"
              :key="attrItem.id"
              :is-check-item="true"
              :is-checked="isAttributeChecked(attrItem.id)"
              :data-testid="`${issuableAttribute}-items`"
              @click="updateAttribute(attrItem.id)"
            >
              {{ attrItem.title }}
              <span v-if="isAttributeOverdue(attrItem)">{{ $options.i18n.expired }}</span>
            </gl-dropdown-item>
          </slot>
        </template>
      </gl-dropdown>
    </template>
  </sidebar-editable-item>
</template>
