Source: modules/forms.js

/**
 * Form handling module for managing form submissions and validation.
 * @module modules/forms
 */

import { setLoadingState, displayError, displaySuccess, clearMessages } from './utils.js';
import { submitConfiguration, fetchConfigurations } from './servlet.js';
import { updateConfigSelect } from './ui.js';

/**
 * Component field definitions for each type.
 * Defines the form fields required for each component type.
 * @constant {Object}
 */
export const componentFields = {
  coaxial: [
    {
      name: 'atenuacion_470mhz',
      label: 'Aten. 470MHz (dB/100m)',
      type: 'number',
      step: '0.01',
      placeholder: 'ej., 12.30',
    },
    {
      name: 'atenuacion_694mhz',
      label: 'Aten. 694MHz (dB/100m)',
      type: 'number',
      step: '0.01',
      placeholder: 'ej., 15.57',
    },
  ],
  derivador: [
    {
      name: 'atenuacion_derivacion',
      label: 'Aten. Derivación (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 14.0',
    },
    {
      name: 'atenuacion_paso',
      label: 'Aten. Paso (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 4.5',
    },
    {
      name: 'directividad',
      label: 'Directividad (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 13.0',
    },
    {
      name: 'desacoplo',
      label: 'Desacoplo (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 16.0',
    },
    {
      name: 'perdidas_retorno',
      label: 'Pérdidas Retorno (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 12.0',
    },
  ],
  distribuidor: [
    {
      name: 'numero_salidas',
      label: 'Nº Salidas',
      type: 'number',
      step: '1',
      min: '2',
      placeholder: 'ej., 2',
    },
    {
      name: 'atenuacion_distribucion',
      label: 'Aten. Distribución (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 4.0',
    },
    {
      name: 'desacoplo',
      label: 'Desacoplo (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 19.0',
    },
    {
      name: 'perdidas_retorno',
      label: 'Pérdidas Retorno (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 16.0',
    },
  ],
  toma: [
    {
      name: 'atenuacion',
      label: 'Atenuación (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 1.0',
    },
    {
      name: 'desacoplo',
      label: 'Desacoplo (dB)',
      type: 'number',
      step: '0.1',
      placeholder: 'ej., 14.0',
    },
  ],
};

/**
 * Validates that all required component fields are present and valid.
 * @param {FormData} formData - The form data to validate
 * @returns {boolean} True if all fields are valid, false otherwise
 */
export function validateComponentForm(formData) {
  const type = formData.get('type');
  if (!type || !componentFields[type]) return false;
  return componentFields[type].every((field) => {
    const value = formData.get(field.name);
    return value !== null && value !== '' && (field.type !== 'number' || !isNaN(value));
  });
}

/**
 * Dynamically updates the component form fields based on selected type.
 * @param {string} type - The component type (coaxial, derivador, distribuidor, toma)
 */
export function updateComponentForm(type) {
  const dynamicFieldsContainer = document.getElementById('dynamic-fields');
  if (!dynamicFieldsContainer) return;
  dynamicFieldsContainer.innerHTML = '';
  if (!type || !componentFields[type]) {
    dynamicFieldsContainer.innerHTML =
      '<p class="text-sm text-zinc-400 dark:text-zinc-500 italic">Seleccione un tipo de componente para ver sus propiedades.</p>';
    return;
  }
  componentFields[type].forEach((field) => {
    const fieldWrapper = document.createElement('div');
    const label = document.createElement('label');
    label.htmlFor = field.name;
    label.textContent = field.label;
    label.className = 'block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1';
    const input = document.createElement('input');
    input.type = field.type;
    input.id = field.name;
    input.name = field.name;
    input.placeholder = field.placeholder;
    input.required = true;
    if (field.type === 'number') {
      input.step = field.step || 'any';
      if (field.min) input.min = field.min;
      if (field.max) input.max = field.max;
    }
    input.className =
      'block w-full rounded-lg border-zinc-300 bg-zinc-200 shadow-sm transition focus:border-primary-500 focus:ring focus:ring-primary-500 focus:ring-opacity-50 dark:border-zinc-700 dark:bg-zinc-800 dark:text-white dark:focus:border-primary-400 dark:focus:ring-primary-400';
    fieldWrapper.appendChild(label);
    fieldWrapper.appendChild(input);
    dynamicFieldsContainer.appendChild(fieldWrapper);
  });
}

/**
 * Prepares simulation data payload for the backend.
 * @param {number} configId - The configuration ID
 * @param {string} signalType - The signal type
 * @param {Object} componentsByFloor - Components organized by floor
 * @returns {Object} The prepared simulation data object
 * @throws {Error} If required elements are not found
 */
export function prepareSimulationData(configId, signalType, componentsByFloor) {
  const configSelect = document.getElementById('simulation-config');
  const frequencyInput = document.getElementById('signal-frequency');
  if (!configSelect || !frequencyInput) {
    throw new Error('Required elements not found (config select or frequency input)');
  }
  const frequency = parseInt(frequencyInput.value);
  if (isNaN(frequency) || frequency < 470 || frequency > 694) {
    throw new Error('Frecuencia debe ser un valor entero entre 470 y 694 MHz');
  }
  const selectedOption = Array.from(configSelect.options).find(
    (option) => option.value === configId,
  );
  if (!selectedOption || !selectedOption.dataset.config) {
    throw new Error('Selected configuration data not found in dropdown');
  }
  let configData;
  try {
    configData = JSON.parse(selectedOption.dataset.config);
  } catch (e) {
    throw new Error('Failed to parse configuration data from dropdown');
  }
  const components = [];
  Object.entries(componentsByFloor).forEach(([floor, floorComponents]) => {
    const floorNum = parseInt(floor);
    if (isNaN(floorNum) || floorNum <= 0) return;
    if (floorNum > configData.num_pisos) {
      throw new Error(
        `Floor ${floorNum} exceeds the configuration's number of floors (${configData.num_pisos})`,
      );
    }
    // Collect all components for this floor
    Object.entries(floorComponents).forEach(([compKey, compValue]) => {
      if (compKey === 'derivador' && compValue) {
        components.push({ type: 'derivador', model: compValue, floor: floorNum });
      } else if (compKey === 'distribuidores' && Array.isArray(compValue)) {
        compValue.forEach((model) => {
          if (model) components.push({ type: 'distribuidor', model: model, floor: floorNum });
        });
      } else if (compKey === 'tomasLeft' && Array.isArray(compValue)) {
        compValue.forEach((model) => {
          if (model) components.push({ type: 'toma', model: model, floor: floorNum });
        });
      } else if (compKey === 'tomasRight' && Array.isArray(compValue)) {
        compValue.forEach((model) => {
          if (model) components.push({ type: 'toma', model: model, floor: floorNum });
        });
      }
    });
  });
  return {
    num_pisos: configData.num_pisos,
    nivel_cabecera: configData.nivel_cabecera,
    tipo_senal: signalType,
    frequency: frequency,
    components: components,
  };
}

/**
 * Handles initial configuration form submission.
 * @async
 * @param {Event} event - The form submit event
 * @param {HTMLFormElement} initialConfigForm - The configuration form element
 * @param {HTMLElement} errorMessageElement - The error message container
 * @param {HTMLElement} successMessageElement - The success message container
 * @param {HTMLSelectElement} configSelect - The configuration select dropdown
 * @returns {Promise<void>}
 */
export async function handleFormSubmit(
  event,
  initialConfigForm,
  errorMessageElement,
  successMessageElement,
  configSelect,
) {
  event.preventDefault();
  clearMessages(errorMessageElement, successMessageElement);
  const formData = new FormData(initialConfigForm);
  const configName = formData.get('nombre')?.trim();
  const nivelCabeceraStr = formData.get('nivel_cabecera');
  const numPisosStr = formData.get('num_pisos');
  // Basic validation
  const nivelCabecera = parseFloat(nivelCabeceraStr);
  const numPisos = parseInt(numPisosStr);
  let errors = [];
  if (!configName) {
    errors.push('El nombre de la configuración es requerido.');
  }
  if (isNaN(nivelCabecera) || nivelCabecera < 70 || nivelCabecera > 120) {
    errors.push('Nivel de Cabecera debe ser un número entre 70 y 120.');
  }
  if (isNaN(numPisos) || numPisos < 1 || numPisos > 50) {
    errors.push('Número de Pisos debe ser un número entero entre 1 y 50.');
  }
  if (errors.length > 0) {
    displayError(errors.join(' '), errorMessageElement, successMessageElement);
    return;
  }
  const submitButton = initialConfigForm.querySelector('button[type="submit"]');
  if (!submitButton.dataset.originalText) {
    submitButton.dataset.originalText = submitButton.innerHTML;
  }
  setLoadingState(submitButton, true);
  try {
    const result = await submitConfiguration(formData);
    if (result.success && result.id) {
      displaySuccess(
        result.success || 'Configuración creada con éxito.',
        successMessageElement,
        errorMessageElement,
      );
      initialConfigForm.reset();
      const configurations = await fetchConfigurations();
      updateConfigSelect(configurations, configSelect);
      const simulationConfigSelect = document.getElementById('simulation-config');
      if (simulationConfigSelect) {
        updateConfigSelect(configurations, simulationConfigSelect);
      }
      const newConfigId = result.id;
      if (configSelect.querySelector(`option[value="${newConfigId}"]`)) {
        configSelect.value = newConfigId;
        configSelect.dispatchEvent(new Event('change'));
      }
      if (
        simulationConfigSelect &&
        simulationConfigSelect.querySelector(`option[value="${newConfigId}"]`)
      ) {
        simulationConfigSelect.value = newConfigId;
        simulationConfigSelect.dispatchEvent(new Event('change'));
      }
    } else {
      const errorMsg = result?.error || 'Error desconocido al crear la configuración.';
      displayError(`Error: ${errorMsg}`, errorMessageElement, successMessageElement);
    }
  } catch (error) {
    console.error('Error submitting configuration:', error);
    displayError(
      `Error de red o del servidor: ${error.message}`,
      errorMessageElement,
      successMessageElement,
    );
  } finally {
    setLoadingState(submitButton, false);
  }
}