/**************************************************************************************
 *
 * Catalyst PHP Framework - JavaScript Component
 * ES6+/ES7 Standard
 *
 * @package   Catalyst
 * @subpackage Js
 * @see       https://github.com/arcanisgk/catalyst
 *
 * @author    Walter Nuñez (arcanisgk/original founder) < [email protected]>
 * @copyright 2023 - 2025
 * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 *
 * @note      This program is distributed in the hope that it will be useful
 *            WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *            or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @category  Framework
 * @filesource
 *
 * @link      https://catalyst.dock Local development URL
 *
 * Main component for the Catalyst Framework
 *
 */
 
 /**
 * Handle OAuth credential operations with specialized UI updates
 *
 * @param {string} action - The action to perform ('save' or 'clear')
 * @param {HTMLFormElement} form - The credentials form
 * @param {string} serviceKey - The service key identifier
 * @param {bootstrap.Modal} modal - The Bootstrap modal to hide on success
 * @returns {Promise} - Promise that resolves when operation is complete
 */
async function handleOAuthCredentials(action, form, serviceKey, modal) {
    try {
        let url, data;
        if (action === 'save') {
            url = '/configure/oauth/save';
            const formData = new FormData(form);
            data = new URLSearchParams(formData).toString();
        } else if (action === 'clear') {
            url = '/configure/oauth/clear';
            data = `service_key=${serviceKey}`;
        } else {
            throw new Error('Invalid action');
        }
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'X-Requested-With': 'XMLHttpRequest'
            },
            body: data
        });
        const result = await response.json();
        if (result.success) {
            // Get service checkbox for UI updates
            const serviceCheckbox = document.querySelector(`.service-checkbox[data-service="${serviceKey}"]`);
            if (serviceCheckbox) {
                const serviceItem = serviceCheckbox.closest('.service-item');
                if (serviceItem) {
                    if (action === 'save') {
                        // Add success badge if saving
                        const badgeParent = serviceItem.querySelector('.form-check').parentElement;
                        if (!badgeParent.querySelector('.text-success')) {
                            const badge = document.createElement('span');
                            badge.className = 'ms-1 text-success';
                            badge.title = 'Credentials configured';
                            badge.innerHTML = '<i class="bi bi-check-circle"></i>';
                            badgeParent.appendChild(badge);
                        }
                    } else if (action === 'clear') {
                        // Remove badge if clearing
                        const badge = serviceItem.querySelector('.text-success');
                        if (badge) badge.remove();
                    }
                }
            }
            // Reset form and hide modal
            if (form) form.reset();
            if (modal) modal.hide();
            // Show success toast
            window.toasts.success(`Service credentials ${action === 'save' ? 'saved' : 'cleared'} successfully`);
        } else {
            window.toasts.error(`Error: ${result.message}`);
        }
        return result;
    } catch (error) {
        console.error(`Error ${action}ing credentials:`, error);
        window.toasts.error(`An error occurred while ${action}ing credentials`);
        throw error;
    }
}
/**
 * Handle configuration form submissions with standard UI feedback
 *
 * @param {HTMLFormElement|string} form - Form element or form selector
 * @param {string} endpoint - API endpoint to submit data to
 * @param {Object} options - Additional options
 * @returns {Promise} - Promise that resolves when submission is complete
 */
async function handleConfigSubmit(form, endpoint, options = {}) {
    // Default options
    const defaults = {
        redirectDelay: 1000,
        loadingText: '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Saving...',
        submitSelector: 'button[type="submit"]',
        collectFromForms: null, // Optional selector for multiple forms to collect data from
        preProcess: null, // Optional function to process data before submission
    };
    options = {...defaults, ...options};
    // Get the form element if a selector was provided
    if (typeof form === 'string') {
        form = document.querySelector(form);
    }
    if (!form) {
        console.error('Form not found');
        return false;
    }
    // Find submit button
    const submitButton = options.submitSelector instanceof HTMLElement
        ? options.submitSelector
        : form.querySelector(options.submitSelector);
    // Store original button content
    const originalButtonContent = submitButton ? submitButton.innerHTML : '';
    try {
        // Disable submit button and show loading indicator
        if (submitButton) {
            submitButton.disabled = true;
            submitButton.innerHTML = options.loadingText;
        }
        // Collect form data
        let formData = new FormData(form);
        // If we need to collect data from multiple forms
        if (options.collectFromForms) {
            const additionalForms = document.querySelectorAll(options.collectFromForms);
            additionalForms.forEach(additionalForm => {
                const additionalFormData = new FormData(additionalForm);
                for (const [key, value] of additionalFormData.entries()) {
                    formData.append(key, value);
                }
            });
        }
        // Get CSRF token from the form or document
        const csrfToken = form.querySelector('input[name="csrf_token"]')?.value ||
            document.querySelector('input[name="csrf_token"]')?.value;
        // Ensure CSRF token is included
        if (csrfToken && !formData.has('csrf_token')) {
            formData.append('csrf_token', csrfToken);
        }
        // Allow pre-processing of form data if needed
        if (typeof options.preProcess === 'function') {
            formData = options.preProcess(formData);
        }
        // Send the request
        const response = await fetch(endpoint, {
            method: 'POST',
            body: formData,
            headers: {
                'X-CSRF-TOKEN': csrfToken || '',
                'X-Requested-With': 'XMLHttpRequest'
            }
        });
        // Parse the response
        const result = await response.json();
        // Handle the result
        if (result.success) {
            window.toasts.success(result.message || 'Configuration saved successfully');
            // Redirect if specified
            if (result.redirect) {
                setTimeout(() => {
                    window.location.href = result.redirect;
                }, options.redirectDelay);
            }
            return true;
        } else {
            window.toasts.error(result.message || 'Failed to save configuration');
            console.error('Form submission error:', result);
            return false;
        }
    } catch (error) {
        console.error('Form submission error:', error);
        window.toasts.error('An unexpected error occurred');
        return false;
    } finally {
        // Restore submit button
        if (submitButton) {
            submitButton.disabled = false;
            submitButton.innerHTML = originalButtonContent;
        }
    }
}
/**
 * Función utilitaria para peticiones AJAX
 * @param {string} url - URL del endpoint
 * @param {Object} data - Datos a enviar
 * @param {Object} options - Opciones adicionales
 * @returns {Promise} - Promesa con la respuesta JSON
 */
async function apiPost(url, data, options = {redirectDelay: 1000, handleRedirect: true}) {
    try {
        // Add CSRF token to the data if it doesn't already exist
        if (typeof data === 'object' && data !== null) {
            // Get CSRF token from any form or meta tag on the page
            const csrfTokenElement = document.querySelector('input[name="csrf_token"]');
            const csrfToken = csrfTokenElement ? csrfTokenElement.value : null;
            if (csrfToken && !data.csrf_token) {
                data.csrf_token = csrfToken;
            }
        }
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest',
                'Accept': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('input[name="csrf_token"]')?.value || ''
            },
            body: JSON.stringify(data)
        });
        const result = await response.json();
        // Manejo estandarizado de respuestas
        if (result.success) {
            window.toasts.success(result.message);
            // Handle redirect if present and not disabled in options
            if (result.redirect && options.handleRedirect !== false) {
                // Optional delay to allow toast to be seen
                if (options.redirectDelay) {
                    await new Promise(resolve => setTimeout(resolve, options.redirectDelay));
                }
                window.location.href = result.redirect;
            }
        } else {
            window.toasts.error(result.message || 'Error en la operación');
        }
        return result;
    } catch (error) {
        console.error('Error en petición API:', error);
        window.toasts.error('Error de conexión');
        throw error;
    }
}
// Generic password toggle functionality
/**
 * Password Toggle Utility
 *
 * A standalone utility that adds toggle functionality to password fields
 * Works with both static and dynamically added elements
 */
(function () {
    'use strict';
    /**
     * Initialize password toggle functionality
     */
    function initPasswordToggle() {
        // Use event delegation for all toggle password buttons
        document.addEventListener('click', function (event) {
            // Find if a toggle-password button was clicked or any of its children
            const toggleButton = event.target.closest('.toggle-password');
            if (!toggleButton) return;
            // Find the associated input field (should be a sibling in the same input-group)
            const inputGroup = toggleButton.closest('.input-group');
            if (!inputGroup) return;
            const passwordInput = inputGroup.querySelector('input[type="password"], input[type="text"]');
            if (!passwordInput) return;
            // Find the icon element
            const iconElement = toggleButton.querySelector('i, .bi');
            // Toggle the password visibility
            if (passwordInput.type === 'password') {
                passwordInput.type = 'text';
                if (iconElement) {
                    iconElement.classList.remove('bi-eye');
                    iconElement.classList.add('bi-eye-slash');
                }
            } else {
                passwordInput.type = 'password';
                if (iconElement) {
                    iconElement.classList.remove('bi-eye-slash');
                    iconElement.classList.add('bi-eye');
                }
            }
            // Prevent the default button action
            event.preventDefault();
        });
    }
    // Initialize when the DOM is fully loaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initPasswordToggle);
    } else {
        initPasswordToggle();
    }
})();
// Add global CSRF protection for all fetch requests
(function () {
    // Store the original fetch function
    const originalFetch = window.fetch;
    // Override fetch with our version that adds CSRF tokens
    window.fetch = function (url, options = {}) {
        // Don't modify GET requests or requests that already have a body
        if (options.method && options.method.toUpperCase() !== 'GET') {
            const csrfToken = document.querySelector('input[name="csrf_token"]')?.value;
            if (csrfToken) {
                // If it's a FormData object, append the token
                if (options.body instanceof FormData) {
                    if (!options.body.has('csrf_token')) {
                        options.body.append('csrf_token', csrfToken);
                    }
                } else if (typeof options.body === 'string' && options.headers?.['Content-Type'] === 'application/json') {
                    // If it's JSON, parse and add token
                    try {
                        const bodyData = JSON.parse(options.body);
                        if (!bodyData.csrf_token) {
                            bodyData.csrf_token = csrfToken;
                            options.body = JSON.stringify(bodyData);
                        }
                    } catch (e) {
                        // Not valid JSON, leave as is
                    }
                }
                // Add CSRF header for all non-GET requests
                options.headers = options.headers || {};
                if (!options.headers['X-CSRF-TOKEN']) {
                    options.headers['X-CSRF-TOKEN'] = csrfToken;
                }
            }
        }
        // Call the original fetch with our modified options
        return originalFetch(url, options);
    };
})();
 
  |