//
//  AppDataWriter.swift
//  ylgamehall
//
//  H5 app_*.js 预注入文件机制 — 在 loadFileURL 之前写 4 个 .js 文件到沙盒，
//  H5 业务通过 <script src="app_*.js"> 同步引入即可读到实际渠道/启动/设备值。
//  与原 msext NewRootVC.initJSdata / changebattery / changenetstate 1:1 等价。
//
//  详见 docs/H5-Native-Implementation-Design.md §7.5。
//

import Foundation
import os.log

@MainActor
public struct AppDataWriter {

    public enum ContainerRole: CustomStringConvertible, Sendable {
        case lobby
        case subGame(name: String, dir: String)

        public var description: String {
            switch self {
            case .lobby:               return "lobby"
            case .subGame(let n, _):   return "subGame(\(n))"
            }
        }
    }

    let bundleConfig: BundleConfig
    let resolvedVersion: ResolvedVersion?
    let containerRole: ContainerRole

    private static let log = Logger(subsystem: "ylgamehall", category: "AppDataWriter")

    public init(
        bundleConfig: BundleConfig,
        resolvedVersion: ResolvedVersion?,
        containerRole: ContainerRole
    ) {
        self.bundleConfig = bundleConfig
        self.resolvedVersion = resolvedVersion
        self.containerRole = containerRole
    }

    // MARK: - Public API

    /// 一次性写 app_data.js（含 #1-#12 共 12 个 var）+ app_gamesname.js（#13）。
    /// loadFileURL 前调一次。
    public func writeInitial() throws {
        try writeAppData()
        try writeGamesName()
    }

    /// 写 #14 app_battery.js。loadFileURL 前补一次 + battery 变化重写。
    public func writeBattery(_ level: Float) throws {
        // 模拟器 batteryLevel 返回 -1（无电池硬件），用 0.0 占位避免负数
        let normalized = max(level, 0)
        let value = String(format: "%.2f", normalized)
        let line = "var app_getbattery=\(value);\n"
        let url = filePath("app_battery.js")
        try line.write(to: url, atomically: true, encoding: .utf8)
        Self.log.debug("[\(containerRole.description, privacy: .public)] write app_battery.js: app_getbattery=\(value, privacy: .public) → \(url.path, privacy: .public)")
    }

    /// 写 #15 app_network.js。loadFileURL 前补一次 + NWPathMonitor 变化重写。
    public func writeNetwork(_ code: Int) throws {
        let line = "var app_getnetwork=\(code);\n"
        let url = filePath("app_network.js")
        try line.write(to: url, atomically: true, encoding: .utf8)
        Self.log.debug("[\(containerRole.description, privacy: .public)] write app_network.js: app_getnetwork=\(code, privacy: .public) → \(url.path, privacy: .public)")
    }

    // MARK: - Private writers

    private func writeAppData() throws {
        let bc = bundleConfig
        let launchtype: Int
        let gameName:  String
        let gameDir:   String
        let gameStart: String

        switch containerRole {
        case .lobby:
            launchtype = 0
            gameName   = bc.gameStart
            gameDir    = bc.gameDir
            gameStart  = bc.gameStart
        case .subGame(let name, let dir):
            launchtype = 1
            gameName   = name
            gameDir    = dir
            gameStart  = name
        }

        // ⚠️ app_appversion 不是 "App 的版本号"，而是审核切换标志（参 msext NewRootVC.m:1190-1208）：
        //   远端 ResolvedVersion.appVersion >= 本地 BundleConfig.appVersion
        //     → result = 0，app_gameconfig 取 BundleConfig.gameConfig（正常业务）
        //   否则（远端 < 本地，罕见，可能审核期 / IPA 已升远端未更新）
        //     → result = 1，app_gameconfig 取 BundleConfig.appleConfig（审核期接口）
        // app_appversion 写入字面 '0' / '1'（单引号包裹数字，与 msext "var app_appversion='%d';" 等价）
        let localAppVer  = Int(bc.appVersion) ?? 0
        let remoteAppVer = resolvedVersion?.appVersion ?? localAppVer
        let result:        Int    = remoteAppVer >= localAppVer ? 0 : 1
        let gameConfigStr: String = result == 0 ? bc.gameConfig : bc.appleConfig

        // ⚠️ 严格保持字面（参 docs/H5-Native-Contract.md §4.2 + msext NewRootVC.m:1204）：
        //   - 字符串字段单引号包裹（msext "var x='%@';"）
        //   - 数值字段无引号（app_version=1 / app_Launchtype=0 / app_getwifisignalLevel=1）
        //   - app_appversion 单引号包裹（msext "var app_appversion='%d';"，H5 拿到的是字符串 '0' / '1'）
        //   - 大小写不能改：app_Launchtype L 大写、app_getwifisignalLevel wifi 小写 + signal/Level 区分
        //
        // 声明顺序：5 个关键字段（gameconfig / market / agent / channel / Launchtype）
        // 提到最前，便于 H5 console 调试时一眼看到核心值；JS var 提升机制下顺序对最终读取
        // 值无影响，与 msext 行为等价。
        let lines =
            "var app_gameconfig='\(escape(gameConfigStr))';" +
            "var app_market='\(escape(bc.market))';" +
            "var app_agent='\(escape(bc.agent))';" +
            "var app_channel='\(escape(bc.channel))';" +
            "var app_Launchtype=\(launchtype);" +
            "var app_version=1;" +
            "var app_gamedir='\(escape(gameDir))';" +
            "var app_gamestart='\(escape(gameStart))';" +
            "var app_appversion='\(result)';" +
            "var app_getwifisignalLevel=1;" +
            "var app_gamename='\(escape(gameName))';" +
            "var app_invitationcode='\(escape(bc.other))';"

        let url = filePath("app_data.js")
        try lines.write(to: url, atomically: true, encoding: .utf8)

        Self.log.debug("""
        [\(containerRole.description, privacy: .public)] write app_data.js → \(url.path, privacy: .public)
          app_gameconfig         = '\(gameConfigStr, privacy: .public)' (result=\(result, privacy: .public))
          app_market             = '\(bc.market, privacy: .public)'
          app_agent              = '\(bc.agent, privacy: .public)'
          app_channel            = '\(bc.channel, privacy: .public)'
          app_Launchtype         = \(launchtype, privacy: .public)
          app_version            = 1
          app_gamedir            = '\(gameDir, privacy: .public)'
          app_gamestart          = '\(gameStart, privacy: .public)'
          app_appversion         = '\(result, privacy: .public)' [审核切换标志，非版本号；local=\(localAppVer, privacy: .public) remote=\(remoteAppVer, privacy: .public)]
          app_getwifisignalLevel = 1
          app_gamename           = '\(gameName, privacy: .public)'
          app_invitationcode     = '\(bc.other, privacy: .public)'
        """)
    }

    private func writeGamesName() throws {
        // Phase 1: 暂时只写当前 gameStart 一项（与 msext AppDelegate.m:259 首次启动行为一致）。
        // Phase 6 子游戏完整时改为扫描 SandboxPaths.installedSubGames + 增量追加。
        let names = [bundleConfig.gameStart]
        let escaped = names.map { "'\(escape($0))'" }.joined(separator: ",")
        // ⚠️ msext 字面：var 后两个空格 + new Array(...)，沿用
        let line = "var  app_gamesname=new Array(\(escaped));\n"
        let url = filePath("app_gamesname.js")
        try line.write(to: url, atomically: true, encoding: .utf8)
        Self.log.debug("[\(containerRole.description, privacy: .public)] write app_gamesname.js: \(names.count, privacy: .public) games → \(url.path, privacy: .public)\n  games = \(names, privacy: .public)")
    }

    private func filePath(_ name: String) -> URL {
        // 与 msext NewRootVC 一致：{gamedir}/{gamestart}/app_*.js
        let base: URL
        switch containerRole {
        case .lobby:
            // {Caches}/{gameDir}/{gameStart}/ = SandboxPaths.lobbyIndex 的父目录
            base = SandboxPaths.lobbyIndex.deletingLastPathComponent()
        case .subGame(let name, let dir):
            // {Caches}/{dir}/{name}/ = SandboxPaths.subGameIndex(dir,name) 的父目录
            base = SandboxPaths.subGameIndex(dir, name).deletingLastPathComponent()
        }
        return base.appendingPathComponent(name)
    }

    /// JS string 转义：反斜杠 / 单引号 / 换行（msext 用单引号包裹字符串，
    /// 转义对象是单引号）。
    private func escape(_ s: String) -> String {
        s.replacingOccurrences(of: "\\", with: "\\\\")
         .replacingOccurrences(of: "'", with: "\\'")
         .replacingOccurrences(of: "\n", with: "\\n")
         .replacingOccurrences(of: "\r", with: "\\r")
    }
}
