<template>
  <div class="grid-view__cell grid-field-many-to-many__cell active">
    <div class="grid-field-many-to-many__list">
      <component
        :is="publicGrid || !canAccessLinkedTable ? 'span' : 'a'"
        v-for="item in value"
        :key="item.id"
        class="grid-field-many-to-many__item"
        @click.prevent="showForeignRowModal(item)"
      >
        <span
          class="grid-field-many-to-many__name"
          :class="{
            'grid-field-link-row__unnamed':
              item.value === null || item.value === '',
          }"
          :title="item.value"
        >
          {{
            item.value || $t('gridViewFieldLinkRow.unnamed', { value: item.id })
          }}
        </span>
        <span
          v-if="itemLoadingId === item.id"
          class="grid-field-many-to-many__loading"
        ></span>
        <a
          v-else-if="canAccessLinkedTable"
          class="grid-field-many-to-many__remove"
          @click.prevent.stop="removeValue($event, value, item.id)"
        >
          <i class="iconoir-cancel"></i>
        </a>
      </component>
      <a
        v-if="canAccessLinkedTable"
        class="grid-field-many-to-many__item grid-field-many-to-many__item--link"
        @click.prevent="showModal()"
      >
        <i class="iconoir-plus"></i>
      </a>
    </div>
    <SelectRowModal
      v-if="canAccessLinkedTable"
      ref="selectModal"
      :table-id="field.link_row_table_id"
      :new-row-presets="presetsForNewRowInLinkedTable"
      :view-id="field.link_row_limit_selection_view_id"
      :value="value"
      :multiple="true"
      @selected="addValue(value, $event)"
      @unselected="removeValue({}, value, $event.row.id)"
      @hidden="hideModal"
    ></SelectRowModal>
    <ForeignRowEditModal
      v-if="canAccessLinkedTable"
      ref="rowEditModal"
      :table-id="field.link_row_table_id"
      :fields-sortable="false"
      :can-modify-fields="false"
      :read-only="readOnly"
      @hidden="hideModal"
      @refresh-row="$emit('refresh-row')"
    ></ForeignRowEditModal>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

import { isElement } from '@baserow/modules/core/utils/dom'
import gridField from '@baserow/modules/database/mixins/gridField'
import linkRowField from '@baserow/modules/database/mixins/linkRowField'
import SelectRowModal from '@baserow/modules/database/components/row/SelectRowModal'
import ForeignRowEditModal from '@baserow/modules/database/components/row/ForeignRowEditModal'
import { notifyIf } from '@baserow/modules/core/utils/error'
import { DatabaseApplicationType } from '@baserow/modules/database/applicationTypes'
import { isPrintableUnicodeCharacterKeyPress } from '@baserow/modules/core/utils/events'

export default {
  name: 'GridViewFieldLinkRow',
  components: { ForeignRowEditModal, SelectRowModal },
  mixins: [gridField, linkRowField],
  data() {
    return {
      modalOpen: false,
      itemLoadingId: -1,
    }
  },
  computed: {
    // Return the reactive object that can be updated in runtime.
    workspace() {
      return this.$store.getters['workspace/get'](this.workspaceId)
    },
    canAccessLinkedTable() {
      const linkedTable = this.allTables.find(
        ({ id }) => id === this.field.link_row_table_id
      )

      if (!linkedTable) {
        return false
      }

      return (
        this.$hasPermission(
          'database.table.read',
          linkedTable,
          this.workspace.id
        ) && !this.readOnly
      )
    },
    allTables() {
      const databaseType = DatabaseApplicationType.getType()
      return this.$store.getters['application/getAll'].reduce(
        (tables, application) => {
          if (application.type === databaseType) {
            return tables.concat(application.tables || [])
          }
          return tables
        },
        []
      )
    },
  },
  beforeCreate() {
    this.$options.computed = {
      ...(this.$options.computed || {}),
      ...mapGetters({
        publicGrid: 'page/view/public/getIsPublic',
      }),
    }
  },
  methods: {
    select() {
      // While the field is selected we want to open the select row toast by pressing
      // the enter key.
      this.$el.keydownEvent = (event) => {
        // If the tab or arrow keys are pressed we don't want to do anything because
        // the GridViewField component will select the next field.
        const ignoredKeys = [
          'Tab',
          'ArrowLeft',
          'ArrowUp',
          'ArrowRight',
          'ArrowDown',
        ]
        if (ignoredKeys.includes(event.key)) {
          return
        }

        // If the space bar key is pressed, we don't want to do anything because it
        // should open the row edit modal.
        if (event.key === ' ') {
          return
        }

        // When the enter key, or any printable character is pressed when not editing
        // the value we want to show the select row modal.
        if (
          !this.modalOpen &&
          (event.key === 'Enter' || isPrintableUnicodeCharacterKeyPress(event))
        ) {
          this.showModal()
        }
      }
      document.body.addEventListener('keydown', this.$el.keydownEvent)
    },
    beforeUnSelect() {
      document.body.removeEventListener('keydown', this.$el.keydownEvent)
    },
    /**
     * If the user clicks inside the select row or file modal we do not want to
     * unselect the field. The modals lives in the root of the body element and not
     * inside the cell, so the system naturally wants to unselect when the user clicks
     * inside one of these contexts.
     */
    canUnselectByClickingOutside(event) {
      if (!this.canAccessLinkedTable) {
        return true
      }

      const openModals = [
        ...this.$refs.selectModal.$refs.modal.moveToBody.children.map(
          (child) => child.$el
        ),
        this.$refs.selectModal.$el,
        ...this.$refs.rowEditModal.$refs.modal.$refs.modal.moveToBody.children.map(
          (child) => child.$el
        ),
        this.$refs.rowEditModal.$refs.modal.$el,
      ]

      return (
        // If the user clicks inside the select or row edit modal, we don't want to
        // allow unselecting.
        !openModals.some((modal) => {
          return isElement(modal, event.target)
        }) &&
        // If an element is not part of the body anymore, then it was deleted, and then
        // we don't have to unselect. This can for example happen when the user clicks
        // on something that will deleted because of it.
        document.body.contains(event.target)
      )
    },
    /**
     * Prevent unselecting the field cell by changing the event. Because the deleted
     * item is not going to be part of the dom anymore after deleting it will get
     * noticed as if the user clicked outside the cell which wasn't the case.
     */
    removeValue(event, value, id) {
      event.preventFieldCellUnselect = true
      return linkRowField.methods.removeValue.call(this, event, value, id)
    },
    showModal() {
      if (!this.canAccessLinkedTable) {
        return
      }

      this.modalOpen = true
      this.$refs.selectModal.show()
    },
    hideModal() {
      this.modalOpen = false
    },
    /**
     * While the modal is open, all key combinations related to the field must be
     * ignored.
     */
    canSelectNext() {
      return !this.modalOpen
    },
    canKeyDown() {
      return !this.modalOpen
    },
    canKeyboardShortcut() {
      return !this.modalOpen
    },
    async showForeignRowModal(item) {
      // It's not possible to open the related row when the view is shared publicly
      // because the visitor doesn't have the right permissions.
      if (this.publicGrid || !this.canAccessLinkedTable) {
        return
      }

      this.itemLoadingId = item.id
      try {
        await this.$refs.rowEditModal.show(item.id)
        this.modalOpen = true
      } catch (error) {
        notifyIf(error)
      }
      this.itemLoadingId = -1
    },
  },
}
</script>