//
//  H5ErrorRelay.swift
//  ylgamehall
//
//  H5 错误中继：把 WebView 内 `console.error` / `window.onerror` /
//  `unhandledrejection` 通过 `webkit.messageHandlers.h5error` 上报到原生 print。
//
//  Phase 9.2（Design §11.2）。msext 时代仅靠用户/运营手工反馈；本项目作为
//  原则 B「内部自由现代化」的一部分，开发期把 H5 异常打到 Xcode console，
//  减少调试盲点。生产期无开关控制（输出 print 默认走 OSLog，对体积影响极小）。
//
//  **不修改 H5 一行**（契约原则 A）：用 WKUserScript `.atDocumentStart` 在
//  业务 JS 跑前就 hook，不要求 H5 团队配合。
//
//  仅在 BridgedWebView（大厅 + 子游戏）注入；OverlayViewController 是
//  第三方外链，错误不属于 ylgamehall 内部信号，故不挂。
//

import Foundation
import WebKit

@MainActor
public final class H5ErrorRelay: NSObject {

    public static let shared = H5ErrorRelay()

    /// WKScriptMessageHandler 名字。与下面 JS source 内 `webkit.messageHandlers.h5error` 必须一致。
    public static let messageName = "h5error"

    public override init() { super.init() }

    /// 把 hook JS + message handler 注册到给定的 UserContentController。
    /// BridgedWebView.init 在挂 WVJB 之后调一次。
    public func install(into controller: WKUserContentController) {
        let userScript = WKUserScript(
            source: Self.javaScriptSource,
            injectionTime: .atDocumentStart,  // 业务 JS 之前 hook
            forMainFrameOnly: true             // iframe 不抓
        )
        controller.addUserScript(userScript)
        controller.add(MessageProxy(target: self), name: Self.messageName)
    }

    fileprivate func handle(_ body: Any) {
        guard let dict = body as? [String: Any],
              let kind = dict["kind"] as? String else { return }
        switch kind {
        case "console.error":
            let args = (dict["args"] as? [String]) ?? []
            diagLog("[H5 console.error] " + args.joined(separator: " "))
        case "console.warn":
            // 桥诊断需要：WebViewJavascriptBridge.js 在「Native 发来的 handlerName
            // H5 侧没注册」时走 console.warn，之前不上报导致这类丢包完全不可见。
            let args = (dict["args"] as? [String]) ?? []
            diagLog("[H5 console.warn] " + args.joined(separator: " "))
        case "onerror":
            let msg  = (dict["msg"]  as? String) ?? ""
            let src  = (dict["src"]  as? String) ?? ""
            let line = (dict["line"] as? Int) ?? 0
            let col  = (dict["col"]  as? Int) ?? 0
            let stack = dict["stack"] as? String
            diagLog("[H5 onerror] \(msg) at \(src):\(line):\(col)" + (stack.map { "\n\($0)" } ?? ""))
        case "unhandledrejection":
            let reason = (dict["reason"] as? String) ?? ""
            let stack = dict["stack"] as? String
            diagLog("[H5 unhandledrejection] " + reason + (stack.map { "\n\($0)" } ?? ""))
        default:
            diagLog("[H5 unknown error] \(dict)")
        }
    }

    /// JS 端 hook 三类异常源，统一通过 `webkit.messageHandlers.h5error.postMessage(payload)`
    /// 上报。所有 send 调用都包 try/catch，hook 自身永远不应抛错（避免污染业务流程）。
    private static let javaScriptSource = """
    (function(){
      function send(payload){
        try { window.webkit.messageHandlers.h5error.postMessage(payload); } catch(e){}
      }
      var origErr = console.error;
      console.error = function(){
        try {
          var args = [];
          for (var i=0; i<arguments.length; i++) { args.push(String(arguments[i])); }
          send({ kind:'console.error', args: args });
        } catch(e){}
        if (origErr) { origErr.apply(console, arguments); }
      };
      var origWarn = console.warn;
      console.warn = function(){
        try {
          var args = [];
          for (var i=0; i<arguments.length; i++) { args.push(String(arguments[i])); }
          send({ kind:'console.warn', args: args });
        } catch(e){}
        if (origWarn) { origWarn.apply(console, arguments); }
      };
      window.addEventListener('error', function(ev){
        send({
          kind:'onerror',
          msg:  String(ev.message  || ''),
          src:  String(ev.filename || ''),
          line: ev.lineno || 0,
          col:  ev.colno  || 0,
          stack: (ev.error && ev.error.stack) ? String(ev.error.stack) : null
        });
      });
      window.addEventListener('unhandledrejection', function(ev){
        var r = ev.reason;
        send({
          kind:'unhandledrejection',
          reason: String(r),
          stack:  (r && r.stack) ? String(r.stack) : null
        });
      });
    })();
    """
}

// MARK: - WKScriptMessageHandler proxy（weak target 切断 retain cycle）

@MainActor
private final class MessageProxy: NSObject, WKScriptMessageHandler {
    private weak var target: H5ErrorRelay?

    init(target: H5ErrorRelay) {
        self.target = target
        super.init()
    }

    func userContentController(_ ucc: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        // WKScriptMessageHandler 在主队列派发；MessageProxy / H5ErrorRelay 都
        // MainActor，直接调即可，无需 Task 切换。
        target?.handle(message.body)
    }
}
