/*
 * WebViewJavascriptBridge.js
 *
 * H5 端桥协议代码，与 Native BridgeBus（@MainActor BridgeProtocol 实现）
 * 一一对应。详见 docs/H5-Native-Implementation-Design.md §3.2 / §3.3。
 *
 * ⚠️ 本文件的**语义**必须与原工程 msext 的 `WebViewJavascriptBridge_JS.m`
 * （marcuswestin 原版，daoqi/msext/Class/WebViewJavascriptBridge/）保持一致。
 * 传输层可以现代化（msext 用 iframe + _fetchQueue 拉取，我们用
 * WKScriptMessageHandler 直推），但**派发时机与消息字段必须逐项对齐**，
 * 否则 H5 侧同一份代码在两个壳里的执行顺序不同 → 行为不一致。
 *
 * 已对齐的 msext 语义（每一条都曾经不一致，是排查「子游戏定位拿不到」时发现的）：
 *
 *  1. **异步派发**：msext `dispatchMessagesWithTimeoutSafety = true`，且
 *     `disableJavscriptAlertBoxSafetyTimeout` 在整个 msext 工程里**从未被调用**，
 *     所以 Native → H5 的每一条消息都走 `setTimeout(_doDispatch)`，在**新的
 *     macrotask** 上执行。早前本文件是同步派发（在 evaluateJavaScript 内直接
 *     调 handler），H5 handler 相对自身 pending 脚本/微任务的顺序与 msext 不同。
 *  2. **WVJBCallbacks 用 `setTimeout(..., 0)` flush**（msext `_callWVJBCallbacks`），
 *     不是同步 flush —— 决定 H5 的 `registerHandler` 何时生效。
 *  3. **H5 → Native 的 response 消息带 `handlerName`**（msext `_doSend({handlerName,
 *     responseId, responseData})`）。
 *  4. `_disableJavascriptAlertBoxSafetyTimeout` 作为 H5 侧 handler 注册（msext 有）。
 *  5. 缺省安装 `window.onerror`（msext 在 H5 未装时兜底装一个）。
 *  6. 「收到 Native 消息但没有对应 handler」用 `console.log`（msext 原文），不是 warn。
 *
 * 协议（Native ↔ H5 双向 JSON 消息）：
 *   - H5 → Native：window.webkit.messageHandlers.WVJBHandler.postMessage({...})
 *   - Native → H5：window.WebViewJavascriptBridge._handleMessageFromObjC('<base64>')
 *   - 单条消息：
 *       { handlerName, data?, callbackId? }     // 主动调用对端 handler
 *       { responseId, responseData? }            // 响应对端早前 callHandler 的 callbackId
 */

;(function () {
    if (window.WebViewJavascriptBridge) { return; }

    // msext 同款：H5 没装 onerror 时兜底装一个
    if (!window.onerror) {
        window.onerror = function (msg, url, line) {
            console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
        };
    }

    // ── 内部状态 ─────────────────────────────────────────────
    var messageHandlers = {};    // H5 注册的 handler：name → fn(data, responseCallback)
    var responseCallbacks = {};  // 等待 Native 响应的 JS 回调：callbackId → fn(responseData)
    var nextCallbackId = 1;
    // msext `dispatchMessagesWithTimeoutSafety`：默认 true，仅在 Native 下发
    // `_disableJavascriptAlertBoxSafetyTimeout` 时置 false（msext 从不下发）。
    var dispatchMessagesWithTimeoutSafety = true;

    // ── 对外 API ─────────────────────────────────────────────
    var bridge = window.WebViewJavascriptBridge = {

        /**
         * H5 注册 handler，等待 Native 主动 callHandler。重名覆盖。
         */
        registerHandler: function (handlerName, handler) {
            messageHandlers[handlerName] = handler;
        },

        /**
         * H5 主动调 Native handler。
         * msext `callHandler` 支持 2 参形式（第二个参数是函数时视为 responseCallback）。
         */
        callHandler: function (handlerName, data, responseCallback) {
            if (arguments.length === 2 && typeof data === 'function') {
                responseCallback = data;
                data = null;
            }
            _doSend({ handlerName: handlerName, data: data }, responseCallback);
        },

        disableJavscriptAlertBoxSafetyTimeout: function () {
            dispatchMessagesWithTimeoutSafety = false;
        },

        /**
         * Native → H5 入口。BridgeBus.sendToJS 会注入：
         *   window.WebViewJavascriptBridge._handleMessageFromObjC('<base64of JSON>')
         * base64 包装是为了避免 JSON 内容里的单引号 / 反斜杠扰乱 evaluateJavaScript
         * 的字符串字面量（msext 走的是逐个 escape，等价）。
         *
         * ⚠️ 无返回值：msext 的派发是 setTimeout 异步的，拿不到「H5 有没有跑到
         * handler」的同步结果。原生侧不要依赖它的返回值做诊断。
         */
        _handleMessageFromObjC: function (base64String) {
            _dispatchMessageFromObjC(base64String);
        }
    };

    // ── 内部 ─────────────────────────────────────────────────

    function _postMessageToNative(message) {
        try {
            window.webkit.messageHandlers.WVJBHandler.postMessage(message);
        } catch (e) {
            console.log('[WVJB] post to native failed (WVJBHandler 通道未注册?):', e);
        }
    }

    function _doSend(message, responseCallback) {
        if (typeof responseCallback === 'function') {
            var callbackId = 'cb_' + (nextCallbackId++) + '_' + new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message['callbackId'] = callbackId;
        }
        _postMessageToNative(message);
    }

    /// msext `_dispatchMessageFromObjC` 逐句等价：默认走 setTimeout（新 macrotask）。
    function _dispatchMessageFromObjC(base64String) {
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
            _doDispatchMessageFromObjC();
        }

        function _doDispatchMessageFromObjC() {
            var message;
            try {
                message = JSON.parse(_decodeBase64UTF8(base64String));
            } catch (e) {
                console.log('[WVJB] handle message from native failed:', e);
                return;
            }

            var responseCallback;

            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) { return; }
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
                return;
            }

            if (message.callbackId) {
                var callbackResponseId = message.callbackId;
                responseCallback = function (responseData) {
                    // msext 同款：response 消息里**带 handlerName**
                    _doSend({
                        handlerName:  message.handlerName,
                        responseId:   callbackResponseId,
                        responseData: responseData
                    });
                };
            }

            var handler = messageHandlers[message.handlerName];
            if (!handler) {
                console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                // 诊断上报（不改 msext 语义，只是额外往自家通道发一条原生侧忽略的消息）
                _postMessageToNative({ diagDispatched: message.handlerName, hadHandler: false });
            } else {
                var threw = null;
                try { handler(message.data, responseCallback); }
                catch (e) { threw = String(e); }
                _postMessageToNative({
                    diagDispatched: message.handlerName,
                    hadHandler: true,
                    threw: threw
                });
                // msext 不吞异常（handler 抛错会冒到 setTimeout 的全局 onerror）；
                // 这里把它原样重抛，保持行为一致，只是先记录了一笔。
                if (threw !== null) { throw new Error(threw); }
            }
        }
    }

    /**
     * 标准 base64 → UTF-8 字符串。
     * 直接 atob() 只能处理 latin1；这里用 decodeURIComponent + escape 处理多字节字符。
     */
    function _decodeBase64UTF8(b64) {
        return decodeURIComponent(Array.prototype.map.call(atob(b64), function (c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }

    // msext 同款：把 Native 可下发的 safety-timeout 开关注册成 H5 侧 handler
    bridge.registerHandler("_disableJavascriptAlertBoxSafetyTimeout",
                           bridge.disableJavscriptAlertBoxSafetyTimeout);

    // ── 告知原生「桥已就绪」──────────────────────────────────
    // 等价 msext 的 `__bridge_loaded__` iframe 回调 → `injectJavascriptFile` →
    // flush `startupMessageQueue`。原生在收到本消息前发出的 callHandler 必须排队，
    // 不能丢（msext 有 startupMessageQueue，早前我们是直接丢弃）。
    _postMessageToNative({ bridgeReady: true });

    // ── marcuswestin 旧式握手兼容 ─────────────────────────────
    // msext `_callWVJBCallbacks` 用 setTimeout(..., 0)，**不是同步 flush**：
    // 这决定 H5 的 registerHandler 相对页面其它顶层脚本的生效时机。
    setTimeout(function () {
        var callbacks = window.WVJBCallbacks;
        if (!callbacks) { return; }
        delete window.WVJBCallbacks;
        for (var i = 0; i < callbacks.length; i++) {
            try { callbacks[i](bridge); }
            catch (e) { console.log('[WVJB] pending WVJBCallback threw:', e); }
        }
    }, 0);
})();
