//
//  VoicePlayer.swift
//  ylgamehall
//
//  远端语音播放器：AVAudioPlayer 包装 + 反向 callback `gameui_stop_voice` 触发点。
//
//  契约：docs/H5-Native-Contract.md §3.1 ［5］mediaTypeAudio + §3.2 ［7］［8］
//  gameui_play_voice / gameui_stop_voice
//
//  ## 与原项目的行为对齐
//
//  daoqi msext NewRootVC.m / gameController.m 把 voicePlayer / User_id 作
//  为 VC 成员变量；本类把同款状态封装为单例。关键行为：
//
//  - `lastUserId`：mediaTypeAudio 入参 user 字段（每次 mediaTypeAudio 进来
//    都更新，无论是否真的播放——对齐 daoqi gameController.m:438
//    `self.User_id=userid;` 在 canbofang=NO 时也会执行）
//  - `gameui_stop_voice` payload 用 `lastUserId`，不是 didFinishPlaying 传
//    入的 player 关联值（对齐 daoqi stopradio 用 self.User_id 的行为；多条
//    消息连发时 gameui_stop_voice 发最新 user，**这是 daoqi 的行为**）
//  - 覆盖式 play：前一条还在播时收到第二条，直接覆盖 player（让旧的被
//    ARC/ARC-like 释放）；对齐 daoqi `voicePlayer = [[AVAudioPlayer alloc]...]`
//    赋值行为
//  - 主动 stop **不**触发 onFinish（daoqi 没有主动 stop 路径，只有
//    audioPlayerDidFinishPlaying 触发 stopradio）
//

import AVFoundation
import os.log

public final class VoicePlayer: NSObject, AVAudioPlayerDelegate {

    public static let shared = VoicePlayer()

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

    private var player: AVAudioPlayer?

    /// 最近一次 mediaTypeAudio 入参中的 user 字段。
    /// 对齐 daoqi self.User_id 行为（成员变量，每次 mediaTypeAudio 进来都更新）。
    public private(set) var lastUserId: String = ""

    /// 播放完成回调（AVAudioPlayer didFinishPlaying 触发，成功/失败都触发）。
    /// mediaTypeAudio handler 注册此 closure 触发反向调用 gameui_stop_voice。
    /// payload 是 `lastUserId`（不是 player 关联的 user，对齐 daoqi 行为）。
    public var onFinish: ((String) -> Void)?

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

    /// 记录 mediaTypeAudio 入参的 user，**无论是否实际播放都要调**。
    /// 对齐 daoqi gameController.m:438（voicePlaying=0 时仍执行 `self.User_id=userid`）。
    public func recordIncomingUser(_ user: String) {
        lastUserId = user
    }

    /// 播放 wav。若已有正在播放的 player，直接覆盖（对齐 daoqi 行为）。
    /// 调用方应在调用前调 `recordIncomingUser(user)` 更新 lastUserId。
    /// - Throws: AVAudioPlayer 创建/启动失败
    public func play(_ wav: URL) throws {
        let p = try AVAudioPlayer(contentsOf: wav)
        p.delegate = self
        guard p.prepareToPlay(), p.play() else {
            throw PlayerError.startFailed
        }
        player = p
        Self.log.debug("play \(wav.lastPathComponent, privacy: .public) user=\(self.lastUserId, privacy: .public)")
    }

    /// 主动停止当前播放（不触发 onFinish）。
    /// 外部清理使用（如 H5 BackGameData 退出子游戏时）；
    /// 业务正常路径下不会主动调，由 didFinishPlaying 自然完成。
    public func stop() {
        player?.stop()
        player = nil
    }

    public enum PlayerError: Error, Sendable {
        case startFailed
    }

    // MARK: - AVAudioPlayerDelegate
    //
    // AVAudioPlayerDelegate 方法签名是 nonisolated（AVFoundation API 约束）。
    // 内部访问 @MainActor 隔离的状态需要跳转回主线程。

    nonisolated public func audioPlayerDidFinishPlaying(
        _ player: AVAudioPlayer,
        successfully flag: Bool
    ) {
        // AVAudioPlayer 是 non-Sendable，不能直接跨 actor 边界。用 ObjectIdentifier
        // （Sendable）传递身份，handleFinish 内重新查 self.player 比对。
        let id = ObjectIdentifier(player)
        Task { @MainActor in
            self.handleFinish(playerIdentity: id, success: flag)
        }
    }

    nonisolated public func audioPlayerDecodeErrorDidOccur(
        _ player: AVAudioPlayer,
        error: (any Error)?
    ) {
        let id = ObjectIdentifier(player)
        Task { @MainActor in
            self.handleFinish(playerIdentity: id, success: false)
        }
    }

    /// 处理 didFinishPlaying / decodeError。
    /// 防御覆盖式 play 时旧 player stop 触发延迟 delegate：仅当 didFinish
    /// 的 player 与当前 self.player 同身份时才清理 + 触发 onFinish。
    private func handleFinish(playerIdentity: ObjectIdentifier, success: Bool) {
        guard let current = player, ObjectIdentifier(current) == playerIdentity else { return }
        player = nil
        Self.log.debug("didFinish user=\(self.lastUserId, privacy: .public) success=\(success, privacy: .public)")
        onFinish?(lastUserId)
    }
}
