<template>
  <Modal
    ref="modal"
    :full-height="hasRightSidebar"
    :right-sidebar="hasRightSidebar"
    :content-scrollable="hasRightSidebar"
    :right-sidebar-scrollable="false"
    :collapsible-right-sidebar="true"
    @hidden="hidden"
  >
    <template #actions>
      <component
        :is="actionComponent"
        v-for="(actionComponent, i) in rowModalActionComponents"
        :key="i"
        :database="database"
        :table="table"
        :row="row"
      >
      </component>
    </template>
    <template #content>
      <div v-if="enableNavigation" class="row-edit-modal__navigation">
        <div v-if="navigationLoading" class="loading"></div>
        <template v-else>
          <a
            class="row-edit-modal__navigation-item"
            @click="$emit('navigate-previous', previousRow)"
          >
            <i class="iconoir-nav-arrow-up"></i>
          </a>
          <a
            class="row-edit-modal__navigation-item"
            @click="$emit('navigate-next', nextRow)"
          >
            <i class="iconoir-nav-arrow-down"></i>
          </a>
        </template>
      </div>
      <div class="box__title">
        <h2 class="row-modal__title">
          {{ heading }}
        </h2>
      </div>
      <RowEditModalFieldsList
        :primary-is-sortable="primaryIsSortable"
        :fields="visibleFields"
        :sortable="!readOnly && fieldsSortable"
        :can-modify-fields="!readOnly && canModifyFields"
        :hidden="false"
        :read-only="readOnly"
        :row="row"
        :view="view"
        :table="table"
        :database="database"
        :all-fields-in-table="allFieldsInTable"
        @field-updated="$emit('field-updated', $event)"
        @field-deleted="$emit('field-deleted')"
        @order-fields="$emit('order-fields', $event)"
        @toggle-field-visibility="$emit('toggle-field-visibility', $event)"
        @update="update"
        @refresh-row="$emit('refresh-row', $event)"
      ></RowEditModalFieldsList>
      <RowEditModalHiddenFieldsSection
        v-if="hiddenFields.length"
        :show-hidden-fields="showHiddenFields"
        @toggle-hidden-fields-visibility="
          $emit('toggle-hidden-fields-visibility')
        "
      >
        <RowEditModalFieldsList
          :primary-is-sortable="primaryIsSortable"
          :fields="hiddenFields"
          :sortable="false"
          :hidden="true"
          :read-only="readOnly"
          :row="row"
          :view="view"
          :table="table"
          :database="database"
          :all-fields-in-table="allFieldsInTable"
          @field-updated="$emit('field-updated', $event)"
          @field-deleted="$emit('field-deleted')"
          @toggle-field-visibility="$emit('toggle-field-visibility', $event)"
          @update="update"
          @refresh-row="$emit('refresh-row', $event)"
        >
        </RowEditModalFieldsList>
      </RowEditModalHiddenFieldsSection>
      <div
        v-if="
          !readOnly &&
          canModifyFields &&
          $hasPermission(
            'database.table.create_field',
            table,
            database.workspace.id
          )
        "
        class="actions"
      >
        <span ref="createFieldContextLink">
          <ButtonText
            icon="iconoir-plus"
            @click="
              $refs.createFieldContext.toggle($refs.createFieldContextLink)
            "
          >
            {{ $t('rowEditModal.addField') }}
          </ButtonText></span
        >

        <CreateFieldContext
          ref="createFieldContext"
          :table="table"
          :view="view"
          :all-fields-in-table="allFieldsInTable"
          :database="database"
          @field-created="$emit('field-created', $event)"
          @field-created-callback-done="
            $emit('field-created-callback-done', $event)
          "
        ></CreateFieldContext>
      </div>
    </template>
    <template #sidebar>
      <RowEditModalSidebar
        :row="row"
        :table="table"
        :database="database"
        :fields="allFieldsInTable"
        :read-only="readOnly"
      ></RowEditModalSidebar>
    </template>
  </Modal>
</template>

<script>
import { mapGetters } from 'vuex'
import modal from '@baserow/modules/core/mixins/modal'
import CreateFieldContext from '@baserow/modules/database/components/field/CreateFieldContext'
import RowEditModalFieldsList from './RowEditModalFieldsList.vue'
import RowEditModalHiddenFieldsSection from './RowEditModalHiddenFieldsSection.vue'
import RowEditModalSidebar from './RowEditModalSidebar.vue'
import { getPrimaryOrFirstField } from '@baserow/modules/database/utils/field'

export default {
  name: 'RowEditModal',
  components: {
    CreateFieldContext,
    RowEditModalFieldsList,
    RowEditModalHiddenFieldsSection,
    RowEditModalSidebar,
  },
  mixins: [modal],
  props: {
    database: {
      type: Object,
      required: true,
    },
    table: {
      type: Object,
      required: true,
    },
    view: {
      type: [Object, null],
      required: false,
      default: null,
    },
    allFieldsInTable: {
      type: Array,
      required: true,
    },
    primaryIsSortable: {
      type: Boolean,
      required: false,
      default: false,
    },
    visibleFields: {
      type: Array,
      required: true,
    },
    hiddenFields: {
      type: Array,
      required: false,
      default: () => [],
    },
    showHiddenFields: {
      type: Boolean,
      required: false,
      default: false,
    },
    rows: {
      type: Array,
      required: true,
    },
    readOnly: {
      type: Boolean,
      required: true,
    },
    enableNavigation: {
      type: Boolean,
      required: false,
      default: false,
    },
    fieldsSortable: {
      type: Boolean,
      required: false,
      default: () => true,
    },
    canModifyFields: {
      type: Boolean,
      required: false,
      default: () => true,
    },
  },
  computed: {
    ...mapGetters({
      navigationLoading: 'rowModalNavigation/getLoading',
    }),
    modalRow() {
      return this.$store.getters['rowModal/get'](this._uid)
    },
    rowId() {
      return this.modalRow.id
    },
    rowExists() {
      return this.modalRow.exists
    },
    row() {
      return this.modalRow.row
    },
    rowIndex() {
      return this.rows.findIndex((r) => r !== null && r.id === this.rowId)
    },
    nextRow() {
      return this.rowIndex !== -1 && this.rows.length > this.rowIndex + 1
        ? this.rows[this.rowIndex + 1]
        : null
    },
    previousRow() {
      return this.rowIndex > 0 ? this.rows[this.rowIndex - 1] : null
    },
    heading() {
      const field = getPrimaryOrFirstField(this.visibleFields)

      if (!field) {
        return null
      }

      const name = `field_${field.id}`
      if (Object.prototype.hasOwnProperty.call(this.row, name)) {
        return this.$registry
          .get('field', field.type)
          .toHumanReadableString(field, this.row[name])
      } else {
        return null
      }
    },
    activeSidebarTypes() {
      const allSidebarTypes = this.$registry.getOrderedList('rowModalSidebar')
      return allSidebarTypes.filter(
        (type) =>
          type.isDeactivated(this.database, this.table, this.readOnly) ===
            false && type.getComponent()
      )
    },
    hasRightSidebar() {
      return this.activeSidebarTypes.length > 0
    },
    canSubscribeToRowUpdates() {
      return (
        this.$hasPermission(
          'database.table.listen_to_all',
          this.table,
          this.database.workspace.id
        ) &&
        // If the row ID is not an integer, it could mean that the row hasn't been
        // created in the backend yet.
        Number.isInteger(this.rowId)
      )
    },
    rowModalActionComponents() {
      return this.activeSidebarTypes
        .map((type) => type.getActionComponent(this.row))
        .filter((actionComponent) => actionComponent !== null)
    },
  },
  watch: {
    /**
     * It could happen that the view doesn't always have all the rows buffered. When
     * the modal is opened, it will find the correct row by looking through all the
     * rows of the view. If a filter changes, the existing row could be removed from the
     * buffer while the user still wants to edit the row because the modal is open. In
     * that case, we will keep a copy in the `rowModal` store, which will also listen
     * for real time update events to make sure the latest information is always
     * visible. If the buffer of the view changes and the row does exist, we want to
     * switch to that version to maintain reactivity between the two.
     */
    rows(value) {
      const row = value.find((r) => r !== null && r.id === this.rowId)
      if (row === undefined && this.rowExists) {
        this.$store.dispatch('rowModal/doesNotExist', {
          componentId: this._uid,
        })
      } else if (row !== undefined && !this.rowExists) {
        this.$store.dispatch('rowModal/doesExist', {
          componentId: this._uid,
          row,
        })
      } else if (row !== undefined) {
        // If the row already exists and it has changed, we need to replace it,
        // otherwise we might loose reactivity.
        this.$store.dispatch('rowModal/replace', {
          componentId: this._uid,
          row,
        })
      }
    },
    rowId(newValue, oldValue) {
      if (this.canSubscribeToRowUpdates) {
        if (oldValue > 0) {
          this.$realtime.unsubscribe('row', {
            table_id: this.table.id,
            row_id: oldValue,
          })
        }
        if (newValue > 0) {
          this.$realtime.subscribe('row', {
            table_id: this.table.id,
            row_id: newValue,
          })
        }
      }
    },
  },
  methods: {
    show(rowId, rowFallback = {}, ...args) {
      const row = this.rows.find((r) => r !== null && r.id === rowId)
      this.$store.dispatch('rowModal/open', {
        tableId: this.table.id,
        componentId: this._uid,
        id: rowId,
        row: row || rowFallback,
        exists: !!row,
      })
      if (this.canSubscribeToRowUpdates) {
        this.$realtime.subscribe('row', {
          table_id: this.table.id,
          row_id: rowId,
        })
      }
      this.getRootModal().show(...args)
    },
    hidden(...args) {
      if (this.canSubscribeToRowUpdates) {
        this.$realtime.unsubscribe('row', {
          table_id: this.table.id,
          row_id: this.rowId,
        })
      }
      this.$store.dispatch('rowModal/clear', { componentId: this._uid })
      this.$emit('hidden', { row: this.row })
    },
    /**
     * Because the modal can't update values by himself, an event will be called to
     * notify the parent component to actually update the value.
     */
    update(context) {
      context.table = this.table
      this.$emit('update', context)
    },
  },
}
</script>