<script setup>
import {defineProps, onMounted, ref, watch} from 'vue'
import {SessionUser} from "@/components/session/token/SessionUser.vue";
import {clearErrors, hasError, parseError} from "@/components/request/HandleGlobalError.vue";
import ShowErrorField from "@/components/global/ShowErrorField.vue";
import bulmaCalendar from "bulma-calendar";
import 'bulma-calendar/src/scss/index.scss';
import Swal from "sweetalert2";
import {IS_DEV} from "@/main.js";

const props = defineProps({
  form_identifier: {
    type: String,
    required: true
  },
  /**
   * This adds the keys to the validators
   * E.g. { validateBookingId: { min: 1, max: 999999999, default: 1, locked: true } }
   */
  validator_options: {
    type: Object,
    default: () => {
      return {};
    }
  },
  /**
   * This adds the options to the fields
   *
   */
  field_options: {
    type: Object,
    default: () => {
      return {};
    }
  },
  /**
   * This adds the attributes to the form field
   * E.g. { name: { placeholder: 'Enter your name', default: 'John Doe', locked: true, visible: false } }
   */
  form_field_options: {
    type: Object,
    default: () => {
      return {};
    }
  },
  /**
   * onSuccessfulSubmission
   * This function is called when the form is successfully submitted
   * @param form
   * @returns {Promise<void>}
   */
  onSuccessfulSubmission: {
    type: Function,
    default: () => {
      return async (form) => {
        console.log(form);
      }
    }
  }
})

const default_validator_options = {
  visible: true,
  allow_auto_fill: false,
  locked: false,
}

const reCAPTCHA_data = ref({
  enabled: false,
  site_key: '',
});

const ratelimit_data = ref({
  enabled: false,
  limit: 0,
  remaining: 0,
  reset: 0,
  warning: null,
});

SessionUser.auth.reCAPTCHA.preCheck.get().then((response) => {
  console.log(response.data.data);
  reCAPTCHA_data.value = response.data.data.recaptcha;
  ratelimit_data.value = response.data.data.rate_limit;
  // If the reCAPTCHA is enabled, load the reCAPTCHA script
  if (reCAPTCHA_data.value.enabled) {
    const script = document.createElement('script');
    script.src = 'https://www.google.com/recaptcha/api.js';
    script.async = true;
    script.defer = true;
    document.head.appendChild(script);
  }
});

// Initialize the calendar
const createCalendar = (element, field) => {
  const calendar = bulmaCalendar.attach(element, {
    startDate: new Date(),
    dateFormat: 'yyyy-MM-dd',
  });
}


// Load all date fields
const renderCalendars = () => {
  console.log('renderCalendars');
  // Get all date fields
  const dateFields = document.querySelectorAll('.date-calendar-input');
  // Loop through the date fields
  dateFields.forEach((field) => {
    console.log(field);
    // Create the calendar
    createCalendar(field);
  });
}

const isUserAdmin = ref(false);
const randomElementId = Math.random().toString(36).substring(7)
const fieldErrors = ref([]);

const validators = [
  {
    ...default_validator_options,
    name: 'validateCustomerNumber',
    description: 'the E-conomic customer number',
    type: 'number',
    required: true,
    min: 1,
    max: 999999999,
    default: SessionUser.user.customer_number,
    locked: SessionUser.functions.hasToken(),
    ...props.validator_options.validateCustomerNumber ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateBookingId',
    description: 'the booking id',
    type: 'number',
    required: true,
    min: 1,
    max: 999999999,
    ...props.validator_options.validateBookingId ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateEmail',
    description: 'the email address',
    type: 'email',
    required: true,
    ...props.validator_options.validateEmail ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateString',
    description: 'the string',
    type: 'text',
    required: true,
    ...props.validator_options.validateString ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateRegistrationNumber',
    description: 'the registration number',
    type: 'text',
    required: true,
    min: 3,
    max: 12,
    ...props.validator_options.validateRegistrationNumber ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateBoolean',
    description: 'the boolean',
    type: 'checkbox',
    required: false,
    ...props.validator_options.validateBoolean ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateDate',
    description: 'the date',
    type: 'date',
    required: true,
    ...props.validator_options.validateDate ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateArray',
    description: 'the array',
    type: 'array',
    required: true,
    ...props.validator_options.validateArray ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateObject',
    description: 'the object',
    type: 'object',
    required: true,
    ...props.validator_options.validateObject ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateInt',
    description: 'the integer',
    type: 'number',
    required: true,
    ...props.validator_options.validateInt ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateDepartmentId',
    description: 'the integer',
    type: 'number',
    required: true,
    min: 1,
    ...props.validator_options.validateDepartmentId ?? {}
  },
  {
    ...default_validator_options,
    name: 'validateRecaptcha',
    description: 'The recaptcha token',
    type: 'text',
    required: true,
    ...props.validator_options.validateRecaptcha ?? {}
  },
];

console.log(validators);

const isValidatorDefined = (validator) => {
  return validators.find((v) => v.name === validator) !== undefined;
}

const getValidator = (validator) => {
  return validators.find((v) => v.name === validator);
}

const fieldValues = ref({});


const field_default = {
  id: null,
  name: null,
  validation: null,
  options: null,
  metadata: {
    description: null,
    required: false,
    locked: false,
    error: null,
    help: null,
    label: null,
    placeholder: null,
    // Displays the field if the value of another field is equal to a specific value
    display_if: null,
    display_if_value: null,
    required_if_display: null,
    allow_multiple: false,
    hidden_value: null,
  }
}

const meta = ref({
  name: null,
  description: null,
  identifier: null,
  submit_button_text: null,
});

const fields = ref([]);

const onSubmit = async () => {
  // Clear the errors
  clearErrors(randomElementId);
  // Submit the form
  SessionUser.objects.forms.functions.submit(getForm()).then((response) => {
    // Check if the response is valid JSON
    if (response.data.data === undefined) {
      parseError(response, randomElementId);
      return;
    }
    // Call the onSuccessfulSubmission function
    props.onSuccessfulSubmission(response.data.data);
    Swal.fire(
        'Success',
        'The form has been submitted',
        'success'
    );
  }).catch((error) => {
    // Check if the error has a response (If it does not, it is an internal error)
    if (!error.response) {
      parseError(error, randomElementId);
      return;
    }
    // Check if the error is a 400 error
    if (error.response.status === 400) {
      fieldErrors.value = error.response.data.data.errors;
    }
    parseError(error, randomElementId);
  }).finally(() => {
    // Check if the reCAPTCHA is enabled
    if (reCAPTCHA_data.value.enabled) {
      // Reset the reCAPTCHA
      grecaptcha.reset();
    }
  });
}

const onReset = async () => {
  // Reset the field values
  fieldValues.value = {};
}


const onFieldChange = async (field, value) => {
  // Check if the field has an error
  if (fieldErrors.value[field.id] !== undefined) {
    fieldErrors.value[field.id] = undefined;
  }
  console.log(field, value);
}

const getFieldName = (field) => {
  try {
    return SessionUser.objects.forms.meta.labels.fields.names[field.name].label;
  }
  catch (e) {
    return field.name;
  }
}

const toggleCheckbox = (field) => {
  //console.log(field);
  // Set the checkbox value to false, if it is undefined
  if (fieldValues.value[field.id] === undefined) {
    fieldValues.value[field.id] = false;
  }
  fieldValues.value[field.id] = !fieldValues.value[field.id];
  //console.log(fieldValues.value[field.id]);
}

const isCheckboxChecked = (field) => {
  // Check if the field is set in the fieldValues object
  if (fieldValues.value[field.id] === undefined) {
    fieldValues.value[field.id] = false;
  }
  return fieldValues.value[field.id] === true;
}

const isFieldLocked = (field) => {
  let result = false;
  const validator = getValidator(field.validation);
  // Check if the locked property is set
  if (validator.locked !== undefined) {
    //console.log('locked is undefined', field);
    result = validator.locked === true;
  }
  //console.log('locked', field, result, validator);
  return result;
}

const getFieldOptions = (field) => {
  try {
    console.log(field);
    let result = {
      ...(field.options !== null ? field.options : {}),
      ...(props.field_options[field.name] ?? {})
    };
    console.log(result);
    return result;
  }
  catch (e) {
    return {};
  }
}

const getFieldMetadata = (field) => {
  try {
    return {
      ...field.metadata,
      ...props.form_field_options[field.name] ?? {}
    };
  }
  catch (e) {
    return {};
  }
}

const addRecaptchaToken = () => {
  if (reCAPTCHA_data.value.enabled && !SessionUser.functions.hasToken()) {
    return {
      g_recaptcha_response: reCAPTCHA_data.value.enabled ? grecaptcha.getResponse() : null,
    }
  }
  return {};
}

const hiddenDefaultValues = () => {
  let result = {};
  for (const field_index in fields.value) {
    const field = fields.value[field_index];
    // Check if the field is currently displayed
    if (isFieldDisplayable(field) === false) {
      // Check if the field has a hidden_value property
      if (field.metadata.hidden_value !== null) {
        // Set the field value to the hidden_value
        result[field.id] = field.metadata.hidden_value;
      }
    }
  }
  return result;
}

const lockedValues = () => {
  let result = {};
  for (const field_index in fields.value) {
    const field = fields.value[field_index];
    // Check if the field is locked
    if (isFieldLocked(field)) {
      // set the field value to the locked value
      result[field.id] = fieldValues.value[field.id] ?? null;
    }
  }
  return result;
}

const getForm = () => {
  // Add the missing fields to the fieldValues object
  const defaultNullValues = [];
  for (let i = 0; i < fields.value.length; i++) {
    defaultNullValues[fields.value[i].id] = null;
  }
  let form = {
    id: props.form_identifier,
    ...defaultNullValues,
    ...fieldValues.value,
    ...hiddenDefaultValues(),
    ...lockedValues(),
    ...addRecaptchaToken()
  }
  // Parse the types
  for (const field_index in fields.value) {
    const key = field_index;
    const field = fields.value[field_index];
    const validator = getValidator(field.validation);
    //console.log(field, validator);
    if (validator.type === 'number') {
      // If the field is a multiple select, parse the values to integers
      if (field.metadata.allow_multiple === true) {
        // If the field isn't set, set it to an empty array
        if (form[field.id] === null) {
          form[field.id] = [];
        }
        form[field.id] = form[field.id].map((value) => parseInt(value));
      } else {
        form[field.id] = parseInt(form[field.id]);
      }
    }
  }
  return form;
}

const setDefaultValues = () => {
  //console.log('setDefaultValues');
  //console.log(fields.value);
  for (const field_index in fields.value) {
    const key = field_index;
    const field = fields.value[field_index];
    const validator = getValidator(field.validation);
    //console.log(field, validator);
    if (validator.default !== undefined) {
      fieldValues.value[field.id] = validator.default;
    }
    // Check if the field has a default value
    if (field.metadata.default !== undefined) {
      // Get the options for the field
      fieldValues.value[field.id] = getFieldDefaultValue(field.id) ?? field.metadata.default ?? null;
    }
  }
}

// Get the form
SessionUser.objects.forms.get.single(props.form_identifier).then((response) => {
  console.log(response);
  meta.value = response.metadata;
  let fields_tmp = [];
  for (const field_index in response.fields) {
    const field = response.fields[field_index];
    const field_metadata = getFieldMetadata(field);
    console.log(getFieldMetadata(field));
    let field_tmp = {
      ...field_default,
      ...field,
      metadata: {
        ...field.metadata,
        ...field_metadata
      }
    };
    console.log(field_tmp);
    fields_tmp.push(field_tmp);
  }
  fields.value = fields_tmp;
  setDefaultValues();
  // Wait one tick before rendering the calendars
  setTimeout(() => {
    renderCalendars();
  }, 0);

}).catch((error) => {
  console.error(error);
  parseError(error, randomElementId);
  fieldErrors.value = error;
});

const getFieldError = (field_id) => {
  return fieldErrors.value[field_id] ?? null;
}

const getFieldDefaultValue = (field_id) => {
  return fieldValues.value[field_id] ?? null;
}

const isFieldRequired = (field) => {
  let result = false;
  // First check if the fields validation is defined
  const validator = getValidator(field.validation);
  if (validator.required !== undefined) {
    result = validator.required;
  }
  // Check if the field is required in the metadata
  if (field.metadata.required !== undefined) {
    result = field.metadata.required;
  }
  // Check if the field has a `required_if_display` property
  if (field.metadata.required_if_display !== undefined) {
    // Check if the field is required
    result = field.metadata.required_if_display === true
  }
  return result;
}

const isValidatorVisible = (validator) => {
  return validator.visible ?? true;
}

const isFieldDisplayable = (field) => {
  let result = true;
  // Check if the validator is visible (by default it is)
  if (isValidatorVisible(getValidator(field.validation)) === false) {
    result = false;
  }
  // Check if the field has a display_if property
  if (field.metadata.display_if !== undefined && field.metadata.display_if_value !== undefined) {
    // Check if the field is displayable
    result = fieldValues.value[field.metadata.display_if] === field.metadata.display_if_value;
  }
  return result;
}

const isFieldSelect = (field) => {
  // Check if the field object has any keys
  let fieldOptions = getFieldOptions(field);
  return Object.keys(fieldOptions).length > 0;
}

const isFieldMultiple = (field) => {
  return field.metadata.allow_multiple === true ?? false;
}

const doesMultipleFieldContainValue = (field, index) => {
  return fieldValues.value[field.id] !== undefined && fieldValues.value[field.id].includes(index);
}

const toggleMultipleField = (field, index) => {
  // Check if the field is set in the fieldValues object
  if (fieldValues.value[field.id] === undefined) {
    fieldValues.value[field.id] = [];
  }
  // Check if the value is already in the array
  if (fieldValues.value[field.id].includes(index)) {
    // Remove the value from the array
    fieldValues.value[field.id] = fieldValues.value[field.id].filter((value) => value !== index);
  } else {
    // Add the value to the array
    fieldValues.value[field.id].push(index);
  }
}

watch(SessionUser.permissions, (permissions) => {
  isUserAdmin.value = permissions.includes('admin');
});

const debugGetForm = () => {
  console.log(getForm());
}

</script>

<template>
  <!-- {{ fieldValues }} -->
  <form @submit.prevent="onSubmit" @reset.prevent="onReset">
    <h2 class="title is-5">{{meta.name}}</h2>
    <h4 class="subtitle is-6">{{meta.description}}</h4>
    <!-- Hidden fields -->
    <input type="hidden" name="form_identifier" :value="props.form_identifier">
    <!-- Fields -->
    <div v-for="field in fields" :key="field.id" class="field" v-show="isFieldDisplayable(field)">
      <div class="columns is-multiline is-mobile is-vcentered">
        <div class="column is-narrow">
          <label class="label mb-0" :for="field.id">{{ field.metadata.label }}</label>
          <p class="is-size-7 has-text-grey">{{ field.metadata.help }}</p>
        </div>
        <div class="column">
          <div class="tags is-float-right">
            <span class="tag is-outlined" v-if="isFieldRequired(field)">Påkrævet</span>
            <span class="tag is-outlined" v-if="isFieldLocked(field)">Låst</span>
          </div>
        </div>
      </div>
      <template v-if="isValidatorDefined(field.validation)">
        <!-- Select (If the field has options) -->
        <div class="select is-fullwidth" v-if="isFieldSelect(field)">
          <!-- Multiple select -->
          <template v-if="isFieldMultiple(field)">
            <div class="buttons is-multiline">
              <template v-for="(option, index) in getFieldOptions(field)">
                <div
                    class="button"
                    :class="{
                  'is-link': doesMultipleFieldContainValue(field, index),
                  'is-inverted': doesMultipleFieldContainValue(field, index)
                }"
                    @click="toggleMultipleField(field, index)"
                >
                  <span class="icon">
                    <i class="fas fa-check" v-if="doesMultipleFieldContainValue(field, index)"></i>
                  </span>
                  <span>
                    {{ option }}
                  </span>
                  <span class="icon"></span>
                </div>
              </template>
            </div>
          </template>
          <!-- Single select -->
          <template v-else>
            <select :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.value)" v-model="fieldValues[field.id]" :disabled="isFieldLocked(field)">
              <!-- Loop through the options, while creating the select options -->
              <template v-for="(option, index) in getFieldOptions(field)">
                <option :value="index">{{ option }}</option>
              </template>
            </select>
          </template>
        </div>
        <!-- Number -->
        <input
            class="input is-link"
            v-else-if="getValidator(field.validation).type === 'number'"
            :type="getValidator(field.validation).type"
            :min="getValidator(field.validation).min ?? 0"
            :max="getValidator(field.validation).max ?? ''"
            :required="isFieldRequired(field)"
            :name="field.id" :id="field.id"
            @change="onFieldChange(field, $event.target.value)"
            v-model="fieldValues[field.id]"
            v-bind:disabled="isFieldLocked(field)"
            v-bind:placeholder="field.metadata.placeholder"
        >
        <!-- Text -->
        <input class="input is-link" v-else-if="getValidator(field.validation).type === 'text'" :type="getValidator(field.validation).type" :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.value)" v-model="fieldValues[field.id]" v-bind:disabled="isFieldLocked(field)" v-bind:placeholder="field.metadata.placeholder">
        <!-- Email -->
        <input class="input is-link" v-else-if="getValidator(field.validation).type === 'email'" :type="getValidator(field.validation).type" :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.value)" v-model="fieldValues[field.id]" :disabled="isFieldLocked(field)" v-bind:placeholder="field.metadata.placeholder" v-if="isFieldDisplayable(field)">
        <!-- Checkbox -->
        <div class="control" v-else-if="getValidator(field.validation).type === 'checkbox'" :type="getValidator(field.validation).type" :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.checked)">
          <input class="switch" :type="getValidator(field.validation).type" v-bind:required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.checked)" :checked="isCheckboxChecked(field)">
          <label :for="field.id" @click="toggleCheckbox(field)" :disabled="isFieldLocked(field)"></label>
        </div>
        <!-- Date -->
        <div v-else-if="getValidator(field.validation).type === 'date'">
          <input
              class="input is-link"
              :type="getValidator(field.validation).type"
              :required="isFieldRequired(field)"
              :name="field.id"
              :id="field.id"
              @change="onFieldChange(field, $event.target.value)"
              v-model="fieldValues[field.id]"
              :disabled="isFieldLocked(field)"
              v-bind:placeholder="field.metadata.placeholder"
          >
        </div>
      </template>
      <!-- If the validator is not defined -->
      <template v-else>
        <div class="message is-warning">
          <div class="message-body">
            <p>Validator is unknown for field `{{ field.name }}` with identifier `{{ field.id }}`</p>
          </div>
        </div>
      </template>
      <!-- Field error handling -->
      <p class="help is-danger" v-if="getFieldError(field.id)">
        {{ getFieldError(field.id).error }}
      </p>
    </div>
    <!-- reCaptcha, if enabled -->
    <div class="field" v-if="reCAPTCHA_data.enabled && !SessionUser.functions.hasToken()">
      <div class="control">
        <div class="g-recaptcha" :data-sitekey="reCAPTCHA_data.site_key"></div>
      </div>
    </div>
    <!-- Error handling -->
    <show-error-field :error="randomElementId" v-if="hasError(randomElementId)" />
    <!-- Submit and reset buttons -->
    <div class="field">
      <p class="control buttons">
        <button class="button is-link" type="submit">{{ meta.submit_button_text }}</button>
        <button class="button is-light" type="reset">{{ SessionUser.objects.global.language.reset }}</button>
        <button class="button is-warning is-inverted" @click="debugGetForm" v-if="IS_DEV">Debug</button>
      </p>
    </div>
  </form>
</template>

<style scoped>

</style>