Source: modules/servlet.js

/**
 * API communication module for server interactions.
 * Handles all HTTP requests to the backend servlets.
 * @module modules/servlet
 */

import { displayError } from './utils.js';
import { updateConfigSelect } from './ui.js';

// --- Configuration API Calls ---

/**
 * Fetches all configurations from the server.
 * @async
 * @returns {Promise<Array<Object>>} Array of configuration objects
 * @throws {Error} If the HTTP request fails or returns invalid data
 */
export async function fetchConfigurations() {
  try {
    const response = await fetch('configurations');
    if (!response.ok) {
      console.error('Error fetching configurations:', `HTTP error! status: ${response.status}`);
      displayError('Error al cargar las configuraciones. Por favor, intente de nuevo.');
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    if (!Array.isArray(data)) {
      console.error('Error fetching configurations: Invalid response format');
      displayError('Error en el formato de las configuraciones recibidas.');
      throw new Error('Invalid response format: expected array of configurations');
    }
    return data;
  } catch (error) {
    console.error('Error fetching configurations:', error);
    displayError('Error al cargar las configuraciones. Por favor, intente de nuevo.');
    throw error;
  }
}

/**
 * Updates an existing configuration.
 * @async
 * @param {number} configId - The ID of the configuration to update
 * @param {FormData} formData - Form data containing configuration fields
 * @returns {Promise<Object>} The updated configuration object
 * @throws {Error} If the update fails
 */
export async function updateConfiguration(configId, formData) {
  try {
    const response = await fetch('configurations', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        id_configuraciones: configId,
        ...Object.fromEntries(formData),
      }).toString(),
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error updating configuration:', errorData.error);
      displayError(errorData.error);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error updating configuration:', error);
    displayError('Error al actualizar la configuración. Por favor, intente de nuevo.');
    throw error;
  }
}

/**
 * Deletes a configuration from the server.
 * @async
 * @param {number} configId - The ID of the configuration to delete
 * @returns {Promise<Object>} The server response
 * @throws {Error} If the deletion fails
 */
export async function deleteConfiguration(configId) {
  try {
    const url = new URL('configurations', window.location.href);
    url.searchParams.append('id_configuraciones', configId);
    const response = await fetch(url, { method: 'DELETE' });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error deleting configuration:', errorData.error);
      displayError(errorData.error);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error deleting configuration:', error);
    displayError('Error al eliminar la configuración. Por favor, intente de nuevo.');
    throw error;
  }
}

/**
 * Fetches initial configuration data and populates the config select dropdown.
 * @async
 * @param {HTMLSelectElement} configSelect - The select element to populate
 * @returns {Promise<Array<Object>>} Array of configuration objects
 * @throws {Error} If fetching fails
 */
export async function fetchInitialData(configSelect) {
  try {
    const configurations = await fetchConfigurations();
    updateConfigSelect(configurations, configSelect);
    if (configurations.length > 0) {
      configSelect.value = configurations[0].id_configuraciones || configurations[0].id;
    }
    return configurations;
  } catch (error) {
    console.error('Error fetching initial data:', error);
    displayError('Error al cargar los datos iniciales. Por favor, actualice la página.');
    throw error;
  }
}

/**
 * Submits a new configuration to the server.
 * @async
 * @param {FormData} formData - Form data containing configuration fields
 * @returns {Promise<Object>} The created configuration object
 * @throws {Error} If the submission fails
 */
export async function submitConfiguration(formData) {
  try {
    const response = await fetch('configurations', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams(formData).toString(),
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error submitting configuration:', errorData.error);
      displayError(errorData.error);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error submitting configuration:', error);
    if (!error.message.includes('Ya existe una configuracion con ese nombre')) {
      displayError('Error al guardar la configuración. Por favor, intente de nuevo.');
    }
    throw error;
  }
}

// --- Signal Type API Calls ---

/**
 * Fetches all available signal types from the server.
 * @async
 * @returns {Promise<Array<Object>>} Array of signal type objects
 * @throws {Error} If the fetch operation fails
 */
export async function fetchSignalTypes() {
  try {
    const response = await fetch('signal-types');
    if (!response.ok) {
      console.error('Error fetching signal types:', `HTTP error! status: ${response.status}`);
      displayError('Error al cargar los tipos de señal. Por favor, intente de nuevo.');
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const signalTypes = await response.json();
    if (!Array.isArray(signalTypes)) {
      console.error('Error fetching signal types: Invalid response format');
      displayError('Error en el formato de los tipos de señal recibidos.');
      throw new Error('Invalid response format: expected array of signal types');
    }
    return signalTypes;
  } catch (error) {
    console.error('Error fetching signal types:', error);
    displayError('Error al cargar los tipos de señal. Por favor, intente de nuevo.');
    throw error;
  }
}

// --- Component API Calls ---

/**
 * Fetches components of all types.
 * @async
 * @returns {Promise<void>}
 * @throws {Error} If fetching any component type fails
 */
export async function fetchComponents() {
  const types = ['coaxial', 'derivador', 'distribuidor', 'toma'];
  try {
    await Promise.all(types.map((type) => fetchComponentsByType(type)));
  } catch (error) {
    console.error('Error in fetchComponents:', error);
    displayError('Error al cargar algunos componentes. Por favor, intente actualizar la página.');
  }
}

/**
 * Fetches components of a specific type.
 * @async
 * @param {string} type - The component type (coaxial, derivador, distribuidor, toma)
 * @param {number|null} [idConfiguracion=null] - Optional configuration ID to filter components
 * @returns {Promise<Array<Object>>} Array of component objects
 * @throws {Error} If the fetch operation fails
 */
export async function fetchComponentsByType(type, idConfiguracion = null) {
  try {
    const url = new URL('components', window.location.href);
    url.searchParams.append('type', type);
    if (idConfiguracion) {
      url.searchParams.append('id_configuraciones', idConfiguracion);
    }
    const response = await fetch(url);
    if (!response.ok) {
      console.error(`Error fetching ${type} components:`, `HTTP error! status: ${response.status}`);
      displayError(`Error al cargar los componentes de tipo ${type}. Por favor, intente de nuevo.`);
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    if (!Array.isArray(data)) {
      console.error(`Error fetching ${type} components: Invalid response format`);
      displayError(`Error en el formato de los componentes de tipo ${type} recibidos.`);
      throw new Error('Invalid response format: expected array');
    }
    return data;
  } catch (error) {
    console.error(`Error fetching ${type} components:`, error);
    displayError(`Error al cargar los componentes de tipo ${type}. Por favor, intente de nuevo.`);
    throw error;
  }
}

/**
 * Fetches component details for all types for a specific configuration.
 * @async
 * @param {number} idConfiguracion - The configuration ID
 * @returns {Promise<void>}
 * @throws {Error} If fetching fails
 */
export async function fetchComponentDetails(idConfiguracion) {
  try {
    const types = ['coaxial', 'derivador', 'distribuidor', 'toma'];
    await Promise.all(types.map((type) => fetchComponentsByType(type, idConfiguracion)));
  } catch (error) {
    console.error('Error fetching component details:', error);
    displayError('Error al cargar los detalles de los componentes. Por favor, intente de nuevo.');
    throw error;
  }
}

/**
 * Fetches components by type and model name.
 * @async
 * @param {string} type - The component type
 * @param {string} model - The component model name
 * @returns {Promise<Array<Object>>} Array of matching components
 * @throws {Error} If the fetch operation fails
 */
export async function fetchComponentsByModel(type, model) {
  try {
    const url = new URL('components', window.location.href);
    url.searchParams.append('type', type);
    url.searchParams.append('model', model);
    const response = await fetch(url);
    if (!response.ok) {
      console.error(
        `Error fetching details for ${type} ${model}:`,
        `HTTP error! status: ${response.status}`,
      );
      displayError(
        `Error al cargar los detalles del componente ${model}. Por favor, intente de nuevo.`,
      );
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error(`Error fetching details for ${type} ${model}:`, error);
    displayError(
      `Error al cargar los detalles del componente ${model}. Por favor, intente de nuevo.`,
    );
    throw error;
  }
}

/**
 * Submits a new component to the server.
 * @async
 * @param {FormData} formData - Form data containing component properties
 * @returns {Promise<Object>} The created component object
 * @throws {Error} If the submission fails
 */
export async function submitComponent(formData) {
  try {
    // Convert form data to an object
    const data = {};
    formData.forEach((value, key) => {
      data[key] = !isNaN(value) && value !== '' ? Number(value) : value;
    });
    // Add component-specific properties
    const properties = {};
    switch (data.type) {
      case 'coaxial':
        properties.atenuacion_470mhz = data.atenuacion_470mhz;
        properties.atenuacion_694mhz = data.atenuacion_694mhz;
        break;
      case 'derivador':
        properties.atenuacion_derivacion = data.atenuacion_derivacion;
        properties.atenuacion_paso = data.atenuacion_paso;
        properties.directividad = data.directividad;
        properties.desacoplo = data.desacoplo;
        properties.perdidas_retorno = data.perdidas_retorno;
        break;
      case 'distribuidor':
        properties.numero_salidas = data.numero_salidas;
        properties.atenuacion_distribucion = data.atenuacion_distribucion;
        properties.desacoplo = data.desacoplo;
        properties.perdidas_retorno = data.perdidas_retorno;
        break;
      case 'toma':
        properties.atenuacion = data.atenuacion;
        properties.desacoplo = data.desacoplo;
        break;
    }
    // Create the request body
    const requestBody = new URLSearchParams({
      ...data,
      properties: JSON.stringify(properties),
    });
    const response = await fetch('components', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: requestBody.toString(),
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error submitting component:', errorData.error);
      displayError(errorData.error);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error submitting component:', error);
    if (!error.message.includes('Ya existe un componente con ese modelo')) {
      displayError('Error al guardar el componente. Por favor, intente de nuevo.');
    }
    throw error;
  }
}

// --- Simulation API Calls ---

/**
 * Runs a signal simulation with the provided data.
 * @async
 * @param {Object} simulationData - Simulation parameters including configuration, signal type, etc.
 * @returns {Promise<Object>} The simulation results
 * @throws {Error} If the simulation fails
 */
export async function runSimulation(simulationData) {
  try {
    const simulateResponse = await fetch('calculate', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(simulationData),
    });
    if (!simulateResponse.ok) {
      const errorText = await simulateResponse.text();
      console.error('Error during simulation:', errorText);
      displayError(
        'Error al ejecutar la simulación. Por favor, revise los datos e intente de nuevo.',
      );
      throw new Error(`HTTP error! status: ${simulateResponse.status}. ${errorText}`);
    }
    return await simulateResponse.json();
  } catch (error) {
    console.error('Error during simulation:', error);
    displayError('Error al ejecutar la simulación. Por favor, intente de nuevo.');
    throw error;
  }
}

/**
 * Deletes a component from the server.
 * @async
 * @param {string} type - The component type
 * @param {string} model - The component model name
 * @returns {Promise<Object>} The server response
 * @throws {Error} If the deletion fails
 */
export async function deleteComponent(type, model) {
  try {
    const url = new URL('components', window.location.href);
    url.searchParams.append('type', type);
    url.searchParams.append('modelo', model);
    const response = await fetch(url, { method: 'DELETE' });
    if (!response.ok) {
      const errorData = await response.json();
      console.error(`Error deleting ${type} ${model}:`, errorData.error);
      displayError(`Error al eliminar el componente ${model}: ${errorData.error}`);
      throw new Error(errorData.error);
    }
    const result = await response.json();
    return result;
  } catch (error) {
    console.error(`Error deleting ${type} ${model}:`, error);
    displayError(`Error al eliminar el componente ${model}. Por favor, intente de nuevo.`);
    throw error;
  }
}

/**
 * Updates an existing component on the server.
 * @async
 * @param {string} type - The component type
 * @param {string} model - The component model name
 * @param {FormData} formData - Form data containing updated component properties
 * @returns {Promise<Object>} The updated component object
 * @throws {Error} If the update fails
 */
export async function updateComponent(type, model, formData) {
  try {
    // Convert form data to an object
    const data = {};
    formData.forEach((value, key) => {
      if (key !== 'type' && key !== 'modelo') {
        data[key] = !isNaN(value) && value !== '' ? Number(value) : value;
      }
    });
    // Add component-specific properties
    const properties = {};
    switch (type.toLowerCase()) {
      case 'coaxial':
        properties.atenuacion_470mhz = data.atenuacion_470mhz;
        properties.atenuacion_694mhz = data.atenuacion_694mhz;
        break;
      case 'derivador':
        properties.atenuacion_derivacion = data.atenuacion_derivacion;
        properties.atenuacion_paso = data.atenuacion_paso;
        properties.directividad = data.directividad;
        properties.desacoplo = data.desacoplo;
        properties.perdidas_retorno = data.perdidas_retorno;
        break;
      case 'distribuidor':
        properties.numero_salidas = data.numero_salidas;
        properties.atenuacion_distribucion = data.atenuacion_distribucion;
        properties.desacoplo = data.desacoplo;
        properties.perdidas_retorno = data.perdidas_retorno;
        break;
      case 'toma':
        properties.atenuacion = data.atenuacion;
        properties.desacoplo = data.desacoplo;
        break;
    }
    // Create the request body
    const requestBody = new URLSearchParams({
      type: type,
      modelo: model,
      costo: data.costo,
      properties: JSON.stringify(properties),
    });
    const response = await fetch('components', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: requestBody.toString(),
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.error(`Error updating ${type} ${model}:`, errorData.error);
      displayError(`Error al actualizar el componente ${model}: ${errorData.error}`);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error(`Error updating ${type} ${model}:`, error);
    displayError(`Error al actualizar el componente ${model}. Por favor, intente de nuevo.`);
    throw error;
  }
}

// --- Simulation History API Calls ---
export async function fetchSimulationHistory(idConfiguracion) {
  try {
    const url = new URL('history', window.location.href);
    url.searchParams.append('id_configuraciones', idConfiguracion);
    const response = await fetch(url);
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error fetching simulation history:', errorData.error);
      displayError(`Error al cargar el historial de simulaciones: ${errorData.error}`);
      throw new Error(errorData.error || 'Error al cargar el historial de simulaciones');
    }
    return await response.json();
  } catch (error) {
    console.error('Error fetching simulation history:', error);
    displayError('Error al cargar el historial de simulaciones. Por favor, intente de nuevo.');
    throw error;
  }
}

export async function deleteSimulationHistory(idSimulacion) {
  try {
    const url = new URL('history', window.location.href);
    url.searchParams.append('id_simulaciones', idSimulacion);
    const response = await fetch(url, { method: 'DELETE' });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error deleting simulation history:', errorData.error);
      displayError(`Error al eliminar la simulación: ${errorData.error}`);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error deleting simulation history:', error);
    displayError('Error al eliminar la simulación. Por favor, intente de nuevo.');
    throw error;
  }
}

export async function saveSimulationHistory(simulationData) {
  try {
    const response = await fetch('history', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(simulationData),
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || 'Error al guardar la simulación');
    }
    return await response.json();
  } catch (error) {
    console.error('Error saving simulation history:', error);
    throw error;
  }
}

// --- Schematic API Calls ---
export async function saveSchematicComponent(componentData) {
  try {
    const response = await fetch('schematic', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams(componentData).toString(),
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error saving schematic component:', errorData.error);
      displayError(`Error al guardar el componente: ${errorData.error}`);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error saving schematic component:', error);
    displayError('Error al guardar el componente. Por favor, intente de nuevo.');
    throw error;
  }
}

export async function loadSchematic(idSimulacion) {
  try {
    const url = new URL('schematic', window.location.href);
    url.searchParams.append('id_simulaciones', idSimulacion);
    const response = await fetch(url);
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error loading schematic:', errorData.error);
      displayError(`Error al cargar el esquemático: ${errorData.error}`);
      throw new Error(errorData.error || 'Error al cargar el esquemático');
    }
    return await response.json();
  } catch (error) {
    console.error('Error loading schematic:', error);
    displayError('Error al cargar el esquemático. Por favor, intente de nuevo.');
    throw error;
  }
}

export async function deleteSchematicComponent(idEsquematico) {
  try {
    const url = new URL('schematic', window.location.href);
    url.searchParams.append('id_esquematicos', idEsquematico);
    const response = await fetch(url, { method: 'DELETE' });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error deleting schematic component:', errorData.error);
      displayError(`Error al eliminar el componente: ${errorData.error}`);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error deleting schematic component:', error);
    displayError('Error al eliminar el componente. Por favor, intente de nuevo.');
    throw error;
  }
}

// --- Results API Calls ---
export async function saveSimulationResults(simulationId, results) {
  try {
    const response = await fetch('results', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        id_simulaciones: simulationId,
        results: results,
      }),
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error saving simulation results:', errorData.error);
      displayError(errorData.error);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error saving simulation results:', error);
    displayError('Error al guardar los resultados de la simulación. Por favor, intente de nuevo.');
    throw error;
  }
}

export async function loadSimulationResults(idSimulacion) {
  try {
    const url = new URL('results', window.location.href);
    url.searchParams.append('id_simulaciones', idSimulacion);
    const response = await fetch(url);
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error loading simulation results:', errorData.error);
      displayError(`Error al cargar los resultados de la simulación: ${errorData.error}`);
      throw new Error(errorData.error || 'Error al cargar los resultados de la simulación');
    }
    return await response.json();
  } catch (error) {
    console.error('Error loading simulation results:', error);
    displayError('Error al cargar los resultados de la simulación. Por favor, intente de nuevo.');
    throw error;
  }
}

export async function deleteSimulationResults(idSimulacion) {
  try {
    const url = new URL('results', window.location.href);
    url.searchParams.append('id_simulaciones', idSimulacion);
    const response = await fetch(url, { method: 'DELETE' });
    if (!response.ok) {
      const errorData = await response.json();
      console.error('Error deleting simulation results:', errorData.error);
      displayError(`Error al eliminar los resultados de la simulación: ${errorData.error}`);
      throw new Error(errorData.error);
    }
    return await response.json();
  } catch (error) {
    console.error('Error deleting simulation results:', error);
    displayError('Error al eliminar los resultados de la simulación. Por favor, intente de nuevo.');
    throw error;
  }
}

export async function optimizeBuilding(optimizeData) {
  try {
    const response = await fetch('optimize', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(optimizeData),
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Error during optimization:', error);
    throw error;
  }
}