function ValidationRequest(id) {
    this.id = id;
    this.state = null;
    this.browser = null;
    this.form = null;
    this.validationContainer = null;
    this.captchaContainer = null;
    this.captchaField = null;
    this.captchaAction = null;
    this.captchaKey = null;

    this.init = function () {
        this.setState('loading');
        this.setBrowser();
        this.setForm();
        this.setValidationContainer();
    }

    this.setState = function (state) {
        this.state = state;
        window.dispatchEvent(new CustomEvent(`validation_request:${state}`, {
            id: this.id,
            state: state,
        }));
    }

    this.setBrowser = function () {
        this.browser = navigator.userAgent;
    }

    this.isFireFox = function () {
        return this.browser.includes('Firefox');
    }

    this.setForm = function () {
        this.form = document.getElementById(this.id);
        if (! this.form) {
            throw new Error(`Form with id ${this.id} not found`);
        }
    }

    this.setSubmitEventHandler = function () {

        // Bind the submit buttons for firefox compatibility
        let submitButtons = this.form.querySelectorAll('button[type="submit"], input[type="submit"]');
        submitButtons.forEach((button) => {
            button.addEventListener('click', (event) => {
                event.preventDefault();
                this.generateCaptchaToken();
            });
        });

        // Bind the form submit event for other browsers
        this.form.addEventListener('submit', (event) => {
            event.preventDefault();
            this.generateCaptchaToken();
        });
    }

    this.setValidationContainer = function () {
        this.validationContainer = this.form.getElementsByClassName("validation")[0];
        if (! this.validationContainer) {
            throw new Error(`Unable to create validation container for form ${this.id}`);
        }

        // Make the ajax request
        let xmlhttp = new XMLHttpRequest();
        xmlhttp.onreadystatechange = () => {

            // When the rest finishes
            if (xmlhttp.readyState === XMLHttpRequest.DONE) {

                // If the status code is not 200, then
                if (xmlhttp.status !== 200) {
                    console.log(xmlhttp.responseText);
                    return;
                }

                // Otherwise update the container HTML
                this.validationContainer.innerHTML = xmlhttp.responseText;

                // Prepend the csrf token & set the captcha
                this.setCsrfField();
                this.setCaptcha();

                this.setState('ready');
            }
        };
        xmlhttp.open("GET", "/api/builder/validation", true);
        xmlhttp.send();
    }

    this.getCsrfToken = function () {
        return document.head.querySelector('meta[name="csrf-token"]').content;
    }

    this.setCsrfField = function () {
        let csrfField = document.createElement("input");
        csrfField.setAttribute("type", "hidden");
        csrfField.setAttribute("name", "_token");
        csrfField.setAttribute("value", this.getCsrfToken());

        this.validationContainer.prepend(csrfField);
    }

    this.setCaptcha = function () {
        this.setCaptchaContainer();

        // If the captcha is not enabled, then bail
        if (! this.captchaIsEnabled()) {
            console.log(`Captcha not enabled for ${this.id}`);

            return;
        }

        // Otherwise
        this.checkForCaptchaScript();
        this.setCaptchaField();
        this.setCaptchaAction();
        this.setCaptchaKey();

        // Add the form event handler for the captcha
        this.setSubmitEventHandler();
    }

    this.checkForCaptchaScript = function () {
        if (typeof window.grecaptcha === 'undefined') {
            throw 'Google reCAPTCHA script not loaded.';
        }
    }

    this.setCaptchaContainer = function () {
        this.captchaContainer = this.validationContainer.getElementsByClassName("captcha-container")[0];
        if (! this.captchaContainer) {
            throw new Error(`Unable to find captcha container for form ${this.id}`);
        }
    }

    this.captchaIsEnabled = function () {
        return this.captchaContainer.hasAttribute('data-captcha')
            && this.captchaContainer.getAttribute('data-captcha') === '1';
    }

    this.setCaptchaField = function () {
        let fieldId = this.captchaContainer.getAttribute('data-captcha-field');
        this.captchaField = document.getElementById(fieldId);
        if (! this.captchaField) {
            throw new Error(`Unable to find captcha field for form ${this.id}`);
        }
    }

    this.setCaptchaAction = function () {
        this.captchaAction = this.captchaContainer.getAttribute('data-captcha-action');
        if (! this.captchaAction) {
            throw new Error(`Unable to find captcha action for form ${this.id}`);
        }
    }

    this.setCaptchaKey = function () {
        this.captchaKey = this.captchaContainer.getAttribute('data-captcha-key');
        if (! this.captchaKey) {
            throw new Error(`Unable to find captcha key for form ${this.id}`);
        }
    }

    this.generateCaptchaToken = function () {
        window.grecaptcha.ready(() => {
            window.grecaptcha
                .execute(this.captchaKey, {action: this.captchaAction})
                .then((token) => {
                    this.captchaField.value = token;
                    this.form.submit();
                })
                .catch((error) => {
                    console.error('Recaptcha error:', error);
                });
        });
    }
}
