//
//  BridgeProtocol.swift
//  ylgamehall
//
//  H5 ↔ Native 桥的核心抽象。详见 docs/H5-Native-Implementation-Design.md §3.2。
//

import Foundation

// MARK: - 桥接协议

/// 容器层（WebView VC）和 handler 注册层都依赖此抽象，便于单测注入 mock。
///
/// - 实现见 `BridgeBus`（@MainActor，挂 WKWebView 上）
/// - H5 端协议见 `Resources/JS/WebViewJavascriptBridge.js`（Phase 1.8 引入）
public protocol BridgeProtocol: AnyObject, Sendable {

    /// 注册 H5 → Native handler。重复 name 后注册的会覆盖前一个。
    func register(_ name: String, handler: @escaping BridgeHandler)

    /// Native → H5 主动调用 H5 端 handler。`callback` 可选。
    func call(_ name: String, data: BridgeData?, callback: BridgeCallback?)
}

/// H5 → Native handler 签名。
///
/// - 第 1 参：H5 端 `bridge.callHandler('name', data, ...)` 传过来的 data
/// - 第 2 参：响应回调，handler 调一次 `callback?(...)` 把结果回给 H5；可不调
/// - async：允许 handler 内部 await（如等待 SDK / 网络）
public typealias BridgeHandler = @Sendable (BridgeData?, BridgeCallback?) async -> Void

/// 桥回调签名（H5 → Native 的 responseCallback，或 Native → H5 的回包闭包）。
public typealias BridgeCallback = @Sendable (BridgeData?) -> Void

// MARK: - BridgeData

/// 桥消息载荷：与 JSON 值同构的代数数据类型。
///
/// 所有 H5 ↔ Native 之间流转的数据最终都序列化为此类型，方便：
/// - 类型化访问（`data["title"]?.asString` 比 `data["title"] as? String` 干净）
/// - Sendable 跨 actor 边界（避免 `[String: Any]` 这种 non-Sendable 类型扩散）
/// - 单测 fixture 构造（字面量语法 `["a": 1, "b": [true, nil]]` 等价 `.object([...])`）
public enum BridgeData: Sendable {
    case string(String)
    case number(Double)
    /// 定点小数。序列化成 JSON **数字**，但保留给定的小数位数文本。
    ///
    /// 为什么不用 `.number(Double)`：`JSONSerialization` 写 Double 会输出 17 位有效
    /// 数字，`28.636486` 变成 `28.636486000000001`。虽然 JS `JSON.parse` 出来是同一个
    /// IEEE754 double（H5 侧不可分辨），但 JSON **文本**与原工程不一致。
    /// `getlocationinfo` 的经纬度要求与 msext 老 UIWebView 路径
    /// （`RootVC.m:1998` 拼 `\"latitude\":%f`）的字面文本对齐，故走这条。
    case decimal(Decimal)
    case bool(Bool)
    case null
    case array([BridgeData])
    case object([String: BridgeData])
}

// MARK: - JSON 互转

extension BridgeData {

    /// 从 `JSONSerialization` 输出（Any 树）构造，递归处理子节点。
    /// 遇到未知类型返回 nil（不静默丢弃，便于上层检测异常 payload）。
    public init?(jsonObject: Any) {
        if jsonObject is NSNull { self = .null; return }
        if let s = jsonObject as? String { self = .string(s); return }
        if let n = jsonObject as? NSNumber {
            // NSNumber 同时桥接 Bool/Int/Double。Bool 必须先用 CFTypeID 鉴别，
            // 否则 NSNumber(value: true) 会被当 1.0 处理。
            if CFGetTypeID(n) == CFBooleanGetTypeID() {
                self = .bool(n.boolValue)
                return
            }
            self = .number(n.doubleValue)
            return
        }
        if let arr = jsonObject as? [Any] {
            self = .array(arr.compactMap { BridgeData(jsonObject: $0) })
            return
        }
        if let obj = jsonObject as? [String: Any] {
            var out: [String: BridgeData] = [:]
            out.reserveCapacity(obj.count)
            for (k, v) in obj {
                if let d = BridgeData(jsonObject: v) { out[k] = d }
            }
            self = .object(out)
            return
        }
        return nil
    }

    /// 转为 `JSONSerialization` 可接受的 Any 树，供 evaluateJavaScript 拼 JSON 字符串。
    public var jsonObject: Any {
        switch self {
        case .string(let s):  return s
        case .number(let d):  return d
        case .decimal(let d): return d as NSDecimalNumber
        case .bool(let b):    return b
        case .null:           return NSNull()
        case .array(let arr): return arr.map { $0.jsonObject }
        case .object(let d):  return d.mapValues { $0.jsonObject }
        }
    }
}

// MARK: - 访问语法糖
// 注：项目默认 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor 让所有类型默认 main 隔离；
// BridgeData 是纯值类型、需要在 handler 闭包（@Sendable）内自由访问，
// 所有访问语法糖标 nonisolated 避免跨 actor 限制。

extension BridgeData {

    nonisolated public var asString: String? {
        if case .string(let s) = self { return s }
        return nil
    }

    /// 宽松字符串：string 保持原样、number 自动转 String、bool 转 "true"/"false"。
    ///
    /// 用于 H5 字段类型不严格的场景（如 mediaTypeAudio.user 实际可能传 number
    /// 0/1/userId 或 string）。对齐 daoqi `[NSString stringWithFormat:@"%@",
    /// data[@"user"]]` 的容忍行为—— ObjC `%@` 对 NSNumber 调用 description 自动
    /// 转字符串。
    nonisolated public var asLooseString: String? {
        switch self {
        case .string(let s): return s
        case .number(let d):
            // 整数样的 double 转 Int 字符串（如 0.0 → "0"，避免无意义的 .0 尾巴）
            if d == d.rounded(), d.isFinite, abs(d) < Double(Int.max) {
                return String(Int(d))
            }
            return String(d)
        case .decimal(let d): return "\(d)"
        case .bool(let b):  return b ? "true" : "false"
        case .null:         return nil
        case .array, .object: return nil
        }
    }

    nonisolated public var asDouble: Double? {
        switch self {
        case .number(let d):  return d
        case .decimal(let d): return (d as NSDecimalNumber).doubleValue
        default:              return nil
        }
    }

    nonisolated public var asInt: Int? {
        if case .number(let d) = self { return Int(d) }
        return nil
    }

    nonisolated public var asBool: Bool? {
        if case .bool(let b) = self { return b }
        return nil
    }

    nonisolated public var asArray: [BridgeData]? {
        if case .array(let a) = self { return a }
        return nil
    }

    nonisolated public var asObject: [String: BridgeData]? {
        if case .object(let o) = self { return o }
        return nil
    }

    /// 对象字段访问：`data["title"]?.asString`
    nonisolated public subscript(key: String) -> BridgeData? {
        if case .object(let o) = self { return o[key] }
        return nil
    }

    /// 数组索引访问：`data[0]?.asString`
    nonisolated public subscript(index: Int) -> BridgeData? {
        if case .array(let a) = self, a.indices.contains(index) { return a[index] }
        return nil
    }
}

// MARK: - 字面量构造

extension BridgeData: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) { self = .string(value) }
}

extension BridgeData: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: Int) { self = .number(Double(value)) }
}

extension BridgeData: ExpressibleByFloatLiteral {
    public init(floatLiteral value: Double) { self = .number(value) }
}

extension BridgeData: ExpressibleByBooleanLiteral {
    public init(booleanLiteral value: Bool) { self = .bool(value) }
}

extension BridgeData: ExpressibleByArrayLiteral {
    public init(arrayLiteral elements: BridgeData...) { self = .array(elements) }
}

extension BridgeData: ExpressibleByDictionaryLiteral {
    public init(dictionaryLiteral elements: (String, BridgeData)...) {
        self = .object(Dictionary(uniqueKeysWithValues: elements))
    }
}
