import Vue from 'vue'
import { clone } from '@baserow/modules/core/utils/object'

/**
 * Serializes a row to make sure that the values are according to what the API expects.
 *
 * If a field doesn't have a value it will be assigned the empty value of the field
 * type.
 */
export function prepareRowForRequest(row, fields, registry) {
  return fields.reduce((preparedRow, field) => {
    const name = `field_${field.id}`
    const fieldType = registry.get('field', field._.type.type)

    if (fieldType.isReadOnly) {
      return preparedRow
    }

    preparedRow[name] = Object.prototype.hasOwnProperty.call(row, name)
      ? (preparedRow[name] = fieldType.prepareValueForUpdate(field, row[name]))
      : fieldType.getEmptyValue(field)

    return preparedRow
  }, {})
}

/**
 * This helper function prepares objects that can be used to update a row with an
 * immediate user experience.
 *
 * newRowValues: contains an object of values that can immediately be applied to the
 * row. It will hold the updated value, and the related field values we can can
 * update optimisticly.
 *
 * oldRowValues: contains an object with the same keys as the newRowValues, but with
 * their old values. This can be used to revert back to the old values if something
 * fails.
 *
 * updateRequestValues: contains the values that new values prepared for an API
 * request update. These are the ones you want to pass into the service.
 */
export function prepareNewOldAndUpdateRequestValues(
  row,
  allFields,
  field,
  value,
  oldValue,
  registry
) {
  const newRowValues = {
    id: row.id,
    [`field_${field.id}`]: value,
  }
  const oldRowValues = {
    id: row.id,
    [`field_${field.id}`]: oldValue,
  }
  const updateRequestValues = {}

  // Loop over all fields except the one that we're going to update, to figure out
  // if the `onRowChange` return value of the field has changed. If so, we want to
  // add that to immediately add that to the `newRowValues` immediately, so that the
  // update feels instant to the user.
  allFields
    .filter((f) => f.id !== field.id)
    .forEach((fieldToCall) => {
      const fieldType = registry.get('field', fieldToCall.type)
      const fieldID = `field_${fieldToCall.id}`
      const currentFieldValue = row[fieldID]
      const optimisticFieldValue = fieldType.onRowChange(
        row,
        fieldToCall,
        currentFieldValue
      )

      if (currentFieldValue !== optimisticFieldValue) {
        newRowValues[fieldID] = optimisticFieldValue
        oldRowValues[fieldID] = currentFieldValue
      }
    })

  const fieldType = registry.get('field', field.type)
  const updateValue = fieldType.prepareValueForUpdate(field, value)
  updateRequestValues[`field_${field.id}`] = updateValue

  return { newRowValues, oldRowValues, updateRequestValues }
}

/**
 * Returns an object only containing the read-only values of the row, and the id.
 * This can be used to update a row with the return data after making an update
 * request. The reason we need to do this, is because the other values might have
 * changed in the meantime, and they should not be updated.
 */
export function extractRowReadOnlyValues(row, allFields, registry) {
  const readOnlyValues = { id: row.id }
  allFields.forEach((field) => {
    const fieldType = registry.get('field', field.type)
    const fieldKey = `field_${field.id}`
    if (
      (fieldType.getIsReadOnly() || field.read_only) &&
      Object.prototype.hasOwnProperty.call(row, fieldKey)
    ) {
      readOnlyValues[fieldKey] = row[fieldKey]
    }
  })
  return readOnlyValues
}

/**
 * Call the given updateFunction with the current value of the row metadata type and
 * set the new value. If the row metadata type does not exist yet, it will be
 * created.
 */
export function updateRowMetadataType(row, rowMetadataType, updateFunction) {
  const currentValue = row._.metadata[rowMetadataType]
  const newValue = updateFunction(currentValue)

  if (!Object.prototype.hasOwnProperty.call(row._.metadata, rowMetadataType)) {
    const metaDataCopy = clone(row._.metadata)
    metaDataCopy[rowMetadataType] = newValue
    Vue.set(row._, 'metadata', metaDataCopy)
  } else {
    Vue.set(row._.metadata, rowMetadataType, newValue)
  }
}

/**
 * Return the metadata of a row. If the metadata does not exist yet, it will be created
 * as an empty object.
 */
export function getRowMetadata(row, metadata = {}) {
  return { ...metadata, ...(row.metadata || {}) }
}