<template>
  <form class="context__form" @submit.prevent="submit">
    <FormElement :error="fieldHasErrors('name')" class="control">
      <div class="control__elements">
        <input
          ref="name"
          v-model="values.name"
          :class="{ 'input--error': fieldHasErrors('name') }"
          type="text"
          class="input input--small"
          :placeholder="$t('fieldForm.name')"
          @blur="$v.values.name.$touch()"
          @input="isPrefilledWithSuggestedFieldName = false"
          @keydown.enter="handleKeydownEnter($event)"
        />
        <div
          v-if="$v.values.name.$dirty && !$v.values.name.required"
          class="error"
        >
          {{ $t('error.requiredField') }}
        </div>
        <div
          v-else-if="
            $v.values.name.$dirty && !$v.values.name.mustHaveUniqueFieldName
          "
          class="error"
        >
          {{ $t('fieldForm.fieldAlreadyExists') }}
        </div>
        <div
          v-else-if="
            $v.values.name.$dirty &&
            !$v.values.name.mustNotClashWithReservedName
          "
          class="error"
        >
          {{ $t('error.nameNotAllowed') }}
        </div>
        <div
          v-else-if="$v.values.name.$dirty && !$v.values.name.maxLength"
          class="error"
        >
          {{ $t('error.nameTooLong') }}
        </div>
      </div>
    </FormElement>
    <div v-if="forcedType === null" class="control">
      <div class="control__elements">
        <Dropdown
          ref="fieldTypesDropdown"
          v-model="values.type"
          :class="{ 'dropdown--error': $v.values.type.$error }"
          :fixed-items="true"
          small
          @hide="$v.values.type.$touch()"
        >
          <DropdownItem
            v-for="(fieldType, type) in fieldTypes"
            :key="type"
            :icon="fieldType.iconClass"
            :name="fieldType.getName()"
            :value="fieldType.type"
            :disabled="
              (primary && !fieldType.canBePrimaryField) ||
              !fieldType.isEnabled(workspace) ||
              fieldType.isDeactivated(workspace.id)
            "
            @click="clickOnDeactivatedItem($event, fieldType)"
          >
            <i class="select__item-icon" :class="fieldType.iconClass" />
            <span class="select__item-name-text" :title="fieldType.getName()">{{
              fieldType.getName()
            }}</span>
            <i
              v-if="fieldType.isDeactivated(workspace.id)"
              class="iconoir-lock"
            ></i>
            <component
              :is="fieldType.getDeactivatedClickModal(workspace.id)"
              :ref="'deactivatedClickModal-' + fieldType.type"
              :v-if="
                fieldType.isDeactivated(workspace.id) &&
                fieldType.getDeactivatedClickModal(workspace.id)
              "
              :name="$t(fieldType.getName())"
              :workspace="workspace"
            ></component>
          </DropdownItem>
        </Dropdown>
        <div v-if="$v.values.type.$error" class="error">
          {{ $t('error.requiredField') }}
        </div>
      </div>
    </div>
    <template v-if="hasFormComponent">
      <component
        :is="getFormComponent(values.type)"
        ref="childForm"
        :table="table"
        :field-type="values.type"
        :view="view"
        :primary="primary"
        :all-fields-in-table="allFieldsInTable"
        :name="values.name"
        :default-values="defaultValues"
        :database="database"
        @validate="$v.$touch"
        @suggested-field-name="handleSuggestedFieldName($event)"
      />
    </template>

    <FormElement
      v-if="showDescription"
      :error="fieldHasErrors('description')"
      class="control"
    >
      <label class="control__label control__label--small">{{
        $t('fieldForm.description')
      }}</label>
      <div class="control__elements">
        <RichTextEditor
          ref="description"
          :value="editorValue"
          class="field-form__editor rich-text-editor rich-text-editor--fixed-size"
          :editable="true"
          :enter-stop-edit="false"
          :thin-scrollbar="true"
          :enable-rich-text-formatting="false"
          :placeholder="$t('fieldForm.description')"
          @blur="onDescriptionBlur"
        />
      </div>
    </FormElement>
    <slot v-if="!selectedFieldIsDeactivated"></slot>
  </form>
</template>

<script>
import { mapGetters } from 'vuex'
import { required, maxLength } from 'vuelidate/lib/validators'

import { getNextAvailableNameInSequence } from '@baserow/modules/core/utils/string'
import RichTextEditor from '@baserow/modules/core/components/editor/RichTextEditor.vue'
import form from '@baserow/modules/core/mixins/form'
import {
  RESERVED_BASEROW_FIELD_NAMES,
  MAX_FIELD_NAME_LENGTH,
} from '@baserow/modules/database/utils/constants'

// @TODO focus form on open
export default {
  name: 'FieldForm',
  components: { RichTextEditor },
  mixins: [form],
  props: {
    table: {
      type: Object,
      required: true,
    },
    view: {
      type: [Object, null],
      required: false,
      default: null,
    },
    primary: {
      type: Boolean,
      required: false,
      default: false,
    },
    forcedType: {
      type: [String, null],
      required: false,
      default: null,
    },
    allFieldsInTable: {
      type: Array,
      required: true,
    },
    database: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      allowedValues: ['name', 'type', 'description'],
      values: {
        name: '',
        type: this.forcedType || '',
        description: null,
      },
      isPrefilledWithSuggestedFieldName: false,
      oldValueType: null,
      showDescription: false,
    }
  },
  computed: {
    // Return the reactive object that can be updated in runtime.
    workspace() {
      return this.$store.getters['workspace/get'](this.database.workspace.id)
    },
    fieldTypes() {
      return this.$registry.getAll('field')
    },
    hasFormComponent() {
      return !!this.values.type && this.getFormComponent(this.values.type)
    },
    existingFieldId() {
      return this.defaultValues ? this.defaultValues.id : null
    },
    selectedFieldIsDeactivated() {
      try {
        return this.$registry
          .get('field', this.values.type)
          .isDeactivated(this.workspace.id)
      } catch {
        return false
      }
    },
    editorValue() {
      // temp fix to have proper line breaks
      // this will not be needed when RTE will be in minimal mode
      return (this.values.description || '').replaceAll('\n', '<br/>')
    },
    ...mapGetters({
      fields: 'field/getAll',
    }),
    isNameFieldEmptyOrPrefilled() {
      return (
        this.values.name === '' ||
        this.values.name ===
          this.getNextAvailableFieldName(
            this.fieldTypes[this.oldValueType]?.getName()
          ) ||
        this.values.name ===
          this.getNextAvailableFieldName(
            this.fieldTypes[this.values.type]?.getName()
          ) ||
        this.isPrefilledWithSuggestedFieldName
      )
    },
  },
  watch: {
    // if the name field is empty or prefilled by a default value
    // we want to update the name field with the name of the field type
    // when the field type is changed.
    'values.type'(newValueType, oldValueType) {
      this.oldValueType = oldValueType
      if (this.isNameFieldEmptyOrPrefilled) {
        const availableFieldName = this.getNextAvailableFieldName(
          this.fieldTypes[newValueType]?.getName()
        )
        this.values.name = availableFieldName
      }
      this.isPrefilledWithSuggestedFieldName = false
    },
  },
  validations() {
    return {
      values: {
        name: {
          required,
          maxLength: maxLength(MAX_FIELD_NAME_LENGTH),
          mustHaveUniqueFieldName: this.mustHaveUniqueFieldName,
          mustNotClashWithReservedName: this.mustNotClashWithReservedName,
        },
        type: { required },
      },
    }
  },
  methods: {
    mustHaveUniqueFieldName(param) {
      let fields = this.fields
      if (this.existingFieldId !== null) {
        fields = fields.filter((f) => f.id !== this.existingFieldId)
      }
      return !fields.map((f) => f.name).includes(param?.trim())
    },
    mustNotClashWithReservedName(param) {
      return !RESERVED_BASEROW_FIELD_NAMES.includes(param?.trim())
    },
    getFormComponent(type) {
      const fieldType = this.$registry.get('field', type)
      if (fieldType.isEnabled(this.workspace)) {
        return fieldType.getFormComponent()
      }
    },
    showFieldTypesDropdown(target) {
      this.$refs.fieldTypesDropdown.show(target)
    },
    handleSuggestedFieldName(event) {
      if (this.isNameFieldEmptyOrPrefilled) {
        this.isPrefilledWithSuggestedFieldName = true
        const availableFieldName = this.getNextAvailableFieldName(event)
        this.values.name = availableFieldName
      }
    },
    getNextAvailableFieldName(baseName) {
      const excludeNames = this.fields.map((f) => f.name)
      return getNextAvailableNameInSequence(baseName, excludeNames)
    },
    handleKeydownEnter(event) {
      event.preventDefault()
      this.$emit('keydown-enter')
      this.submit()
    },
    clickOnDeactivatedItem(event, fieldType) {
      if (fieldType.isDeactivated(this.workspace.id)) {
        this.$refs[`deactivatedClickModal-${fieldType.type}`][0].show()
      }
    },
    /**
     * This sets the showDescription flag to display description text editor, even
     * if values.description is empty.
     *
     * Used by parent components.
     */
    showDescriptionField() {
      this.showDescription = true
    },
    /**
     * Helper method to get information if description is not empty.
     * Used by parent components
     */
    isDescriptionFieldNotEmpty() {
      this.showDescription = !!this.values.description
      return this.showDescription
    },
    onDescriptionBlur() {
      // Handle blur event on field description text editor.
      // A bit hacky way to get current state of description editor once the
      // edition finished.
      this.values.description = this.$refs.description.editor.getText()
    },
  },
}
</script>