<template>
  <div
    class="grid-view__rows"
    :style="{
      transform: `translateY(${rowsTop}px) translateX(${leftOffset || 0}px)`,
    }"
  >
    <GridViewRow
      v-for="(row, index) in rows"
      :key="`row-${row._.persistentId}`"
      :groups="groupsPerRow[row.id]"
      :view="view"
      :workspace-id="workspaceId"
      :row="row"
      :rendered-fields="renderedFields"
      :visible-fields="visibleFields"
      :all-fields-in-table="allFieldsInTable"
      :primary-field-is-sticky="primaryFieldIsSticky"
      :field-widths="fieldWidths"
      :include-row-details="includeRowDetails"
      :include-group-by="includeGroupBy"
      :decorations-by-place="decorationsByPlace"
      :read-only="readOnly"
      :can-drag="view.sortings.length === 0 && activeGroupBys.length === 0"
      :store-prefix="storePrefix"
      :row-identifier-type="view.row_identifier_type"
      :count="index + rowsStartIndex + bufferStartIndex + 1"
      v-on="$listeners"
    />
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

import GridViewRow from '@baserow/modules/database/components/view/grid/GridViewRow'
import gridViewHelpers from '@baserow/modules/database/mixins/gridViewHelpers'

export default {
  name: 'GridViewRows',
  components: { GridViewRow },
  mixins: [gridViewHelpers],
  props: {
    /**
     * The visible fields that are within the viewport. The other ones are not rendered
     * for performance reasons.
     */
    renderedFields: {
      type: Array,
      required: true,
    },
    /**
     * The fields that are chosen to be visible within the view.
     */
    visibleFields: {
      type: Array,
      required: true,
    },
    /**
     * All the fields in the table, regardless of the visibility, or whether they
     * should be rendered.
     */
    allFieldsInTable: {
      type: Array,
      required: true,
    },
    decorationsByPlace: {
      type: Object,
      required: true,
    },
    leftOffset: {
      type: Number,
      required: false,
      default: 0,
    },
    view: {
      type: Object,
      required: true,
    },
    includeRowDetails: {
      type: Boolean,
      required: false,
      default: () => false,
    },
    includeGroupBy: {
      type: Boolean,
      required: false,
      default: () => false,
    },
    readOnly: {
      type: Boolean,
      required: true,
    },
    workspaceId: {
      type: Number,
      required: true,
    },
    primaryFieldIsSticky: {
      type: Boolean,
      required: false,
      default: () => true,
    },
  },
  computed: {
    fieldWidths() {
      const fieldWidths = {}
      this.visibleFields.forEach((field) => {
        fieldWidths[field.id] = this.getFieldWidth(field.id)
      })
      return fieldWidths
    },
    /**
     * This computed property prepares an array with clear instructions on the groups
     * for each row. It will hold data whether the row is the start of a group, or the
     * end, and for which group that is.
     *
     * We're calculating this for all the rows in the buffer because that way we can
     * immediately render the correct borders of the rows, even if the user is
     * scrolling fast through them.
     *
     * It returns a structure like:
     *
     * {
     *   [rowId]: [
     *     {
     *       id: groupById,
     *       start: false,
     *       end: true,
     *       groupBy: {}
     *     }
     *   ]
     * }
     */
    groupsPerRow() {
      const groupBys = this.activeGroupBys
      const rows = this.allRows
      const groups = {}
      const fieldTypeMap = {}
      const fieldMap = {}

      rows.forEach((row, index) => {
        const previousRow = rows[index - 1]
        const nextRow = rows[index + 1]

        let lastGroupStart = false
        let lastGroupEnd = false

        const startEndPerGroup = groupBys.map((groupBy) => {
          let start = false // The start of a group based on the group by value.
          let end = false // The end of a group based on the group by value.
          let fieldType = fieldTypeMap[groupBy.field]
          let field = fieldMap[groupBy.field]

          if (fieldType === undefined) {
            field = this.allFieldsInTable.find((f) => f.id === groupBy.field)
            fieldType = this.$registry.get('field', field.type)
            fieldMap[groupBy.field] = field
            fieldTypeMap[groupBy.field] = fieldType
          }

          if (
            previousRow === undefined ||
            !fieldType.isEqual(
              field,
              previousRow[`field_${groupBy.field}`],
              row[`field_${groupBy.field}`]
            ) ||
            lastGroupStart
          ) {
            start = true
          }

          if (
            nextRow === undefined ||
            !fieldType.isEqual(
              field,
              nextRow[`field_${groupBy.field}`],
              row[`field_${groupBy.field}`]
            ) ||
            lastGroupEnd
          ) {
            end = true
          }

          lastGroupStart = start
          lastGroupEnd = end

          return {
            id: groupBy.id,
            start,
            end,
            groupBy,
          }
        })

        groups[row.id] = startEndPerGroup
      })
      return groups
    },
  },
  beforeCreate() {
    this.$options.computed = {
      ...(this.$options.computed || {}),
      ...mapGetters({
        rows: this.$options.propsData.storePrefix + 'view/grid/getRows',
        allRows: this.$options.propsData.storePrefix + 'view/grid/getAllRows',
        rowsTop: this.$options.propsData.storePrefix + 'view/grid/getRowsTop',
        rowsStartIndex:
          this.$options.propsData.storePrefix + 'view/grid/getRowsStartIndex',
        bufferStartIndex:
          this.$options.propsData.storePrefix + 'view/grid/getBufferStartIndex',
        activeGroupBys:
          this.$options.propsData.storePrefix + 'view/grid/getActiveGroupBys',
      }),
    }
  },
}
</script>