import CoreTelephony
import Foundation
import os
import UIKit
import WebKit

/// SDK 内置 ``WKNavigationDelegate``。
///
/// **拦截优先级**(对应 design.md D7 + Android `QianWebViewClient`):
/// 1. `qianapp://` → ``SchemeInterceptor`` 解析 → 回调 + `.cancel`
/// 2. `http(s)://` → 默认导航(`.allow`)
/// 3. `externalSchemes` 白名单 → `UIApplication.open` → `.cancel`
/// 4. 未知 scheme → 丢弃 + `schemeNotMatched` 事件
///
/// **下载处理**(统一 URLSession + Cookie 同步，design.md D23):
/// - `navigationAction.shouldPerformDownload`(iOS 14.5+) → `.cancel` + URLSession 下载
/// - `navigationResponse` 不可渲染 / PDF / attachment → `.cancel` + URLSession 下载
/// - 下载由 ``QianDownloadManager`` 接管（全版本统一路径）
///
/// **错误事件**:`didFailProvisionalNavigation` / `didFail` / 主框架 HTTP 错误(>=400)上报。
///
/// **iOS 等价**:Android `QianWebViewClient`。
final class QianNavigationDelegate: NSObject, WKNavigationDelegate {

    private let config: TencentQianWebViewConfig
    private let schemeInterceptor: SchemeInterceptor
    private let externalSchemeHandler: ExternalSchemeHandler
    private let onEvent: ((SdkEvent) -> Void)?
    private var downloadManager: QianDownloadManager?

    /// 网络权限弹窗导致首次加载失败时的自动重试计数器。
    /// iOS 首次安装 App 弹出"允许使用无线数据"时,WKWebView 请求会被阻断,
    /// 用户确认后系统不会自动重试,需要 SDK 主动 reload。
    private var networkRetryCount = 0
    private static let maxNetworkRetry = 5

    /// 渲染进程崩溃连续恢复计数，防止"必崩页面"死循环 reload。
    /// 每次崩溃恢复 +1，页面成功加载完成（didFinish）时清零；连续崩溃达
    /// ``maxRenderRecovery`` 次仍未成功 → 判定不可恢复，停止 reload，避免 reload 风暴。
    /// 对齐 Android renderRecoveryCount。
    private var renderRecoveryCount = 0
    private static let maxRenderRecovery = 3

    /// 崩溃恢复计数的冷却窗口（秒）。距上次崩溃超过此窗口视为页面已稳定运行过，
    /// 本次崩溃按全新问题处理并重置连续计数，避免长时间运行中的偶发（非连续）崩溃
    /// 累计触顶被误判为"不可恢复"。
    private static let renderRecoveryResetWindow: TimeInterval = 30
    /// 上次渲染进程崩溃的时间戳（主线程访问，无并发）。
    private var lastRenderTerminateAt: TimeInterval = 0

    /// 渲染进程崩溃后延迟恢复的秒数。崩溃(尤其 OOM)后立即 reload 易因内存仍紧张再次被回收
    /// 陷入循环，延迟给系统留资源回收窗口(对齐社区实践 / HarmonyOS RECOVER_DELAY_MS)。
    private static let renderRecoveryDelay: TimeInterval = 0.5

    /// CTCellularData：监听 App 网络权限状态变化，用户授权后自动 reload
    private var cellularData: CTCellularData?
    private var networkRetryFallbackWorkItem: DispatchWorkItem?
    private var hasNetworkRetryReloaded = false
    /// CTCellularData 初始化后会立即触发一次当前状态回调，需要跳过
    private var isFirstCellularCallback = true
    /// didBecomeActive 通知观察者：系统弹窗（网络权限弹窗）消失时 App 从 inactive → active，
    /// 此时立即尝试 reload，作为 CTCellularData 的补充检测手段。
    private var becomeActiveObserver: NSObjectProtocol?
    /// 保存最近一次 provisional navigation 的 URL，用于网络权限恢复后重新加载。
    /// 因为 provisional navigation 失败后 webView.url 为 nil，webView.reload() 无效，
    /// 必须用 webView.load(request) 重新发起请求。
    private var lastProvisionalURL: URL?

    /// 受控 WKWebView weak ref，用于白屏自检与恢复。
    private weak var boundWebView: WKWebView?
    /// 常驻 didBecomeActive 监听，覆盖后台渲染进程回收场景。
    private var lifetimeBecomeActiveObserver: NSObjectProtocol?

    init(
        config: TencentQianWebViewConfig,
        schemeInterceptor: SchemeInterceptor,
        externalSchemeHandler: ExternalSchemeHandler,
        onEvent: ((SdkEvent) -> Void)?
    ) {
        self.config = config
        self.schemeInterceptor = schemeInterceptor
        self.externalSchemeHandler = externalSchemeHandler
        self.onEvent = onEvent
        super.init()
        installLifetimeBecomeActiveObserver()
    }

    deinit {
        cancelNetworkMonitor()
        if let observer = lifetimeBecomeActiveObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }

    /// 设置下载管理器
    func setDownloadManager(_ manager: QianDownloadManager?) {
        self.downloadManager = manager
    }

    // MARK: - 拦截

    /// iOS 13+ 导航决策入口（带 preferences 参数）
    @available(iOS 13.0, *)
    func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationAction: WKNavigationAction,
        preferences: WKWebpagePreferences,
        decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void
    ) {
        handleNavigationAction(navigationAction) { policy in
            decisionHandler(policy, preferences)
        }
    }

    /// iOS 11-12 导航决策入口（无 preferences 参数）
    func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationAction: WKNavigationAction,
        decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
    ) {
        handleNavigationAction(navigationAction) { policy in
            decisionHandler(policy)
        }
    }

    /// 公共导航决策逻辑（scheme 拦截 → http(s) 下载检测 → external scheme → 兜底取消）
    private func handleNavigationAction(
        _ navigationAction: WKNavigationAction,
        completion: @escaping (WKNavigationActionPolicy) -> Void
    ) {
        guard let url = navigationAction.request.url else {
            completion(.cancel)
            return
        }
        let urlString = url.absoluteString

        // 1. qianapp
        if schemeInterceptor.handle(urlString) {
            completion(.cancel)
            return
        }

        // 2. http(s) 走默认（但先检查 shouldPerformDownload）
        let scheme = url.scheme?.lowercased() ?? ""
        if scheme == "http" || scheme == "https" {
            // iOS 14.5+: <a download> 属性触发 → 直接走 URLSession 下载
            if #available(iOS 14.5, *), navigationAction.shouldPerformDownload {
                completion(.cancel)
                downloadManager?.download(url: url, response: nil, downloadWithCookies: config.downloadWithCookies)
                return
            }
            // 保存主框架导航的 URL，用于网络权限恢复后重新加载
            if navigationAction.targetFrame?.isMainFrame ?? true {
                lastProvisionalURL = url
            }
            completion(.allow)
            return
        }

        // 3 & 4. external + 兜底
        if externalSchemeHandler.tryOpen(urlString) {
            completion(.cancel)
            return
        }

        completion(.cancel)
    }

    func webView(
        _ webView: WKWebView,
        decidePolicyFor navigationResponse: WKNavigationResponse,
        decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void
    ) {
        // 下载处理
        let decision = DownloadResponseHandler.decide(
            response: navigationResponse,
            onEvent: onEvent
        )
        switch decision {
        case .cancel:
            decisionHandler(.cancel)
            return
        case .download:
            // 统一走 URLSession 下载（全版本），按配置决定是否同步 Cookie
            decisionHandler(.cancel)
            if let url = navigationResponse.response.url {
                downloadManager?.download(url: url, response: navigationResponse.response, downloadWithCookies: config.downloadWithCookies)
            }
            return
        case .allow:
            break
        }

        // 主框架 HTTP 错误
        if navigationResponse.isForMainFrame,
           let http = navigationResponse.response as? HTTPURLResponse,
           http.statusCode >= 400 {
            onEvent?(.httpError(
                url: http.url?.absoluteString ?? "",
                statusCode: http.statusCode,
                reasonPhrase: HTTPURLResponse.localizedString(forStatusCode: http.statusCode)
            ))
        }

        decisionHandler(.allow)
    }

    // MARK: - 生命周期

    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        onEvent?(.pageStarted(url: webView.url?.absoluteString ?? lastProvisionalURL?.absoluteString ?? ""))
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // 页面加载成功,重置网络重试计数器并清理 monitor
        networkRetryCount = 0
        // 页面成功加载完成 → 崩溃恢复计数清零，但**必须门控稳定运行窗口**：
        // OOM 型必崩页面每次都是"加载完成 → 运行几秒 → 再崩"，若无条件清零，则 didFinish
        // 会把刚累加的计数抹掉，renderRecoveryCount 永远停在 1，maxRenderRecovery 上限形同
        // 虚设、陷入无限 reload。仅当距上次崩溃超过 renderRecoveryResetWindow（真正稳定运行过）
        // 才清零；否则保留计数，让连续崩溃能累加到上限并停止 reload。
        // 从未崩溃过的正常页面：lastRenderTerminateAt 初始 0，差值远超窗口，照常清零（本就是 0，no-op）。
        if Date().timeIntervalSince1970 - lastRenderTerminateAt > Self.renderRecoveryResetWindow {
            renderRecoveryCount = 0
        }
        cancelNetworkMonitor()
        onEvent?(.pageFinished(url: webView.url?.absoluteString ?? ""))
    }

    // MARK: - 错误

    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        let nsErr = error as NSError
        let url = webView.url?.absoluteString ?? ""

        // iOS 首次安装弹出"允许使用无线数据"权限弹窗时,
        // WKWebView 的请求会被系统阻断,触发网络错误。
        // 用户确认授权后系统不会自动重试,需要 SDK 监听网络恢复后自动 reload。
        if nsErr.domain == NSURLErrorDomain && Self.isNetworkPermissionError(nsErr.code) {
            if networkRetryCount < Self.maxNetworkRetry {
                networkRetryCount += 1
                QianLog.navigation.info("[NetworkRetry] Error detected: domain=\(nsErr.domain), code=\(nsErr.code), attempt=\(self.networkRetryCount)/\(Self.maxNetworkRetry)")
                onEvent?(.loadError(
                    url: url,
                    errorCode: nsErr.code,
                    description: "[SDK auto-retry \(self.networkRetryCount)/\(Self.maxNetworkRetry)] \(nsErr.localizedDescription)"
                ))
                startNetworkMonitorForReload(webView: webView)
                return
            }
            // 重试次数耗尽,上报错误,但保留 CTCellularData 监听。
            // 这样即使用户等待很久才点"允许",权限恢复后仍能触发最后一次 reload。
            QianLog.navigation.info("[NetworkRetry] Max retries exhausted (\(Self.maxNetworkRetry)), keeping CTCellularData monitor for late permission grant")
            report(error: error, url: url)
            // 仅在 finalRetryTimer 未运行时才启动最终监听（防止周期性重试触发的 reload 失败后重复创建）
            if finalRetryTimer == nil {
                startFinalNetworkMonitor(webView: webView)
            }
            return
        }

        report(error: error, url: url)
    }

    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        report(error: error, url: webView.url?.absoluteString ?? "")
    }

    private func report(error: Error, url: String) {
        let nsErr = error as NSError
        // SSL 类错误特殊上报
        if nsErr.domain == NSURLErrorDomain &&
            (nsErr.code == NSURLErrorServerCertificateUntrusted ||
             nsErr.code == NSURLErrorServerCertificateHasBadDate ||
             nsErr.code == NSURLErrorServerCertificateHasUnknownRoot ||
             nsErr.code == NSURLErrorServerCertificateNotYetValid ||
             nsErr.code == NSURLErrorClientCertificateRejected ||
             nsErr.code == NSURLErrorClientCertificateRequired)
        {
            onEvent?(.sslError(url: url, primaryError: nsErr.code))
            return
        }
        onEvent?(.loadError(
            url: url,
            errorCode: nsErr.code,
            description: nsErr.localizedDescription
        ))
    }

    /// 判断是否为网络权限相关的错误码。
    /// iOS 首次安装 App 时弹出"允许使用无线数据"弹窗,
    /// 在用户确认前 WKWebView 请求会失败,常见错误码包括:
    /// - NSURLErrorNotConnectedToInternet (-1009)
    /// - NSURLErrorNetworkConnectionLost (-1005)
    /// - NSURLErrorDataNotAllowed (-1020):蜂窝数据未授权
    private static func isNetworkPermissionError(_ code: Int) -> Bool {
        return code == NSURLErrorNotConnectedToInternet ||
               code == NSURLErrorNetworkConnectionLost ||
               code == -1020 // NSURLErrorDataNotAllowed
    }

    // MARK: - Network Permission Monitor（监听权限恢复后自动 reload）

    /// 重试次数耗尽后的最终监听周期性重试间隔和次数
    private static let finalRetryInterval: TimeInterval = 5.0
    private static let finalRetryMaxCount = 6

    private var finalRetryTimer: DispatchSourceTimer?
    private var finalRetryRemaining = 0

    /// 启动网络权限监听（初始重试阶段）。
    /// 三重检测：CTCellularData + didBecomeActive + 3 秒兜底超时。
    /// 任一先触发即 reload 并清理所有监听。
    private func startNetworkMonitorForReload(webView: WKWebView) {
        startNetworkMonitor(webView: webView, phase: "retry")

        // 兜底：3 秒后如果 CTCellularData 和 didBecomeActive 均未触发,也尝试 reload 一次。
        // 覆盖场景：纯 Wi-Fi 设备、用户快速点击允许但回调未触发、iOS 高版本行为变化
        let fallbackItem = DispatchWorkItem { [weak self, weak webView] in
            self?.tryReloadOnce(webView: webView, trigger: "fallback timeout (3s)")
        }
        self.networkRetryFallbackWorkItem = fallbackItem
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0, execute: fallbackItem)
    }

    /// 启动网络权限监听（重试耗尽后的最终阶段）。
    /// 三重检测：CTCellularData + didBecomeActive + 周期性重试（每 5 秒，最多 6 次 = 30 秒）。
    /// 覆盖用户等待很久才点"允许"的场景。
    private func startFinalNetworkMonitor(webView: WKWebView) {
        startNetworkMonitor(webView: webView, phase: "final")

        // 周期性重试兜底：覆盖纯 Wi-Fi 设备 CTCellularData 不回调的场景
        finalRetryRemaining = Self.finalRetryMaxCount
        let timer = DispatchSource.makeTimerSource(queue: .main)
        timer.schedule(deadline: .now() + Self.finalRetryInterval, repeating: Self.finalRetryInterval)
        timer.setEventHandler { [weak self, weak webView] in
            guard let self = self, !self.hasNetworkRetryReloaded else {
                self?.cancelNetworkMonitor()
                return
            }
            guard let webView = webView else {
                QianLog.navigation.info("[NetworkRetry] WebView already released, cancelling final periodic retry")
                self.cancelNetworkMonitor()
                return
            }
            self.finalRetryRemaining -= 1
            QianLog.navigation.info("[NetworkRetry] Final periodic retry, remaining=\(self.finalRetryRemaining)")
            self.reloadWebView(webView)

            // 如果周期性重试也耗尽,彻底放弃
            if self.finalRetryRemaining <= 0 {
                QianLog.navigation.info("[NetworkRetry] Final periodic retries exhausted, giving up completely")
                self.cancelNetworkMonitor()
            }
        }
        self.finalRetryTimer = timer
        timer.resume()
    }

    // MARK: - Network Monitor 公共逻辑

    /// 启动 CTCellularData + didBecomeActive 双重监听。
    /// - Parameter phase: 日志标识（"retry" = 初始重试阶段，"final" = 耗尽后阶段）
    ///
    /// CTCellularData：用户点击"允许"后状态从 restricted → notRestricted 触发 reload。
    /// didBecomeActive：系统弹窗消失时 App 从 inactive → active 触发 reload（更可靠的补充）。
    /// 两者共享 `hasNetworkRetryReloaded` 标志位，first-wins 竞争模式。
    private func startNetworkMonitor(webView: WKWebView, phase: String) {
        // 先清理可能存在的旧监听（防御性编程）
        cancelNetworkMonitor()
        hasNetworkRetryReloaded = false
        isFirstCellularCallback = true

        let cellular = CTCellularData()
        self.cellularData = cellular

        QianLog.navigation.info("[NetworkRetry] [\(phase)] Monitor started: CTCellularData + didBecomeActive")

        // CTCellularData 监听
        cellular.cellularDataRestrictionDidUpdateNotifier = { [weak self, weak webView] state in
            guard let self = self else { return }

            let stateDesc: String
            switch state {
            case .restrictedStateUnknown: stateDesc = "unknown"
            case .restricted: stateDesc = "restricted"
            case .notRestricted: stateDesc = "notRestricted"
            @unknown default: stateDesc = "unrecognized(\(state.rawValue))"
            }
            QianLog.navigation.info("[NetworkRetry] [\(phase)] CTCellularData callback: state=\(stateDesc), isFirst=\(self.isFirstCellularCallback), hasReloaded=\(self.hasNetworkRetryReloaded)")

            // CTCellularData 初始化后会立即触发一次当前状态回调，需要跳过
            if self.isFirstCellularCallback {
                self.isFirstCellularCallback = false
                QianLog.navigation.info("[NetworkRetry] [\(phase)] Skipping initial CTCellularData callback")
                return
            }

            // 用户点击"允许"后,状态变为 notRestricted
            // CTCellularData 回调在后台线程,统一切到主线程操作避免数据竞争
            if state == .notRestricted {
                DispatchQueue.main.async { [weak self, weak webView] in
                    self?.tryReloadOnce(webView: webView, trigger: "[\(phase)] CTCellularData → notRestricted")
                }
            }
        }

        // 监听 App 从 inactive → active（系统弹窗消失时触发）。
        // 网络权限弹窗会导致 App 进入 inactive 状态，
        // 用户点击"允许"或"不允许"后 App 恢复 active，此时立即尝试 reload。
        // 作为 CTCellularData 的补充：CTCellularData 在某些设备/系统版本上可能不回调，
        // 但 didBecomeActive 是系统级通知，可靠性更高。
        becomeActiveObserver = NotificationCenter.default.addObserver(
            forName: UIApplication.didBecomeActiveNotification,
            object: nil,
            queue: .main
        ) { [weak self, weak webView] _ in
            self?.tryReloadOnce(webView: webView, trigger: "[\(phase)] App became active")
        }
    }

    /// 尝试执行一次 reload（first-wins 竞争模式）。
    /// 多个检测源（CTCellularData、didBecomeActive、兜底超时）共享此方法，
    /// 通过 `hasNetworkRetryReloaded` 标志位保证只有第一个触发的源执行 reload。
    private func tryReloadOnce(webView: WKWebView?, trigger: String) {
        guard !hasNetworkRetryReloaded else { return }
        hasNetworkRetryReloaded = true
        QianLog.navigation.info("[NetworkRetry] Triggered by: \(trigger)")
        guard let webView = webView else {
            QianLog.navigation.info("[NetworkRetry] WebView already released, skip reload")
            cancelNetworkMonitor()
            return
        }
        reloadWebView(webView)
        cancelNetworkMonitor()
    }

    /// 重新加载 WebView。
    /// 优先使用保存的 lastProvisionalURL 发起新请求，
    /// 因为 provisional navigation 失败后 webView.url 为 nil，webView.reload() 无效。
    /// 如果 lastProvisionalURL 也为 nil（理论上不会），则 fallback 到 reload()。
    private func reloadWebView(_ webView: WKWebView) {
        if let url = lastProvisionalURL {
            QianLog.navigation.info("[NetworkRetry] Re-loading URL: \(url.absoluteString)")
            webView.load(URLRequest(url: url))
        } else if let url = webView.url {
            QianLog.navigation.info("[NetworkRetry] Fallback reload with webView.url: \(url.absoluteString)")
            webView.reload()
        } else {
            QianLog.navigation.info("[NetworkRetry] No URL available for reload, skipping")
        }
    }

    /// 取消网络权限监听和兜底定时器,释放资源。
    /// 在以下场景调用:
    /// 1. 权限授予成功 reload 后
    /// 2. 兜底超时触发后
    /// 3. 页面加载成功后(didFinish)
    /// 4. deinit(用户关闭页面)
    private func cancelNetworkMonitor() {
        cellularData?.cellularDataRestrictionDidUpdateNotifier = nil
        cellularData = nil
        networkRetryFallbackWorkItem?.cancel()
        networkRetryFallbackWorkItem = nil
        finalRetryTimer?.cancel()
        finalRetryTimer = nil
        if let observer = becomeActiveObserver {
            NotificationCenter.default.removeObserver(observer)
            becomeActiveObserver = nil
        }
    }

    // MARK: - SSL challenge

    func webView(
        _ webView: WKWebView,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {
        // 默认信任处理:走系统默认链路。**永不**自定义信任非法证书。
        completionHandler(.performDefaultHandling, nil)
    }

    // MARK: - Web Content 进程终止恢复

    /// WKWebView 的 Web Content 进程被系统杀死时回调(iOS 低内存时触发)。
    ///
    /// **典型触发场景**:
    /// - 老机型(如 iPhone 8 Plus)运行内存吃紧
    /// - H5 页面调起 `<input type="file">` 拍照,系统相机 + UIImagePickerController
    ///   占用大量内存,WKWebView 渲染进程被优先回收
    /// - 后台被系统因内存压力杀死渲染进程
    ///
    /// **重要陷阱**:进程被杀后 `webView.url` 通常会变为 nil,
    /// 此时 `webView.reload()` 不会发起任何请求,导致永远白屏。
    /// 因此必须使用保存的 `lastProvisionalURL`,通过 `load(URLRequest)` 重新发起请求。
    /// 复用 `reloadWebView(_:)` 保持与网络权限恢复路径一致的容错策略(与 OC 版对齐)。
    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
        let currentUrl = webView.url?.absoluteString
            ?? lastProvisionalURL?.absoluteString
            ?? ""
        onEvent?(.webContentProcessTerminated(url: currentUrl))
        // 冷却重置：距上次崩溃超过 renderRecoveryResetWindow 视为页面已稳定运行过，
        // 本次按全新问题处理，重置连续计数，避免偶发崩溃被误判为"不可恢复"。
        let now = Date().timeIntervalSince1970
        if now - self.lastRenderTerminateAt > Self.renderRecoveryResetWindow {
            self.renderRecoveryCount = 0
        }
        self.lastRenderTerminateAt = now
        // 连续崩溃达上限 → 判定不可恢复，停止 reload，避免死循环 reload 风暴。
        // 客户可据已上报的 webContentProcessTerminated 事件自行决定关闭页面。
        self.renderRecoveryCount += 1
        if self.renderRecoveryCount > Self.maxRenderRecovery {
            QianLog.navigation.info(
                "[ProcessTerminated] crashed \(self.renderRecoveryCount) times, giving up recovery")
            return
        }
        QianLog.navigation.info(
            "[ProcessTerminated] Web Content process killed, will recover after \(Self.renderRecoveryDelay)s for url: \(currentUrl)")
        // 延迟恢复,避免崩溃后立即 reload 因内存仍紧张再次被回收(循环崩溃)。
        // 注意:进程死后 webView.url 多数情况下为 nil,不能直接调用 reload,
        // 必须优先用 lastProvisionalURL 通过 load(URLRequest) 发起新请求。
        // [weak self, weak webView] 捕获:延迟窗口内用户关闭页面时已释放,回调安全跳过(防野指针)。
        DispatchQueue.main.asyncAfter(deadline: .now() + Self.renderRecoveryDelay) { [weak self, weak webView] in
            guard let self, let webView else { return }
            self.reloadWebView(webView)
        }
    }

    // MARK: - 白屏自检

    /// 绑定受控 WKWebView(weak)，用于 lifetime 白屏自检。
    func bindWebView(_ webView: WKWebView) {
        boundWebView = webView
    }

    /// 主动检查并尝试从白屏中恢复。
    func attemptBlankRecoveryIfNeeded() {
        guard let webView = boundWebView, !webView.isLoading else { return }
        let isBlankUrl = (webView.url?.absoluteString.isEmpty ?? true)
            || webView.url?.absoluteString == "about:blank"
        guard isBlankUrl else { return }
        guard let url = lastProvisionalURL else { return }
        QianLog.navigation.info("[BlankRecovery] webView appears blank, reloading URL: \(url.absoluteString)")
        webView.load(URLRequest(url: url))
    }

    private func installLifetimeBecomeActiveObserver() {
        lifetimeBecomeActiveObserver = NotificationCenter.default.addObserver(
            forName: UIApplication.didBecomeActiveNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.attemptBlankRecoveryIfNeeded()
        }
    }
}
