<template>
  <Table
    :database="database"
    :table="table"
    :fields="fields"
    :primary="primary"
    :views="views"
    :view="view"
    :table-loading="tableLoading"
    :read-only="true"
    store-prefix="template/"
    @selected-view="selectView({ viewId: $event.id })"
  ></Table>
</template>

<script>
import Table from '@baserow/modules/database/components/table/Table'

import FieldService from '@baserow/modules/database/services/field'
import { populateField } from '@baserow/modules/database/store/field'
import ViewService from '@baserow/modules/database/services/view'
import { populateView } from '@baserow/modules/database/store/view'
import { Mutex } from 'async-mutex'

export default {
  name: 'TableTemplate',
  components: { Table },
  props: {
    pageValue: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      database: {},
      table: {},
      fields: [],
      primary: {},
      views: [],
      view: {},
      tableLoading: true,
      mutex: new Mutex(),
    }
  },
  watch: {
    pageValue(value) {
      this.fetchTable(value.database, value.table)
    },
  },
  mounted() {
    this.fetchTable(this.pageValue.database, this.pageValue.table)
  },
  methods: {
    async runExclusiveUnlessAlreadyLocked({ callback, alreadyLocked = false }) {
      if (alreadyLocked) {
        return await callback()
      } else {
        return await this.mutex.runExclusive(callback)
      }
    },
    /**
     * Fetches and prepares all the table, field and view data for the provided
     * database and table.
     */
    async fetchTable(database, table) {
      await this.runExclusiveUnlessAlreadyLocked({
        callback: async () => {
          // If the new table is already selected, then we don't need to do anything.
          if (table.id === this.table.id) {
            return
          }

          this.tableLoading = true
          this.database = database
          this.table = table

          // Fetch and prepare the fields of the given table. The primary field is
          // extracted from the array and moved to a separate object because that is what
          // the `Table` components expects.
          const { data: fieldsData } = await FieldService(
            this.$client
          ).fetchAll(table.id)
          fieldsData.forEach((part, index, d) => {
            populateField(fieldsData[index], this.$registry)
          })
          const primaryIndex = fieldsData.findIndex(
            (item) => item.primary === true
          )
          const primary =
            primaryIndex !== -1 ? fieldsData.splice(primaryIndex, 1)[0] : null
          this.fields = fieldsData
          this.primary = primary

          // Fetch and prepare the views of the given table.
          const { data: viewsData } = await ViewService(this.$client).fetchAll(
            table.id,
            true,
            true
          )
          viewsData.forEach((part, index, d) => {
            populateView(viewsData[index], this.$registry)
          })
          this.views = viewsData

          // After selecting the table, the user expects to see the table data and that is
          // only possible if a view is selected. By calling the `selectView` method
          // without parameters, the first view is selected.
          await this.selectView({ alreadyLocked: true })
        },
      })
    },
    /**
     * Selects the view with the given `viewId`. If no `viewId` is provided, then the
     * first view will be selected.
     */
    async selectView({ viewId = null, alreadyLocked = false }) {
      // If the new view is already selected, we don't need to do anything.
      if (viewId === this.view.id) {
        return
      }

      await this.runExclusiveUnlessAlreadyLocked({
        alreadyLocked,
        callback: async () => {
          // If the new view is already selected, we don't need to do anything.
          if (viewId === this.view.id) {
            return
          }

          this.tableLoading = true

          // If no viewId is provided, we want to select the first the first view.
          const firstView = this.views.length > 0 ? this.views[0] : null
          if (viewId === null && firstView !== null) {
            viewId = firstView.id
          }

          // Update the selected state in all the views.
          this.views.forEach((view) => {
            view._.selected = view.id === viewId
          })

          const view = this.views.find((item) => item.id === viewId)
          this.view = view

          // It might be possible that the view also has some stores that need to be
          // filled with initial data, so we're going to call the fetch function here.
          const type = this.$registry.get('view', view.type)
          await type.fetch(
            { store: this.$store },
            view,
            this.fields,
            this.primary,
            'template/'
          )
          this.tableLoading = false
        },
      })
    },
  },
}
</script>