(gina.ready(function onGinaReady($){
/**
 * Main handler
 */

// var eio             = require('vendor/engine.io');
var FormValidator   = require('gina/validator');
var PopinHandler    = require('gina/popin');

var MainHandler = ( function onPubliMainHandled() {
    var self                = {}
        , $validator        = null
        , $popin            = null
        , $subscribeForm    = null
        , $submit           = null
    ;



    var init = function() {

        onSessionTimeout();

        // Engine.io
        // mountSocket();

        // xhr partials listener
        document.addEventListener('xhr:partials:ready', function onXhrPartialsReady() {
            document.removeEventListener('xhr:partials:ready', onXhrPartialsReady);
            handle();
        });
        //  gina.config.webroot +'js/vendor/swig.min.js',
        requirejs([gina.config.webroot +'handlers/_xhr-tag-helper.js']);
    }


    /**
     * Remove the signup form for an authenticated visitor.
     *
     * The logged-in branch is rendered by the `/auth/status` XHR partial in
     * partials/subscription.html — it cannot be gated server-side, because this
     * section ships inside render-cached marketing pages (see that partial's note).
     * Returns true when the visitor is logged in, so the caller skips binding a
     * form that is no longer in the DOM.
     */
    var removeSubscribeFormWhenLoggedIn = function() {
        if ( !document.querySelector('.js-subscription-logged-in') ) return false;

        var $form = document.getElementById('subscribe');
        var $wrap = ($form && $form.parentNode) ? $form.parentNode : null;
        if ($wrap && $wrap.parentNode) {
            $wrap.parentNode.removeChild($wrap);
        }

        // The trial pitch only makes sense to a visitor who has no account yet.
        var $pitch = document.querySelector('.subscription .subscription-call-to-action');
        if ($pitch && $pitch.parentNode) {
            $pitch.parentNode.removeChild($pitch);
        }

        return true;
    };


    var bindSubscribeForm = function() {
        // Guard: don't let Gina cache a null-target entry before XHR injects the form
        if (!document.getElementById('subscribe')) return false;


        $subscribeForm  = $validator.getFormById('subscribe');
        // $subscribeForm.reBind();
        $submit         = ($subscribeForm) ? $subscribeForm.target.querySelector('button[type="submit"]') : null;


        if (!$subscribeForm) return false;

        // Password show/hide is handled by the Design System passwordField
        // (shared/js/utilities/form/password.js), loaded via /js/ds-public.js,
        // which auto-enhances the field on load.

        // ⛔ Hand-rolled double-submit guard REMOVED (2026-08-06). It set the NATIVE
        // `disabled` attribute inside this very click, and from gina 0.6.4 the framework's
        // own submit gate READS that attribute: `#B246` at validator/src/main.js:8016 calls
        // isTriggerDisabled(), whose predicate at :8697 accepts native `disabled` and not
        // only gina's own `aria-disabled` marker. Our listener ran first, synchronously, so
        // by the time gina's click proxy looked the trigger already read disabled and the
        // whole submit cycle was refused — `send()` never called, no XHR, `isSubmitting`
        // never latched. The `error` handler below then cleared the attribute, so the button
        // looked perfectly normal and EVERY subsequent click was eaten the same way, with no
        // console error and nothing marked. That is the "registration submit does nothing"
        // symptom, and it is ours, not a blanket gina regression.
        //
        // Measured (anonymous page, real pointer clicks, isTrusted asserted, all POSTs
        // intercepted): guard active -> 0 send()/0 POST; this one write neutralised ->
        // send() fires and exactly one POST. A/B on published client bytes, same page and
        // same code: 0.6.4 -> 0 dispatch, 0.6.3 -> 1 dispatch. `isTriggerDisabled` does not
        // exist in the published 0.6.3 source at all.
        //
        // ⚠️ CORRECTED 2026-08-06, same day, and the retraction never reached this comment
        // until now — `500b8e452`'s own message already says this, so believe the message,
        // not any older phrasing you find here.
        //
        // The removal was FIRST justified as "gina's send() arms the same native attribute
        // in flight, so the framework already supplies this protection". THAT IS FALSE, and
        // it is the sentence most likely to send you looking for a framework guarantee that
        // does not exist. Measured with two ORDINARY clicks 80ms apart, no forcing: the
        // second click still lands — two requests, on 0.6.4 AND on 0.6.3, with the guard
        // present or absent.
        //
        // What actually makes the removal safe is narrower: on the DEPLOYED pin the guard
        // made no difference whatsoever, so removing it regresses nothing. The double-submit
        // window is PRE-EXISTING and UNCHANGED by this commit — it is not closed here and
        // nothing else closes it. It fails loudly rather than silently: the server rejects
        // the duplicate email.
        //
        // ⛔ Do NOT read any of this as "a double-submit guard is unnecessary". If you decide
        // one is needed, it is genuinely absent — see the constraint below on HOW.
        //
        // ⚠️ If a guard is ever needed again, do NOT set `disabled` or `aria-disabled` on
        // the trigger during the click: both are read by that gate. Use a non-attribute flag.
        //
        // $submit.addEventListener('click', function (e) {
        //     //e.preventDefault();
        //     this.setAttribute('disabled', 'disabled');
        // });

        $subscribeForm
            // Belt only, since the click-time disable above was removed (2026-08-06): the
            // attribute this clears is now gina's OWN in-flight arming, which gina already
            // releases on settle. Kept because it is idempotent and costs nothing, and it
            // still covers BOTH blocked paths (measured: gina emits `error` for a natively
            // invalid form AND for one its own rules reject). Fires once per failing rule.
            .on('error', function (e, errors) {
                $submit.removeAttribute('disabled');
            })
            .on('success', function (e, result) {
                e.preventDefault();

                if ( result.isAuthenticated ) {
                    window.location.hash = ''; //removing hashtag
                    window.location.href = result.location;
                }
            })
        ;

        return true;
    };


    var handle = function () {
        // Runs on `xhr:partials:ready`, so the `/auth/status` partial has rendered
        // and the logged-in branch (if any) is already in the DOM.
        if ( removeSubscribeFormWhenLoggedIn() ) return;

        bindSubscribeForm();
    };

    var mountSocket = function() {
        // Engine IO
        // IMPORTANT: `ws` for http & `wss` for https
        //socket = eio('wss://ws-webhook-freelancer.com', {secure: true, reconnect: true}); // prod with nginx
        var socketOptions = {
            port: 8889, // targeted IO server : 8889 is for the ConfigWatcher
            pingInterval: 5000,
            pingTimeout: 60000,
            secure: true,
            reconnect: true,
            // connect to public notification
            path: gina.config.webroot+'push/public',
            // prefred order: https://ably.com/blog/websockets-vs-long-polling
            transports: ["websocket", "polling"]
        };
        // same host
        var scheme = ( /^https/.test(window.location.protocol) ) ? 'wss' : 'ws';
        // var hostname = scheme+'://'+ gina.config.hostname.replace(/^(https|http):\/\//, '').replace(/\:\d+$/, ':'+ socketOptions.port);
        // var hostname = scheme+'://'+ document.location.hostname.replace(/\:\d+$/, ':'+ socketOptions.port);
        var hostname = scheme+'://'+ document.location.hostname;
        var port = document.location.port;
        if (port && port != 443) {
            hostname += ':'+ ~~(port)
        }
        socket = eio(hostname, socketOptions);


        socket.on('open', function(){
            // on.message is handled thru `on("message")` method
            var cookie      = parseCookie(document.cookie);
            var sessionId   = null;
            if (cookie.sessionid) {
                sessionId = cookie.sessionid.match(/^[s\:]{2}?(.*)\./)[1];
            }
            var id          = socket.id;

            this.pingTimeout = 60000;

            socket.send( JSON.stringify({ session : { id: sessionId } }));
            socket.on('close', function(reason){
                console.debug('socket `#'+ id +'` closed !\nReason: '+ reason);
            });

            socket.on('message', function (payload) {
                if ( typeof(payload) == 'string' && /^(\[|\{)/.test(payload) ) {
                    payload = JSON.parse(payload);
                }
                console.debug(payload, '\nSession: '+ cookie.sessionid);
                console.debug('pingTimeout', this.pingTimeout);

                if ( typeof(payload.section) != 'undefined' ) {
                    self.onPush(payload, sessionId);
                }
            });
        });
    };

    var parseCookie = function(cookie) {
        var obj = {}, arr = cookie.split(/\; /g), el = null;

        for (var i = 0, len = arr.length; i < len; ++i) {
            el = arr[i].split(/\=/);
            obj[ el[0] ] = decodeURIComponent(el[1])
        }

        return obj
    }

    // Old defineProperties — removed (was a jQuery $.fn.attr override, no longer needed)
    // var defineProperties = function() { ... }

    var onSessionTimeout = function() {

        // Start timeout
        setTimeout( function onLegalConfirmTimeout() {
            // Legal confirmation page
            if ( /\/legal-confirm/.test(window.location.href) ) {
                console.warn('/legal-confirm: Session timeout !');
                // Return to homepage. Use the route's relative `.url` (e.g. `/`), NOT
                // `.toUrl()` — under a reverse proxy in dev, toUrl() (and toUrl(true))
                // prepend the raw bundle host:port (…:5134) because the route's
                // isProxyHost/proxy_hostname is unset on this client path, bouncing the
                // browser off the proxy host. A relative URL keeps it on the proxy host.
                window.location.href = Routing().getRoute('home@public', {}).url;

                return;
            }

            // By default, reload after 1 sec to get a new session
            // window.location.reload();

        }, gina.session.timeout);
    }


    var onGenericXhrResponse = function (event, result) {

        var status = null;
        if ( typeof(event.type) != 'undefined' ) {
            status = event.type.split(/\./)[0];
        }
        // status -> `event.type` ; /^success/.test(event.type)
        console.debug('[ onGenericXhrResponse ] : ', event, result);


        // Generic events handlers
        // handle reload
        if (
            typeof(result.status) != 'undefined' && result.status == 401  && typeof(result.reload) != 'undefined' && result.reload == true
            || typeof(result.status) != 'undefined' && /^2/.test(result.status) && typeof(result.reload) != 'undefined' && result.reload == true
            || typeof(result.operation) != 'undefined' && result.operation == 'reload'
        ) {
            window.location.hash = ''; //removing hashtag
            // Defer so the XHR response body is fully available to CDP
            // before the page unloads (prevents r.json() race in Playwright).
            setTimeout(function() { window.location.reload(); }, 0);
            return false;
        }

        // handle redirect
        if ( typeof(result.error) != 'undefined' && typeof(result.error.sessionError) != 'undefined' ) {
            window.location.hash = ''; //removing hashtag

            result.location = (!/^http/.test(result.location) && !/^\//.test(result.location) ) ? location.protocol +'//' + result.location : result.location;
            // Defer so the XHR response body is fully available to CDP
            // before the page unloads (prevents r.json() race in Playwright).
            setTimeout(function() { window.location.href = result.location; }, 0);
            return false;
        }

    }

    var dependencies = ['formValidator', 'popinHandler'], triggered = false;
    var onReady = function (dependency) {

        dependencies.splice(dependency, 1);
        if ( !dependencies.length && !triggered ) {
            triggered = true;
            init();
        }
    };

    /**  adding dependencies */
    // loading formValidator
    var formValidator = new FormValidator(gina.forms.rules);
    formValidator.on('ready', function (e, validator) {
        e.preventDefault();

        // useful for CORS
        validator.setOptions({ withCredentials: true });

        // Built-in validator labels are translated in this bundle's i18n catalog
        // (`locales/<lang>.json`, `_validator` namespace). Gina resolves the negotiated
        // culture server-side and whispers that subset into `gina.config.validatorLabels`;
        // FormValidator overlays it over its English defaults at construction time.

        // exposing globals
        $validator = validator; // formValidator

        // exports
        window.onGenericXhrResponse = onGenericXhrResponse; // application events handler

        onReady('formValidator');
    });

    // loading popin handler
    var popinHandler = new PopinHandler({ name: 'default', validator: formValidator, preOpen: true });
    popinHandler.on('ready', function (e, popin) {
        e.preventDefault();

        $popin = popin;

        onReady('popinHandler')
    });

})();
},window[originalContext]));
