/**
 * f5bridge.js — injected into the BIG-IP portal page at document-start
 *
 * Replaces Qt WebChannel (qwebchannel.js + VpnWebBridge) with a native
 * WebKit message handler bridge.
 *
 * JS → C:  window.webkit.messageHandlers.f5native.postMessage(json)
 * C → JS:  webkit_web_view_evaluate_javascript calls window.f5bridge._emit()
 *
 * The old Qt client injected qwebchannel.js and registered VpnWebBridge as
 * "external", then set:
 *   var external;
 *   new QWebChannel(qt.webChannelTransport, function(ch) {
 *       external = ch.objects.external;
 *   });
 *
 * The portal page's JavaScript therefore accesses the bridge as:
 *   external.onTunnelConnected.connect(fn)
 *   external.disconnectResource(id)
 *
 * We replicate this by setting window.external directly to a signal-proxy
 * object, exactly as the Qt WebChannel would expose VpnWebBridge.
 */

(function () {
    'use strict';

    /* Prevent double-injection */
    if (window.__f5bridge_injected) return;
    window.__f5bridge_injected = true;

    /* ---------------------------------------------------------------- */
    /* Forward bridge log messages to C via f5log handler              */
    /* ---------------------------------------------------------------- */

    function _log(level, msg) {
        var text = '[f5bridge:' + level + '] ' + msg;
        /* Keep native console working too */
        if (level === 'error') console.error(text);
        else if (level === 'warn')  console.warn(text);
        else                        console.log(text);
        /* Forward to C logger */
        if (window.webkit && window.webkit.messageHandlers &&
                window.webkit.messageHandlers.f5log) {
            window.webkit.messageHandlers.f5log.postMessage(text);
        }
    }

    /* ---------------------------------------------------------------- */
    /* Event handler registry                                           */
    /* Portal handlers only — single fn per event (overwrite semantics)*/
    /* matching old Qt client behaviour: last connect() wins.           */
    /* ---------------------------------------------------------------- */

    var _handlers = {};  /* event -> fn (portal, overwrite) */

    /* Register a portal handler (called via external.onXxx.connect) */
    function on(event, fn) {
        _handlers[event] = fn;
    }

    function _emit(event, args) {
        _log('info', '_emit ' + event + ' args=' + JSON.stringify(args));
        if (_handlers[event]) {
            try { _handlers[event].apply(null, args); }
            catch (e) { _log('error', 'handler error for ' + event + ': ' + e); }
        } else {
            _log('warn', 'no handler registered for ' + event);
        }
    }

    /* ---------------------------------------------------------------- */
    /* Send message to native side                                       */
    /* ---------------------------------------------------------------- */

    function _send(obj) {
        var json = JSON.stringify(obj);
        if (window.webkit && window.webkit.messageHandlers &&
                window.webkit.messageHandlers.f5native) {
            window.webkit.messageHandlers.f5native.postMessage(json);
        } else {
            console.warn('[f5bridge] native handler not available, msg:', json);
        }
    }

    var _extLoggingEnabled  = false;
    var _disconnectSent     = false;  /* one-shot guard: prevent double-disconnect */

    /* ---------------------------------------------------------------- */
    /* VpnWebBridge signal proxy                                         */
    /* Matches the Qt VpnWebBridge signals and invokable methods.       */
    /* The portal page accesses this as window.external (set below).    */
    /* ---------------------------------------------------------------- */

    var vpnBridgeProxy = {
        /* Signals — portal calls: external.onXxx.connect(fn) */
        onTunnelAdd:             { connect: function(fn) { _log('info','handler registered: onTunnelAdd'); on('onTunnelAdd', fn); } },
        onTunnelConnecting:      { connect: function(fn) { _log('info','handler registered: onTunnelConnecting'); on('onTunnelConnecting', fn); } },
        onTunnelConnected:       { connect: function(fn) { _log('info','handler registered: onTunnelConnected'); on('onTunnelConnected', fn); } },
        onTunnelReconnecting:    { connect: function(fn) { _log('info','handler registered: onTunnelReconnecting'); on('onTunnelReconnecting', fn); } },
        onTunnelDisconnected:    { connect: function(fn) { _log('info','handler registered: onTunnelDisconnected'); on('onTunnelDisconnected', fn); } },
        onTunnelError:           { connect: function(fn) { _log('info','handler registered: onTunnelError'); on('onTunnelError', fn); } },
        onTunnelData:            { connect: function(fn) { _log('info','handler registered: onTunnelData'); on('onTunnelData', fn); } },
        onConnectionDetails:     { connect: function(fn) { _log('info','handler registered: onConnectionDetails'); on('onConnectionDetails', fn); } },
        onResourceStatusChanged: { connect: function(fn) { _log('info','handler registered: onResourceStatusChanged'); on('onResourceStatusChanged', fn); } },

        /* Q_INVOKABLE methods — portal calls: external.disconnectResource(id) */
        launchApplication: function(path, args) {
            _log('info', 'launchApplication: ' + path + ' args=' + JSON.stringify(args));
            /* Normalise args to an array of strings */
            var argArr = Array.isArray(args) ? args : (args ? [String(args)] : []);
            _send({ cmd: 'launchApplication', path: path, args: argArr });
        },
        disconnectResource: function(id) {
            /* One-shot guard applies ONLY to generic disconnect (no resource_id).
             * Portal green dot clicks (with resource_id) must NOT be blocked so users
             * can cancel a close dialog and try again. */
            if (!id || id.length === 0) {
                /* Generic disconnect from connected.html button — apply one-shot guard */
                if (_disconnectSent) {
                    _log('warn', 'disconnectResource: already sent — ignoring duplicate call');
                    return;
                }
                _disconnectSent = true;
            } else {
                /* Resource-specific call from portal green dot — always allow through */
                _log('info', 'disconnectResource called with resource_id=' + id);
            }
            _log('info', 'disconnectResource called with id=' + id);
            _send({ cmd: 'disconnect', id: id });
        },
        setQuitMessage: function(msg) { /* no-op */ },
        getRoutingTable: function() { return window._f5RoutingTable || ''; },
        getIpTable: function() { return window._f5IpTable || ''; },
        showLog: function() { _send({ cmd: 'showLog' }); },
        setLogLevel: function(extended) {
            _extLoggingEnabled = !!extended;
            _send({ cmd: 'setLogLevel', extended: _extLoggingEnabled });
        },
        getLogLevel: function() { return _extLoggingEnabled; },
        requestTunnelInfo: function() { _send({ cmd: 'tunnelInfo' }); },

        /* Alias: some portal versions call connectResource via bridge */
        connectResource: function(id, type, params) {
            _send({ cmd: 'connect', id: id, type: type, params: params || {} });
        }
    };

    /* ---------------------------------------------------------------- */
    /* window.external — primary interface the portal page uses         */
    /* Mirrors what the Qt client set up via:                           */
    /*   external = channel.objects.external  (VpnWebBridge registered  */
    /*                                          as "external")          */
    /* ---------------------------------------------------------------- */
    window.external = vpnBridgeProxy;

    /* ---------------------------------------------------------------- */
    /* DEBUG: Proxy wrapper — logs every property access on window.external
     * so we can see exactly what the portal JS calls after onTunnelConnected.
     * Remove once launchApplication is confirmed working.               */
    /* ---------------------------------------------------------------- */
    if (typeof Proxy !== 'undefined') {
        window.external = new Proxy(vpnBridgeProxy, {
            get: function(target, prop) {
                var val = target[prop];
                if (typeof val === 'function') {
                    return function() {
                        var argsArr = Array.prototype.slice.call(arguments);
                        _log('info', '[external.' + prop + '] called, args=' +
                             JSON.stringify(argsArr));
                        return val.apply(target, arguments);
                    };
                }
                if (typeof val === 'object' && val !== null &&
                        typeof val.connect === 'function') {
                    /* Signal object — log connect() calls */
                    return {
                        connect: function(fn) {
                            _log('info', '[external.' + prop + '.connect] registered');
                            return val.connect(fn);
                        }
                    };
                }
                _log('info', '[external.' + prop + '] get → ' + typeof val);
                return val;
            }
        });
    }

    /* ---------------------------------------------------------------- */
    window.f5bridge = {
        on:    on,
        _emit: _emit,
        _send: _send,

        connectResource:    vpnBridgeProxy.connectResource,
        disconnectResource: vpnBridgeProxy.disconnectResource,
        getResourceStatus: function(id) { _send({ cmd: 'status', id: id }); },
        getTunnelStats:    function(id) { _send({ cmd: 'stats', id: id }); }
    };

    /* ---------------------------------------------------------------- */
    /* Compatibility alias: f5vpnctrl (used by some portal versions)    */
    /* ---------------------------------------------------------------- */
    window.f5vpnctrl = window.f5bridge;

    /* ---------------------------------------------------------------- */
    /* Qt WebChannel compatibility shim                                 */
    /* Handles: new QWebChannel(qt.webChannelTransport, callback)       */
    /* Exposes VpnWebBridge as both "external" and "VpnWebBridge".      */
    /* ---------------------------------------------------------------- */

    if (typeof window.qt === 'undefined') {
        window.qt = {
            webChannelTransport: {
                send: function () {}
            }
        };
    }

    if (typeof window.QWebChannel === 'undefined') {
        window.QWebChannel = function (transport, callback) {
            var self = this;
            self.objects = {
                /* Old Qt client registered as "external" */
                external:    vpnBridgeProxy,
                /* Alias under class name for compatibility */
                VpnWebBridge: vpnBridgeProxy
            };
            /* Deliver asynchronously like the real QWebChannel */
            setTimeout(function () { callback(self); }, 0);
        };
    }

    _log('info', 'injected — window.external set, WebKit native bridge active');

    /* ---------------------------------------------------------------- */
    /* Override window.close() — BIG-IP portal Disconnect calls          */
    /* OnExit() → window.close().  WebKit2GTK only fires close-web-view  */
    /* for JS-created windows, not our programmatic webview.             */
    /* Route it through the native bridge as a 'windowClose' command.    */
    /* ---------------------------------------------------------------- */
    window.close = function() {
        _log('info', 'window.close() intercepted — sending windowClose to native');
        _send({ cmd: 'windowClose' });
    };

    /* ---------------------------------------------------------------- */
    /* DEBUG: intercept window.open to see if portal uses it for launch */
    /* ---------------------------------------------------------------- */
    var _origOpen = window.open;
    window.open = function(url, target, features) {
        _log('info', 'window.open() called: url=' + url +
             ' target=' + target + ' features=' + features);
        return _origOpen.apply(window, arguments);
    };

    /* DEBUG: log clicks on any <a> or element with href/onclick */
    document.addEventListener('click', function(e) {
        var el = e.target;
        while (el && el !== document.body) {
            if (el.tagName === 'A' || el.tagName === 'BUTTON' ||
                    el.tagName === 'INPUT') {
                _log('info', 'CLICK: <' + el.tagName.toLowerCase() + '>' +
                     ' href=' + (el.href || '') +
                     ' id=' + (el.id || '') +
                     ' text=' + (el.innerText || '').substring(0, 60));
                break;
            }
            el = el.parentElement;
        }
    }, true);

})();
