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

  const INLINE_TEXT_REQUIRED = Drupal.t('Campo requerido');
  const INLINE_TEXT_MINCHECKED = Drupal.t('Selecciona al menos uno');

  const FIELD_LABELS = {
    'student.email': Drupal.t('correo electrónico del equipo'),
    'student.password': Drupal.t('contraseña del equipo'),
    'student.password_confirmation': Drupal.t('confirmación de contraseña'),
    'hours_per_week': Drupal.t('horas semanales'),
    'hours per week': Drupal.t('horas semanales'),
    'weeks_per_period': Drupal.t('semanas por periodo'),
    'weeks per period': Drupal.t('semanas por periodo'),
    'hours_total': Drupal.t('horas totales'),
    'hours total': Drupal.t('horas totales'),
    'students_total': Drupal.t('total de estudiantes'),
    'students total': Drupal.t('total de estudiantes'),
    'academic_year': Drupal.t('año académico'),
    'academic year': Drupal.t('año académico'),
    'email': Drupal.t('correo electrónico'),
    'password': Drupal.t('contraseña'),
    'password_confirmation': Drupal.t('confirmación de contraseña'),
    'schedule': Drupal.t('horario'),
    'name': Drupal.t('nombre'),
    'surname': Drupal.t('apellidos'),
    'project_id': Drupal.t('proyecto'),
    'project id': Drupal.t('proyecto'),
    'team_id': Drupal.t('equipo'),
    'team id': Drupal.t('equipo'),
    'user_id': Drupal.t('usuario'),
    'user id': Drupal.t('usuario')
  };

  function normalizeMessageText(message) {
    return String(message || '')
      .replace(/<[^>]+>/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();
  }

  function labelForAttribute(attribute) {
    const raw = String(attribute || '').trim().replace(/[.:]+$/g, '');
    const normalized = raw.toLowerCase().replace(/-/g, '_').replace(/\s+/g, ' ');
    const candidates = [
      raw,
      raw.toLowerCase(),
      normalized,
      normalized.replace(/ /g, '_')
    ];

    for (let i = 0; i < candidates.length; i++) {
      if (Object.prototype.hasOwnProperty.call(FIELD_LABELS, candidates[i])) {
        return FIELD_LABELS[candidates[i]];
      }
    }

    return raw.replace(/[._-]+/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
  }

  function localizeValidationMessage(message) {
    const text = normalizeMessageText(message);
    if (!text) return '';

    const lower = text.toLowerCase();
    const direct = {
      'the student.email has already been taken.': Drupal.t('El correo electrónico del equipo ya está en uso.'),
      'the student.password field confirmation does not match.': Drupal.t('La confirmación de la contraseña del equipo no coincide.'),
      'the given data was invalid.': Drupal.t('Los datos enviados no son válidos. Revisa los campos marcados.')
    };

    if (Object.prototype.hasOwnProperty.call(direct, lower)) {
      return direct[lower];
    }

    const patterns = [
      [/^The (.+?) field is required\.$/i, 'required'],
      [/^The (.+?) is required\.$/i, 'required'],
      [/^The (.+?) field must be an integer\.$/i, 'integer'],
      [/^The (.+?) must be an integer\.$/i, 'integer'],
      [/^The (.+?) field must be a number\.$/i, 'numeric'],
      [/^The (.+?) must be a number\.$/i, 'numeric'],
      [/^The (.+?) field must be numeric\.$/i, 'numeric'],
      [/^The (.+?) must be numeric\.$/i, 'numeric'],
      [/^The (.+?) field must be a valid email address\.$/i, 'email'],
      [/^The (.+?) must be a valid email address\.$/i, 'email'],
      [/^The (.+?) has already been taken\.$/i, 'unique'],
      [/^The (.+?) field confirmation does not match\.$/i, 'confirmed'],
      [/^The (.+?) confirmation does not match\.$/i, 'confirmed'],
      [/^The (.+?) field must be a valid date\.$/i, 'date'],
      [/^The (.+?) must be a valid date\.$/i, 'date'],
      [/^The (.+?) field must be at least (.+?)\.$/i, 'min'],
      [/^The (.+?) must be at least (.+?)\.$/i, 'min'],
      [/^The (.+?) field may not be greater than (.+?)\.$/i, 'max'],
      [/^The (.+?) may not be greater than (.+?)\.$/i, 'max']
    ];

    for (let i = 0; i < patterns.length; i++) {
      const match = text.match(patterns[i][0]);
      if (!match) continue;

      const field = labelForAttribute(match[1]);
      const value = match[2] || '';

      switch (patterns[i][1]) {
        case 'required':
          return Drupal.t('El campo @field es obligatorio.', { '@field': field });
        case 'integer':
          return Drupal.t('El campo @field debe ser un número entero.', { '@field': field });
        case 'numeric':
          return Drupal.t('El campo @field debe ser numérico.', { '@field': field });
        case 'email':
          return Drupal.t('El campo @field debe ser una dirección de correo electrónico válida.', { '@field': field });
        case 'unique':
          return Drupal.t('El campo @field ya está en uso.', { '@field': field });
        case 'confirmed':
          return Drupal.t('La confirmación de @field no coincide.', { '@field': field });
        case 'date':
          return Drupal.t('El campo @field debe ser una fecha válida.', { '@field': field });
        case 'min':
          return Drupal.t('El campo @field debe ser como mínimo @value.', { '@field': field, '@value': value });
        case 'max':
          return Drupal.t('El campo @field no puede ser mayor que @value.', { '@field': field, '@value': value });
      }
    }

    return text;
  }

  function flattenAjaxErrors(errors, prefix = '') {
    if (!errors) return [];

    if (typeof errors === 'string' || typeof errors === 'number') {
      return [String(errors)];
    }

    if (Array.isArray(errors)) {
      return errors.flatMap(item => flattenAjaxErrors(item, prefix));
    }

    if (typeof errors === 'object') {
      return Object.keys(errors).flatMap(key => {
        const nextPrefix = prefix ? `${prefix}.${key}` : key;
        return flattenAjaxErrors(errors[key], nextPrefix);
      });
    }

    return [];
  }

  function uniqueMessages(messages) {
    const seen = new Set();
    return messages
      .map(localizeValidationMessage)
      .filter(Boolean)
      .filter(message => {
        if (seen.has(message)) return false;
        seen.add(message);
        return true;
      });
  }

  function getAjaxResponseMessage(data, fallback) {
    if (!data || typeof data !== 'object') {
      return Drupal.t(fallback || 'Error al enviar el formulario.');
    }

    const messages = [];
    if (data.errors) messages.push(...flattenAjaxErrors(data.errors));
    if (data.message) {
      const genericInvalid = normalizeMessageText(data.message).toLowerCase() === 'the given data was invalid.';
      if (!data.errors || !genericInvalid) {
        messages.unshift(data.message);
      }
    }
    if (data.error) messages.push(data.error);

    const localized = uniqueMessages(messages);
    return localized.length ? localized.join(' ') : Drupal.t(fallback || 'Error al enviar el formulario.');
  }

  Drupal.valnalonLocalizeValidationMessage = localizeValidationMessage;
  Drupal.valnalonGetAjaxResponseMessage = getAjaxResponseMessage;

  const FORM_SELECTOR = 'form[data-ajax-submit="1"], form[data-form-enhance="1"]';

  function getMessagesRegion(form) {
    return document.querySelector('[data-drupal-messages]') || form;
  }

  function clearAjaxDrupalMessages(root) {
    if (!root) return;
    root.querySelectorAll('.ajax-drupal-message').forEach(n => n.remove());
  }

  function showDrupalMessage(form, text, type = 'error', focus = false) {
    const region = getMessagesRegion(form);
    clearAjaxDrupalMessages(region);

    const msg = document.createElement('div');
    msg.className = `messages messages--${type} ajax-drupal-message`;
    msg.setAttribute('role', 'alert');
    msg.setAttribute('aria-live', 'polite');
    msg.setAttribute('tabindex', '-1');
    msg.textContent = text;

    region.prepend(msg);
    msg.scrollIntoView({ behavior: 'smooth', block: 'center' });

    if (focus) msg.focus();

    return msg;
  }

  function clearAll(form) {
    if (!form) return;

    form.querySelectorAll('.required-focus-message').forEach(n => n.remove());
    form.querySelectorAll('.required-focus-inline').forEach(n => n.remove());

    clearAjaxDrupalMessages(getMessagesRegion(form));

    form.querySelectorAll('.is-invalid').forEach(n => n.classList.remove('is-invalid'));
    form.querySelectorAll('.required-focus-invalid').forEach(n => n.classList.remove('required-focus-invalid'));
    form.querySelectorAll('[aria-invalid="true"]').forEach(n => n.removeAttribute('aria-invalid'));
  }

  function addInlineMessage(afterEl, text) {
    if (!afterEl) return;

    const msg = document.createElement('div');
    msg.className = 'invalid-feedback d-block required-focus-inline';
    msg.textContent = text;
    afterEl.insertAdjacentElement('afterend', msg);
  }

  function getSelect2SelectionEl(selectEl) {
    if (selectEl.id) {
      const rendered = document.querySelector(`#select2-${CSS.escape(selectEl.id)}-container`);
      if (rendered) {
        const container = rendered.closest('.select2-container');
        if (container) return container.querySelector('.select2-selection');
      }
    }

    const sib = selectEl.nextElementSibling;
    if (sib && sib.classList && sib.classList.contains('select2')) {
      return sib.querySelector('.select2-selection');
    }

    const cont = selectEl.parentElement ? selectEl.parentElement.querySelector('.select2-container') : null;
    if (cont) return cont.querySelector('.select2-selection');

    return null;
  }

  function isEmptyRequired(el, form) {
    if (!el || el.disabled || !el.required) return false;

    const tag = el.tagName.toLowerCase();
    const type = (el.getAttribute('type') || '').toLowerCase();

    if (type === 'hidden' || type === 'submit' || type === 'button') return false;

    if (type === 'radio' && el.name) {
      return form.querySelectorAll(`input[type="radio"][name="${CSS.escape(el.name)}"]:checked`).length === 0;
    }

    if (type === 'checkbox') return !el.checked;

    if (tag === 'select') return !el.value;

    return !String(el.value || '').trim();
  }

  function getInvalidRequiredFields(form) {
    const required = Array.from(form.querySelectorAll('[required]'));
    const invalid = [];
    const seenRadioNames = new Set();

    required.forEach(el => {
      if (!isEmptyRequired(el, form)) return;

      const type = (el.getAttribute('type') || '').toLowerCase();

      if (type === 'radio' && el.name) {
        if (seenRadioNames.has(el.name)) return;
        seenRadioNames.add(el.name);
      }

      invalid.push(el);
    });

    return invalid;
  }

  function markAndExplainRequired(el) {
    const $el = $(el);
    const type = (el.getAttribute('type') || '').toLowerCase();

    if ($el.hasClass('select2-hidden-accessible') || $el.data('select2')) {
      const sel = getSelect2SelectionEl(el);
      if (sel) {
        sel.classList.add('is-invalid', 'required-focus-invalid');
        sel.setAttribute('aria-invalid', 'true');
        addInlineMessage(sel, INLINE_TEXT_REQUIRED);
      }
      return;
    }

    if (type === 'radio' && el.name && el.form) {
      const radios = el.form.querySelectorAll(`input[type="radio"][name="${CSS.escape(el.name)}"]`);
      radios.forEach(radio => {
        radio.classList.add('is-invalid', 'required-focus-invalid');
        radio.setAttribute('aria-invalid', 'true');
      });

      const lastRadio = radios[radios.length - 1];
      if (lastRadio) {
        addInlineMessage(lastRadio, INLINE_TEXT_REQUIRED);
      }
      return;
    }

    el.classList.add('is-invalid', 'required-focus-invalid');
    el.setAttribute('aria-invalid', 'true');
    addInlineMessage(el, INLINE_TEXT_REQUIRED);
  }


  function markInvalidWithMessage(el, text) {
    if (!el) return;

    const $el = $(el);
    if ($el.hasClass('select2-hidden-accessible') || $el.data('select2')) {
      const sel = getSelect2SelectionEl(el);
      if (sel) {
        sel.classList.add('is-invalid', 'required-focus-invalid');
        sel.setAttribute('aria-invalid', 'true');
        addInlineMessage(sel, text);
      }
      return;
    }

    el.classList.add('is-invalid', 'required-focus-invalid');
    el.setAttribute('aria-invalid', 'true');
    addInlineMessage(el, text);
  }

  function isIntegerField(el) {
    if (!el) return false;
    if (el.getAttribute('data-integer') === '0') return false;
    if (el.getAttribute('data-integer') === '1') return true;
    const type = (el.getAttribute('type') || '').toLowerCase();
    const step = el.getAttribute('step');
    return type === 'number' && (!step || step === '1');
  }

  function isValidEmail(value) {
    return /^[^@\s;]+@[^@\s;]+\.[^@\s;]+$/.test(String(value || '').trim());
  }

  function isValidMultiEmailList(value) {
    const parts = String(value || '').split(';').map(part => part.trim());

    if (!parts.length || parts.some(part => part === '')) {
      return false;
    }

    return parts.every(isValidEmail);
  }

  function getInvalidFormatFields(form) {
    const invalid = [];
    const controls = Array.from(form.querySelectorAll('input, textarea, select'));

    controls.forEach(el => {
      if (!el || el.disabled || el.readOnly) return;

      const value = String(el.value || '').trim();
      if (!value) return;

      const type = (el.getAttribute('type') || '').toLowerCase();
      const name = (el.getAttribute('name') || '').toLowerCase();
      const isMultiEmail = el.getAttribute('data-multi-email') === '1';

      if (isMultiEmail && !isValidMultiEmailList(value)) {
        invalid.push({ el, message: Drupal.t('Introduce uno o varios correos válidos separados por ;.') });
        return;
      }

      if (!isMultiEmail && (type === 'email' || name.includes('email')) && !isValidEmail(value)) {
        invalid.push({ el, message: Drupal.t('Introduce un correo electrónico válido.') });
        return;
      }

      if (isIntegerField(el) && !/^-?\d+$/.test(value)) {
        invalid.push({ el, message: Drupal.t('Solo se admiten números enteros.') });
        return;
      }

      if (type === 'number' && el.getAttribute('data-integer') === '0' && Number.isNaN(Number(value))) {
        invalid.push({ el, message: Drupal.t('Solo se admiten valores numéricos.') });
        return;
      }

      if (type === 'number') {
        const numericValue = Number(value);
        const min = el.getAttribute('min');
        const max = el.getAttribute('max');
        if (min !== null && min !== '' && numericValue < Number(min)) {
          invalid.push({ el, message: Drupal.t('El valor debe ser como mínimo @value.', { '@value': min }) });
          return;
        }
        if (max !== null && max !== '' && numericValue > Number(max)) {
          invalid.push({ el, message: Drupal.t('El valor no puede ser mayor que @value.', { '@value': max }) });
          return;
        }
      }

      const pattern = el.getAttribute('pattern');
      if (pattern) {
        try {
          const re = new RegExp(`^(?:${pattern})$`);
          if (!re.test(value)) {
            invalid.push({ el, message: el.getAttribute('title') || Drupal.t('El formato del campo no es válido.') });
            return;
          }
        } catch (e) {}
      }

      if (name.endsWith('_confirmation')) {
        const baseName = name.replace(/_confirmation$/, '');
        const original = form.querySelector(`[name="${CSS.escape(baseName)}"]`);
        if (original && String(original.value || '') !== value) {
          invalid.push({ el, message: Drupal.t('Las contraseñas no coinciden.') });
        }
      }
    });

    return invalid;
  }

  function getInvalidMinCheckedGroups(form) {
    const groups = Array.from(form.querySelectorAll('.js-minchecked[data-minchecked]'));

    return groups.filter(group => {
      const min = parseInt(group.getAttribute('data-minchecked') || '1', 10);
      const checked = group.querySelectorAll('input[type="checkbox"]:checked').length;
      return checked < min;
    });
  }

  function markMinCheckedInvalid(group) {
    group.classList.add('required-focus-invalid', 'is-invalid');
    group.setAttribute('aria-invalid', 'true');

    const msg = document.createElement('div');
    msg.className = 'invalid-feedback d-block required-focus-inline';
    msg.textContent = group.getAttribute('data-error') || INLINE_TEXT_MINCHECKED;
    group.appendChild(msg);
  }

  function dropzoneHiddenHasFiles(hidden) {
    if (!hidden) return false;

    const raw = String(hidden.value || '').trim();
    if (!raw) return false;

    try {
      const parsed = JSON.parse(raw);

      if (Array.isArray(parsed)) {
        return parsed.length > 0;
      }

      if (parsed && typeof parsed === 'object') {
        return Object.keys(parsed).length > 0;
      }

      return false;
    } catch (e) {
      return false;
    }
  }

  function dropzoneHasFiles(dzEl, form) {
    if (!dzEl) return false;

    const hiddenId = dzEl.getAttribute('data-dz-hidden');
    if (hiddenId) {
      const hidden = form.querySelector(`#${CSS.escape(hiddenId)}`);
      if (dropzoneHiddenHasFiles(hidden)) {
        return true;
      }
    }

    const dz = dzEl.dropzone;
    if (!dz) return false;

    if (Array.isArray(dz.files)) {
      const validFiles = dz.files.filter(file => {
        if (!file) return false;
        if (typeof Dropzone !== 'undefined' && file.status === Dropzone.CANCELED) return false;
        return true;
      });

      if (validFiles.length > 0) {
        return true;
      }
    }

    if (typeof dz.getAcceptedFiles === 'function' && dz.getAcceptedFiles().length > 0) {
      return true;
    }

    if (typeof dz.getQueuedFiles === 'function' && dz.getQueuedFiles().length > 0) {
      return true;
    }

    if (typeof dz.getUploadingFiles === 'function' && dz.getUploadingFiles().length > 0) {
      return true;
    }

    return false;
  }

  function getInvalidDropzones(form) {
    const dzEls = Array.from(form.querySelectorAll('.dropzone[data-dz-min]'));

    return dzEls.filter(dzEl => {
      const min = parseInt(dzEl.getAttribute('data-dz-min') || '0', 10);
      if (min <= 0) return false;
      return !dropzoneHasFiles(dzEl, form);
    });
  }

  function markDropzoneInvalid(dzEl) {
    if (!dzEl) return;

    dzEl.classList.add('required-focus-invalid', 'is-invalid');
    dzEl.setAttribute('aria-invalid', 'true');
  }

  function focusFieldByNameOrId(form, field) {
    if (!field) return false;

    let el = form.querySelector(`#${CSS.escape(field)}`);
    if (el) {
      markAndExplainRequired(el);
      return true;
    }

    el = form.querySelector(`[name="${CSS.escape(field)}"]`);
    if (!el) el = form.querySelector(`[name="${CSS.escape(field)}[]"]`);

    if (el) {
      if (el.type === 'checkbox') {
        const group = el.closest('.js-minchecked') || el.closest('.row') || el.parentElement;
        if (group) markMinCheckedInvalid(group);
        return true;
      }
      markAndExplainRequired(el);
      return true;
    }

    return false;
  }

  async function submitViaAjax(form) {
    const submitBtn = form.querySelector('button[type="submit"], input[type="submit"]');
    const prevText = submitBtn && submitBtn.tagName.toLowerCase() === 'button' ? submitBtn.textContent : null;

    if (submitBtn) {
      submitBtn.disabled = true;
      if (prevText !== null) submitBtn.textContent = Drupal.t('Enviando...');
    }

    try {
      const fd = new FormData(form);
      fd.append('_ajax_submit', '1');

      const res = await fetch(form.action, {
        method: 'POST',
        body: fd,
        credentials: 'same-origin',
        headers: {
          'Accept': 'application/json',
          'X-Requested-With': 'XMLHttpRequest'
        }
      });

      const data = await res.json().catch(() => null);

      if (!data) {
        showDrupalMessage(form, Drupal.t('Error inesperado: la respuesta no es JSON.'), 'error', false);
        return;
      }

      if (!res.ok || data.success === false) {
        showDrupalMessage(form, getAjaxResponseMessage(data, 'Error al enviar el formulario.'), 'error', false);
        if (data.field) focusFieldByNameOrId(form, data.field);
        return;
      }

      if (data.message) {
        showDrupalMessage(form, localizeValidationMessage(data.message), 'status', false);
      }

      if (data.redirect) {
        window.location.href = data.redirect;
        return;
      }

      resetEnhancedForm(form);

    } catch (err) {
      showDrupalMessage(form, Drupal.t('Error de red o servidor al enviar el formulario.'), 'error', false);
    } finally {
      if (submitBtn) {
        submitBtn.disabled = false;
        if (prevText !== null) submitBtn.textContent = prevText;
      }
    }
  }

  function resetEnhancedForm(form) {
    if (!form) return;

    form.querySelectorAll('.required-focus-inline').forEach(n => n.remove());
    form.querySelectorAll('.is-invalid').forEach(n => n.classList.remove('is-invalid'));
    form.querySelectorAll('.required-focus-invalid').forEach(n => n.classList.remove('required-focus-invalid'));
    form.querySelectorAll('[aria-invalid="true"]').forEach(n => n.removeAttribute('aria-invalid'));

    form.reset();

    form.querySelectorAll('input, textarea, select').forEach(el => {
      try { el.setCustomValidity(''); } catch (e) {}
    });

    if (window.jQuery) {
      window.jQuery(form).find('select.select2-hidden-accessible').each(function () {
        window.jQuery(this).trigger('change.select2');
      });
    }

    form.querySelectorAll('.dropzone').forEach(dzEl => {
      const dz = dzEl.dropzone;
      if (dz && typeof dz.__valReset === 'function') {
        dz.__valReset();
      } else {
        const hiddenId = dzEl.getAttribute('data-dz-hidden');
        if (hiddenId) {
          const hidden = form.querySelector(`#${CSS.escape(hiddenId)}`);
          if (hidden) {
            hidden.value = '';
            hidden.dispatchEvent(new Event('input', { bubbles: true }));
            hidden.dispatchEvent(new Event('change', { bubbles: true }));
          }
        }
        dzEl.querySelectorAll('.dz-preview').forEach(p => p.remove());
      }
    });
  }

  Drupal.behaviors.requiredFocusOnSubmit = {
    attach(context) {
      if ($.fn.select2) {
        $(once('initSelect2', '.js-example-basic-multiple', context)).select2();
      }

      $(once('requiredFocusOnSubmit', FORM_SELECTOR, context))
        .each(function () {
          this.setAttribute('novalidate', 'novalidate');
        })
        .on('submit', function (e) {
          const form = this;

          if (e.defaultPrevented) return false;

          clearAll(form);

          const invalidDropzones = getInvalidDropzones(form);
          const invalidRequired = getInvalidRequiredFields(form);
          const invalidMinChecked = getInvalidMinCheckedGroups(form);
          const invalidFormats = getInvalidFormatFields(form);

          invalidDropzones.forEach(markDropzoneInvalid);
          invalidRequired.forEach(markAndExplainRequired);
          invalidMinChecked.forEach(markMinCheckedInvalid);
          invalidFormats.forEach(item => markInvalidWithMessage(item.el, item.message));

          const hasErrors =
            invalidDropzones.length > 0 ||
            invalidRequired.length > 0 ||
            invalidMinChecked.length > 0 ||
            invalidFormats.length > 0;

          if (hasErrors) {
            e.preventDefault();
            showDrupalMessage(
              form,
              invalidFormats.length > 0
                ? Drupal.t('Hay campos con formato incorrecto. Revisa los campos marcados.')
                : Drupal.t('Hay campos obligatorios sin rellenar. Revisa los campos marcados.'),
              'error',
              false
            );
            return false;
          }

          const ajax = form.getAttribute('data-ajax-submit') === '1';
          if (ajax) {
            e.preventDefault();
            submitViaAjax(form);
            return false;
          }

          return true;
        });

      $(once('requiredFocusClear', FORM_SELECTOR, context)).on(
        'input change',
        '[required], .js-minchecked input[type="checkbox"], input[type="hidden"][id]',
        function () {
          const form = this.form || this.closest('form');
          if (!form) return;
          clearAll(form);
        }
      );
    }
  };

})(Drupal, once, jQuery);