(function ($, Drupal, once) {
  "use strict";

  Drupal.behaviors.castorcitoFormValidation = {
    attach: function (context, settings) {
      const fields = drupalSettings.field_components;
      $(once('cfv', '.ccfv--form', context)).on('submit', function (event) {
        let errorData = {
          hasErrors: false,
          firstErrorElement: null
        };

        Object.entries(fields).forEach(([key, field]) => {
          const form = $(this);
          const textareas = form.find(`textarea[name^="${key}["]`);

          textareas.each(function (index) {
            const textarea = $(this);
            const value = textarea.val().trim();

            if (value !== '') {
              let model;
              try {
                model = JSON.parse(value);
              } catch (error) {
                console.error("Error parsing JSON in:", key, error);
                return;
              }

              const fieldName = `${key}_${index}_${model.type}`;

              // Validate the main model.
              validateModel(event, model, fieldName, 0, errorData);

              // Validate container type subfields.
              const containerFields = getContainerFields(model, 'container');
              Object.entries(containerFields).forEach(([cckey, ccfield]) => {
                ccfield.forEach((ccModel, ccfIndex) => {
                  const ccFieldName = `${ccfIndex}_${cckey}`;
                  validateModel(event, ccModel, fieldName, ccFieldName, errorData);
                });
              });

              // Validate advanced container type subfields.
              const advancedContainerFields = getContainerFields(model, 'advanced_container');
              Object.entries(advancedContainerFields).forEach(([cackey, cacfield]) => {
                cacfield.forEach((cacModel, cacfIndex) => {
                  // Head
                  const cacheadFieldName = `${cacfIndex}_head_${cackey}`;
                  validateModel(event, cacModel.head, fieldName, cacheadFieldName, errorData);

                  // Body
                  const cacBodyFieldName = `${cacfIndex}_body_${cackey}`;
                  validateModel(event, cacModel.body, fieldName, cacBodyFieldName, errorData);
                });
              });
            }
          });
        });

        // Scroll to the first error detected
        if (errorData.hasErrors && errorData.firstErrorElement) {
          $('html, body').stop().animate({
            scrollTop: errorData.firstErrorElement.offset().top - 100
          }, 500);
        }
      });
    },
  };

  /**
   * Function to validate a model and handle errors.
   *
   * @param {Event} event - The submit event, which is prevented if validation fails.
   * @param {Object} model - The model containing form data.
   * @param {string} fieldName - The base name of the fields being validated.
   * @param {string} ccFieldName - The identifier for the container that groups related fields.
   * @param {Object} errorData - Object to track validation errors and store the first error element.
   */
  function validateModel(event, model, fieldName, ccFieldName, errorData) {
    const emptyRequiredFields = getEmptyRequiredFields(model);

    if (emptyRequiredFields.length > 0) {
      event.preventDefault();
      errorData.hasErrors = true;

      emptyRequiredFields.forEach((cfield) => {
        const name = `${fieldName}_${cfield}_${ccFieldName}`;
        const label = $(`label[for="${name}"]`);
        const element = $(`#${name}`);

        if (label.length) label.addClass('has-error');
        if (element.length) element.addClass('error');

        // Save the first error to scroll.
        if (!errorData.firstErrorElement) {
          errorData.firstErrorElement = label;
        }

        // Eliminate focusing error (CKEditor or normal field).
        removeErrorOnFocus(element, label);
      });
    }
  }

  /**
   * Removes the error class when the field gains focus.
   *
   * This function checks if the given field is associated with a CKEditor 5 instance.
   * If it is, it listens for the focus event on the CKEditor instance and removes
   * the error class from both the label and the input field.
   * If the field is a regular input or textarea, it directly listens for the focus event.
   *
   * @param {jQuery} element - The form field (input, textarea, or CKEditor instance).
   * @param {jQuery} label - The associated label element.
   */
  function removeErrorOnFocus(element, label) {
    const ckeditorId = element.attr('data-ckeditor5-id');

    if (ckeditorId !== undefined) {
      const editorInstance = Drupal.CKEditor5Instances.get(ckeditorId);
      if (editorInstance) {
        // Ensure that the focus event is only added once.
        editorInstance.editing.view.document.on('focus', () => {
          label.removeClass('has-error');
          element.removeClass('error');
        }, { once: true });
      }
    }
    else {
      // Normal event for inputs and textareas without CKEditor.
      element.on('focus', function () {
        label.removeClass('has-error');
        element.removeClass('error');
      });
    }
  }

  /**
   * Retrieves the required fields that have an empty value.
   *
   * This function first gets the required fields for the given model
   * using `getRequiredFields()`, then checks which of these fields have an empty value (`''`).
   *
   * @param {Object} model - The model containing field data.
   *
   * @returns {Array} An array of required field keys that are empty.
   */
  function getEmptyRequiredFields(model) {
    let requiredFields = getRequiredFields(model);
    requiredFields = getConditionalListTextFields(model, requiredFields);

    return requiredFields.filter(field =>
      model.fields[field] && model.fields[field].value?.trim() === ''
    );
  }

  /**
   * Retrieves required fields from a given model.
   *
   * This function checks the component field settings defined in `drupalSettings`
   * and identifies which fields are required for the given model.
   *
   * @param {Object} model - The model containing field data.
   *
   * @returns {Array} An array of required field keys.
   */
  function getRequiredFields(model) {
    const modelSettings = drupalSettings.components_fields_settings[model.type];

    if (!modelSettings) {
      console.warn(`No settings found for model type: ${model.type}`);
      return [];
    }

    return Object.entries(model.fields)
      .filter(([key, field]) => modelSettings.fields[key]?.required)
      .map(([key]) => key);
  }

  /**
   * Retrieves the items of fields of type $key from a given model.
   *
   * @param {Object} model - The model containing field data, including field types.
   *
   * @returns {Object} An object where the keys are field keys and the values are the field items.
   */
  function getContainerFields(model, $key) {
    let data = {};
    Object.entries(model.fields).forEach(([key, field]) => {
      if (field.type === $key) {
        data[key] = field.items;
      }
    });

    return data;
  }

  /**
   * Filters the required fields based on conditional 'list_text' fields in a given model.
   *
   * @param {Object} model - The model containing field data.
   * @param {Array} requiredFields - The initial list of required fields.
   *
   * @returns {Array} The filtered list of required fields.
   */
  function getConditionalListTextFields(model, requiredFields) {
    const modelSettings = drupalSettings.components_fields_settings[model.type];
    const fieldConditionals = Object.entries(model.fields)
      .filter(([key, field]) => field.type === 'list_text' && modelSettings.fields[key].settings.condition)
      .map(([key]) => key);

    if (!fieldConditionals.length) {
      return requiredFields;
    }

    fieldConditionals.forEach(item => {
      const fieldValue = model.fields[item]?.value;
      const fieldSettings = modelSettings.fields[item].settings;
      const searchKey = Object.entries(fieldSettings.options).find(([_, val]) => val.key === fieldValue);
      const { fields: conditionFields, state } = fieldSettings.conditions[searchKey[0]];

      requiredFields = requiredFields.filter(value =>
        state === 'visible' ? conditionFields.hasOwnProperty(value) : !conditionFields.hasOwnProperty(value)
      );
    });

    return requiredFields;
  }
})(jQuery, Drupal, once);