/**
 * Customizzazione:
 * - test empty fields like dataGrid and blocks field (getRealEmptyField)
 * - Supporto alla validazione dei campi DataGridField
 */

import map from 'lodash/map';
import uniq from 'lodash/uniq';
import keys from 'lodash/keys';
import intersection from 'lodash/intersection';
import isEmpty from 'lodash/isEmpty';
import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
import { toast } from 'react-toastify';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import { getRealEmptyField } from 'io-sanita-theme/helpers/FormValidation/FormValidation';

import config from '@plone/volto/registry';

/**
 * Will return the intl message if invalid
 * @param {boolean} isValid
 * @param {string} criterion
 * @param {string | number} valueToCompare can compare '47' < 50
 * @param {Function} intlFunc
 */
export const validationMessage = (
  isValid,
  criterion,
  valueToCompare,
  intlFunc,
) =>
  !isValid
    ? intlFunc(messages[criterion], {
        len: valueToCompare,
      })
    : null;

/**
 * The string that comes my not be a valid JSON
 * @param {string} requestItem
 */
export const tryParseJSON = (requestItem) => {
  let resultObj = null;
  try {
    resultObj = JSON.parse(requestItem);
  } catch (e) {
    try {
      resultObj = JSON.parse(requestItem.replace(/'/g, '"'));
    } catch (e) {
      resultObj = null;
    }
  }
  return resultObj;
};

/**
 * If required fields are undefined, return list of errors
 * @returns {Object[string]} - list of errors
 */
const validateRequiredFields = (
  schema,
  formData,
  formatMessage,
  touchedField,
) => {
  const errors = {};
  const fields = isEmpty(touchedField)
    ? // Custom: Serve creare una copia dei required
      // per poterlo modificare tre righe più giù
      schema.required.slice()
    : intersection(schema.required, keys(touchedField));

  const uniqueFields = uniq(fields);

  map(uniqueFields, (requiredField) => {
    const type = schema.properties[requiredField]?.type;
    const widget = schema.properties[requiredField]?.widget;

    //Custom: test empty fields like dataGrid and blocks field
    //[volto]:let isEmptyField = !formData[requiredField] && formData[requiredField] !== 0;
    let isEmptyField = getRealEmptyField(
      formData,
      touchedField,
      requiredField,
      type,
      widget,
    );

    //Custom: la validazione dei vari tipi di campo è stata spostata in getRealEmptyField
    if (!isEmptyField) {
      if (type === 'array') {
        // Custom: Supporto alla validazione dei campi DataGridField
        if (widget === 'data_grid') {
          const dgfFields = schema.properties[requiredField]?.items;
          const dgfRequiredFields = dgfFields?.required;
          const dgfData = formData[requiredField];
          isEmptyField =
            dgfRequiredFields.filter((dgfRequiredField) =>
              dgfData.some((dgfField) => !!!dgfField[dgfRequiredField]),
            )?.length > 0;
        } else {
          isEmptyField = formData[requiredField]
            ? formData[requiredField].length === 0
            : true;
        }
      }
    }
    if (
      schema.properties[requiredField] &&
      schema.properties[requiredField].type !== 'boolean' &&
      !schema.properties[requiredField].readonly &&
      isEmptyField
    ) {
      errors[requiredField] = [];
      errors[requiredField].push(formatMessage(messages.required));
    }
  });
  return errors;
};
/**
 * Return list of errors if field constraints are not respected
 * (ex min, max, maxLength, email format, url format etc)
 * each potential criterion has a validation process in widgetValidation
 * !!ONLY fields with data will be tested (those undefined are ignored here)
 * @returns {Object[string]} - list of errors
 */
const validateFieldsPerFieldset = (
  schema,
  formData,
  formatMessage,
  touchedField,
) => {
  const errors = validateRequiredFields(
    schema,
    formData,
    formatMessage,
    touchedField,
  );

  function checkFieldErrors(fieldValidationCriteria, field, fieldData) {
    return fieldValidationCriteria
      .map((widgetCriterion) => {
        const errorMessage =
          fieldData === undefined || fieldData === null
            ? null
            : widgetCriterion.method({
                value: fieldData,
                field,
                formData,
                formatMessage,
              });
        return errorMessage;
      })
      .filter((item) => !!item);
  }

  Object.entries(schema.properties).forEach(([fieldId, field]) => {
    let fieldData = formData[fieldId];

    // Validation per specific validator set (format property)
    const hasSpecificValidator =
      field.widgetOptions?.frontendOptions?.format || field.format;
    let specificFieldErrors = [];
    if (hasSpecificValidator) {
      const specificValidationCriteria = config.getUtilities({
        type: 'validator',
        dependencies: { format: hasSpecificValidator },
      });

      specificFieldErrors = checkFieldErrors(
        specificValidationCriteria,
        field,
        fieldData,
      );
    }

    // Validation per field type
    const fieldType = field.type || 'string'; // defaults to string
    const fieldTypeValidationCriteria = config.getUtilities({
      type: 'validator',
      dependencies: { fieldType },
    });

    const fieldErrors = checkFieldErrors(
      fieldTypeValidationCriteria,
      field,
      fieldData,
    );

    // Validation per field widget
    const widgetName =
      field.widgetOptions?.frontendOptions?.widget || field.widget || '';

    let widgetErrors = [];
    if (widgetName) {
      const widgetNameValidationCriteria = config.getUtilities({
        type: 'validator',
        dependencies: { widget: widgetName },
      });

      widgetErrors = checkFieldErrors(
        widgetNameValidationCriteria,
        field,
        fieldData,
      );
    }

    // Validation per specific behavior and field name (for content types)
    const behaviorName = field.behavior;
    let perBehaviorFieldErrors = [];
    if (behaviorName) {
      const specificPerBehaviorFieldValidationCriteria = config.getUtilities({
        type: 'validator',
        dependencies: { behaviorName, fieldName: fieldId },
      });

      perBehaviorFieldErrors = checkFieldErrors(
        specificPerBehaviorFieldValidationCriteria,
        field,
        fieldData,
      );
    }

    // Validation per block type validator (for blocks)
    const blockType = formData['@type'];
    let blockTypeFieldErrors = [];
    if (blockType) {
      const blockTypeFieldValidationCriteria = config.getUtilities({
        type: 'validator',
        dependencies: { blockType, fieldName: fieldId },
      });

      blockTypeFieldErrors = checkFieldErrors(
        blockTypeFieldValidationCriteria,
        field,
        fieldData,
      );
    }

    const mergedErrors = [
      ...specificFieldErrors,
      ...fieldErrors,
      ...widgetErrors,
      ...perBehaviorFieldErrors,
      ...blockTypeFieldErrors,
    ];

    if (mergedErrors.length > 0) {
      errors[fieldId] = [
        ...(errors[fieldId] || []),
        ...specificFieldErrors,
        ...fieldErrors,
        ...widgetErrors,
        ...perBehaviorFieldErrors,
        ...blockTypeFieldErrors,
      ];
    }
  });

  return errors;
};

/**
 * Create the errors object from backend the same way it is done on Frontend validation
 * @param {string} requestError form the server
 * @returns {Object}
 */
const giveServerErrorsToCorrespondingFields = (requestError) => {
  let errorsList = tryParseJSON(requestError);
  const errors = {};

  if (Array.isArray(errorsList) && errorsList.length > 0) {
    errorsList.forEach((errorItem) => {
      errors[errorItem.field] = errors[errorItem.field]
        ? errors[errorItem.field].push(errorItem.message)
        : [errorItem.message];
    });
  }
  return errors;
};

/**
 * The first Fieldset (Tab) that has any errors
 * will be selected
 * @param {Object[]} errors
 * @param {string} errors[].field
 * @param {Object} schema
 * @returns {number} activeIndex
 */
const getIndexOfFirstTabWithErrors = (errors, schema) => {
  let activeIndex = 0;

  schema.fieldsets.some((fieldSet, index) => {
    let foundfield = fieldSet.fields.some((fieldId) => errors[fieldId]);

    activeIndex = foundfield ? index : activeIndex;
    return foundfield;
  });

  return activeIndex;
};

class FormValidation {
  /**
   * The first Fieldset (Tab) that has any errors
   * will be selected
   * @param {Object} errors
   * @param {Object} schema
   * @returns {number} activeIndex
   */
  static showFirstTabWithErrors({
    errors = {},
    schema = { properties: {}, fieldsets: [], required: [] },
  } = {}) {
    return getIndexOfFirstTabWithErrors(errors, schema);
  }
  /**
   * Create the errors object from backend the same way it is done on Frontend validation
   * @param {string} requestError form the server
   * @returns {Object}
   */
  static giveServerErrorsToCorrespondingFields(requestError = '') {
    return giveServerErrorsToCorrespondingFields(requestError);
  }
  /**
   * Return validation result
   * @param {Object} schema
   * @param {Object} formData
   * @param {function} formatMessage
   * @returns {Object} errors
   */
  static validateFieldsPerFieldset({
    schema = { properties: {}, fieldsets: [], required: [] },
    formData = {},
    formatMessage = () => {},
    touchedField = {},
  } = {}) {
    return validateFieldsPerFieldset(
      schema,
      formData,
      formatMessage,
      touchedField,
    );
  }
}

export default FormValidation;

/**
 * Check if a file upload is within the maximum size limit.
 * @param {File} file
 * @param {Function} intlFunc
 * @returns {Boolean}
 */
export const validateFileUploadSize = (file, intlFunc) => {
  const isValid =
    !config.settings.maxFileUploadSize ||
    file.size <= config.settings.maxFileUploadSize;
  if (!isValid) {
    toast.error(
      <Toast
        error
        title={intlFunc(messages.error)}
        content={intlFunc(messages.fileTooLarge, {
          limit: `${Math.floor(
            config.settings.maxFileUploadSize / 1024 / 1024,
          )}MB`,
        })}
      />,
    );
  }
  return isValid;
};

/**
 * Extract invariant errors given an array of errors.
 * @param {Array} erros
 */
export const extractInvariantErrors = (erros) => {
  return erros
    .filter((errorItem) => !('field' in errorItem))
    .map((errorItem) => errorItem['message']);
};
