/**
 * Grav Form XHR Submitter
 *
 * A modular system for handling form submissions via XMLHttpRequest (AJAX).
 * Features include content replacement, captcha handling, and error management.
 */

(function() {
    'use strict';

    // Main namespace
    window.GravFormXHR = {};

    /**
     * Core Module - Contains configuration and utility functions
     */
    const Core = {
        config: {
            debug: false,
            enableLoadingIndicator: false
        },

        /**
         * Configure global settings
         * @param {Object} options - Configuration options
         */
        configure: function(options) {
            Object.assign(this.config, options);
        },

        /**
         * Logger utility
         * @param {string} message - Message to log
         * @param {string} level - Log level ('log', 'warn', 'error')
         */
        log: function(message, level = 'log') {
            if (!this.config.debug) return;

            const validLevels = ['log', 'warn', 'error'];
            const finalLevel = validLevels.includes(level) ? level : 'log';

            console[finalLevel](`[GravFormXHR] ${message}`);
        },

        /**
         * Display an error message within a target element
         * @param {HTMLElement} target - The element to display the error in
         * @param {string} message - The error message
         */
        displayError: function(target, message) {
            const errorMsgContainer = target.querySelector('.form-messages') || target;
            const errorMsg = document.createElement('div');
            errorMsg.className = 'form-message error';
            errorMsg.textContent = message;
            errorMsgContainer.insertBefore(errorMsg, errorMsgContainer.firstChild);
        }
    };

    /**
     * DOM Module - Handles DOM manipulation and form tracking
     */
    const DOM = {
        /**
         * Find a form wrapper by formId
         * @param {string} formId - ID of the form
         * @returns {HTMLElement|null} - The wrapper element or null
         */
        getFormWrapper: function(formId) {
            const wrapperId = formId + '-wrapper';
            return document.getElementById(wrapperId);
        },

        /**
         * Add or remove loading indicators
         * @param {HTMLElement} form - The form element
         * @param {HTMLElement} wrapper - The wrapper element
         * @param {boolean} isLoading - Whether to add or remove loading classes
         */
        updateLoadingState: function(form, wrapper, isLoading) {
            if (!Core.config.enableLoadingIndicator) return;

            if (isLoading) {
                wrapper.classList.add('loading');
                form.classList.add('submitting');
            } else {
                wrapper.classList.remove('loading');
                form.classList.remove('submitting');
            }
        },

        /**
         * Update form content with server response
         * @param {string} responseText - Server response HTML
         * @param {string} wrapperId - ID of the wrapper to update
         * @param {string} formId - ID of the original form
         */
        updateFormContent: function(responseText, wrapperId, formId) {
            const wrapperElement = document.getElementById(wrapperId);
            if (!wrapperElement) {
                console.error(`Cannot update content: Wrapper #${wrapperId} not found`);
                return;
            }

            Core.log(`Updating content for wrapper: ${wrapperId}`);

            // Parse response
            const tempDiv = document.createElement('div');
            try {
                tempDiv.innerHTML = responseText;
            } catch (e) {
                console.error(`Error parsing response HTML for wrapper: ${wrapperId}`, e);
                Core.displayError(wrapperElement, 'An error occurred processing the server response.');
                return;
            }

            try {
                this._updateWrapperContent(tempDiv, wrapperElement, wrapperId, formId);
                this._reinitializeUpdatedForm(wrapperElement, formId);
            } catch (e) {
                console.error(`Error during content update for wrapper ${wrapperId}:`, e);
                Core.displayError(wrapperElement, 'An error occurred updating the form content.');
            }
        },

        /**
                         * Update wrapper content based on response parsing strategy
                         * @private
                         */
        _updateWrapperContent: function(tempDiv, wrapperElement, wrapperId, formId) {
            // Strategy 1: Look for matching wrapper ID in response
            const newWrapperElement = tempDiv.querySelector('#' + wrapperId);

            if (newWrapperElement) {
                wrapperElement.innerHTML = newWrapperElement.innerHTML;
                Core.log(`Update using newWrapperElement.innerHTML SUCCESSFUL for wrapper: ${wrapperId}`);
                return;
            }

            // Strategy 2: Look for matching form ID in response
            const hasMatchingForm = tempDiv.querySelector('#' + formId);

            if (hasMatchingForm) {
                Core.log(`Wrapper element #${wrapperId} not found in XHR response, but found matching form. Using entire response.`);
                wrapperElement.innerHTML = tempDiv.innerHTML;
                return;
            }

            // Strategy 3: Look for toast messages
            const hasToastMessages = tempDiv.querySelector('.toast');

            if (hasToastMessages) {
                Core.log('Found toast messages in response. Updating wrapper with the response.');
                wrapperElement.innerHTML = tempDiv.innerHTML;
                return;
            }

            // Fallback: Use entire response with warning
            Core.log('No matching content found in response. Response may not be valid for this wrapper.', 'warn');
            wrapperElement.innerHTML = tempDiv.innerHTML;
        },

        /**
                         * Reinitialize updated form and its components
                         * @private
                         */
        _reinitializeUpdatedForm: function(wrapperElement, formId) {
            const updatedForm = wrapperElement.querySelector('#' + formId);

            if (updatedForm) {
                Core.log(`Re-running initialization for form ${formId} after update`);

                // First reinitialize any captchas
                CaptchaManager.reinitializeAll(updatedForm);

                // Trigger mutation._grav event for Dropzone and other field reinitializations
                setTimeout(() => {
                    Core.log('Triggering mutation._grav event for field reinitialization');

                    // Trigger using jQuery if available (preferred method for compatibility)
                    if (typeof jQuery !== 'undefined') {
                        jQuery('body').trigger('mutation._grav', [wrapperElement]);
                    } else {
                        // Fallback: dispatch native custom event
                        const event = new CustomEvent('mutation._grav', {
                            detail: { target: wrapperElement },
                            bubbles: true
                        });
                        document.body.dispatchEvent(event);
                    }
                }, 0);

                // Then re-attach the XHR listener
                setTimeout(() => {
                    FormHandler.setupListener(formId);
                }, 10);
            } else {
                // Check if this was a successful submission with just a message
                const hasSuccessMessage = wrapperElement.querySelector('.toast-success, .form-success');

                if (hasSuccessMessage) {
                    Core.log('No form found after update, but success message detected. This appears to be a successful submission.');
                } else {
                    console.warn(`Could not find form #${formId} inside the updated wrapper after update. Cannot re-attach listener/initializers.`);
                }
            }
        }
    };

    /**
                     * XHR Module - Handles XMLHttpRequest operations
                     */
    const XHRManager = {
        /**
                         * Send form data via XHR
                         * @param {HTMLFormElement} form - The form to submit
                         */
        sendFormData: function(form) {
            const formId = form.id;
            const wrapperId = formId + '-wrapper';
            const wrapperElement = DOM.getFormWrapper(formId);

            if (!wrapperElement) {
                console.error(`XHR submission: Target wrapper element #${wrapperId} not found on the page! Cannot proceed.`);
                form.innerHTML = '<p class="form-message error">Error: Form wrapper missing. Cannot update content.</p>';
                return;
            }

            Core.log(`Initiating XHR submission for form: ${formId}, targeting wrapper: ${wrapperId}`);
            DOM.updateLoadingState(form, wrapperElement, true);

            const xhr = new XMLHttpRequest();
            xhr.open(form.getAttribute('method') || 'POST', form.getAttribute('action') || window.location.href);

            // Set Headers
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.setRequestHeader('X-Grav-Form-XHR', 'true');

            // Success handler
            xhr.onload = () => {
                Core.log(`XHR request completed for form: ${formId}, Status: ${xhr.status}`);
                DOM.updateLoadingState(form, wrapperElement, false);

                if (xhr.status >= 200 && xhr.status < 300) {
                    DOM.updateFormContent(xhr.responseText, wrapperId, formId);
                } else {
                    Core.log(`Form submission failed for form: ${formId}, HTTP Status: ${xhr.status} ${xhr.statusText}`, 'error');
                    Core.displayError(wrapperElement, `An error occurred during submission (Status: ${xhr.status}). Please check the form and try again.`);
                }
            };

            // Network error handler
            xhr.onerror = () => {
                Core.log(`Form submission failed due to network error for form: ${formId}`, 'error');
                DOM.updateLoadingState(form, wrapperElement, false);
                Core.displayError(wrapperElement, 'A network error occurred. Please check your connection and try again.');
            };

            // Prepare and send data
            try {
                const formData = new FormData(form);
                const urlEncodedData = new URLSearchParams(formData).toString();
                Core.log(`Sending XHR request for form: ${formId} with custom header X-Grav-Form-XHR`);
                xhr.send(urlEncodedData);
            } catch (e) {
                Core.log(`Error preparing or sending XHR request for form: ${formId}: ${e.message}`, 'error');
                DOM.updateLoadingState(form, wrapperElement, false);
                Core.displayError(wrapperElement, 'An unexpected error occurred before sending the form.');
            }
        }
    };

    /**
                     * CaptchaManager - Handles captcha registration and initialization
                     */
    const CaptchaManager = {
        providers: {},

        /**
                         * Register a captcha provider
                         * @param {string} name - Provider name
                         * @param {object} provider - Provider object with init and reset methods
                         */
        register: function(name, provider) {
            this.providers[name] = provider;
            Core.log(`Registered captcha provider: ${name}`);
        },

        /**
                         * Get a provider by name
                         * @param {string} name - Provider name
                         * @returns {object|null} Provider object or null if not found
                         */
        getProvider: function(name) {
            return this.providers[name] || null;
        },

        /**
                         * Get all registered providers
                         * @returns {object} Object containing all providers
                         */
        getProviders: function() {
            return this.providers;
        },

        /**
                         * Reinitialize all captchas in a form
                         * @param {HTMLFormElement} form - Form element containing captchas
                         */
        reinitializeAll: function(form) {
            if (!form || !form.id) return;

            const formId = form.id;
            const containers = form.querySelectorAll('[data-captcha-provider]');

            containers.forEach(container => {
                const providerName = container.dataset.captchaProvider;
                Core.log(`Found captcha container for provider: ${providerName} in form: ${formId}`);

                const provider = this.getProvider(providerName);
                if (provider && typeof provider.reset === 'function') {
                    setTimeout(() => {
                        try {
                            provider.reset(container, form);
                            Core.log(`Successfully reset ${providerName} captcha in form: ${formId}`);
                        } catch (e) {
                            console.error(`Error resetting ${providerName} captcha:`, e);
                        }
                    }, 0);
                } else {
                    console.warn(`Could not reset captcha provider "${providerName}" - provider not registered or missing reset method`);
                }
            });
        }
    };

    /**
                     * FormHandler - Handles form submission and event listeners
                     */
    const FormHandler = {
        /**
                         * Submit a form via XHR
                         * @param {HTMLFormElement} form - Form to submit
                         */
        submitForm: function(form) {
            if (!form || !form.id) {
                console.error('submitForm called with invalid form element or form missing ID.');
                return;
            }

            XHRManager.sendFormData(form);
        },

        /**
                         * Set up XHR submission listener for a form
                         * @param {string} formId - ID of the form
                         */
        setupListener: function(formId) {
            setTimeout(() => {
                const form = document.getElementById(formId);
                if (!form) {
                    Core.log(`XHR Setup (delayed): Form with ID "${formId}" not found.`, 'warn');
                    return;
                }

                // Check if any captcha provider is handling the submission
                const captchaContainer = form.querySelector('[data-captcha-provider][data-intercepts-submit="true"]');

                if (!captchaContainer) {
                    // No intercepting captcha found, attach direct listener
                    this._attachDirectListener(form);
                } else {
                    // Captcha will intercept, don't attach direct listener
                    const providerName = captchaContainer.dataset.captchaProvider;
                    Core.log(`XHR listener deferred: ${providerName} should intercept submit for form: ${formId}`);
                    this._detachDirectListener(form);
                }
            }, 0);
        },

        /**
                         * Attach a direct submit event listener to a form
                         * @private
                         * @param {HTMLFormElement} form - Form element
                         */
        _detachDirectListener: function(form) {
            if (form.__gravDirectXhrHandler) {
                form.removeEventListener('submit', form.__gravDirectXhrHandler);
                delete form.__gravDirectXhrHandler;
            }
            delete form.dataset.directXhrListenerAttached;
        },

        _attachDirectListener: function(form) {
            // Only proceed if XHR is enabled for this form
            if (form.dataset.xhrEnabled !== 'true') {
                Core.log(`XHR not enabled for form: ${form.id}. Skipping direct listener attachment.`);
                return;
            }

            // Re-arm rather than stack another listener. setupListener() legitimately
            // runs more than once for the same element (on page load, and again after
            // every XHR wrapper update), and the old dataset guard could never catch
            // that because setupListener() deleted the marker immediately before
            // calling this. The result was one listener per call, so a single submit
            // fired N XHR requests -- with cap + xhr_submit that showed up as a
            // duplicate POST whose response replaced the real one.
            this._detachDirectListener(form);

            const directXhrSubmitHandler = (event) => {
                Core.log(`Direct XHR submit handler triggered for form: ${form.id}`);
                event.preventDefault();
                FormHandler.submitForm(form);
            };

            Core.log(`Attaching direct XHR listener for form: ${form.id}`);
            form.__gravDirectXhrHandler = directXhrSubmitHandler;
            form.addEventListener('submit', directXhrSubmitHandler);
            form.dataset.directXhrListenerAttached = 'true';
        }
    };

    // Initialize basic built-in captcha handlers
    // Other providers will register themselves via separate handler JS files
    const initializeBasicCaptchaHandlers = function() {
        // Basic captcha handler (image refresh etc.)
        CaptchaManager.register('basic-captcha', {
            reset: function(container, form) {
                const formId = form.id;
                const captchaImg = container.querySelector('img');
                const captchaInput = container.querySelector('input[type="text"]');

                if (captchaImg) {
                    // Add a timestamp to force image reload
                    const timestamp = new Date().getTime();
                    const imgSrc = captchaImg.src.split('?')[0] + '?t=' + timestamp;
                    captchaImg.src = imgSrc;

                    // Clear any existing input
                    if (captchaInput) {
                        captchaInput.value = '';
                    }

                    Core.log(`Reset basic-captcha for form: ${formId}`);
                }
            }
        });
    };

    // Initialize basic captcha handlers
    initializeBasicCaptchaHandlers();

    // --- Expose Public API ---

    // Core configuration
    window.GravFormXHR.configure = Core.configure.bind(Core);

    // Form submission
    window.GravFormXHR.submit = FormHandler.submitForm.bind(FormHandler);
    window.GravFormXHR.setupListener = FormHandler.setupListener.bind(FormHandler);

    // Captcha management
    window.GravFormXHR.captcha = CaptchaManager;

    // Legacy support
    window.GravFormXHRSubmitters = {submit: FormHandler.submitForm.bind(FormHandler)};
    window.attachFormSubmitListener = FormHandler.setupListener.bind(FormHandler);

})();
