//
//  LNVoicePlayer.swift
//  Lanu
//
//  Created by OneeChan on 2025/12/5.
//

import Foundation
import AVFAudio
import AVFoundation


protocol LNVoicePlayerNotify {
    func onAudioStartPlay(path: String)
    func onAudioUpdateDuration(path: String, cur: TimeInterval, total: TimeInterval)
    func onAudioStopPlay(path: String)
}
extension LNVoicePlayerNotify {
    func onAudioStartPlay(path: String) { }
    func onAudioUpdateDuration(path: String, cur: TimeInterval, total: TimeInterval) { }
    func onAudioStopPlay(path: String) { }
}


enum LNVoiceSource: Codable {
    case unknown
    case im
    case game
    
    var folderName: String {
        switch self {
        case .unknown: "common"
        case .im: "im"
        case .game: "game"
        }
    }
}

class LNVoicePlayer: NSObject {
    static let shared = LNVoicePlayer()
    
    private var curPlayer: AVAudioPlayer?
    private(set) var playingUrl: String?
    var isPlaying: Bool {
        curPlayer?.isPlaying == true
    }
    
    private var durationTimer: Timer?
    private(set) var currentTime: TimeInterval = 0.0
    private(set) var duration: TimeInterval = 0.0
    var curSpeed: Float {
        curPlayer?.rate ?? 1.0
    }
    
    private override init() { super.init() }
    
    func play(path: String) {
        stop()
        
        playingUrl = path
        
        playVoice(path)
    }
    
    func play(_ url: String, type: LNVoiceSource = .unknown) {
        stop()
        
        playingUrl = url
        
        LNVoiceResourceManager.shared.voiceResourceFor(url: url, type: type) { [weak self] path in
            guard let self else { return }
            guard playingUrl == url, curPlayer == nil else { return }
            playVoice(path)
        }
    }
    
    func playVoiceMessage(message: LNIMMessageData) {
        stop()
        
        let uuid = message.imMessage.soundElem?.uuid
        
        playingUrl = uuid
        
        if let path = message.content,
           FileManager.default.fileExists(atPath: path) {
            playVoice(path)
            return
        }
        
        let cachePath: URL? = if let uuid {
            LNVoiceResourceManager.shared.voicePath(uuid, type: .im)
        } else {
            nil
        }
        if let cachePath {
            if FileManager.default.fileExists(atPath: cachePath.path) {
                playVoice(cachePath.path)
                return
            }
        }
        
        message.imMessage.soundElem?.getUrl { url in
            guard let url else { return }
            LNVoiceResourceManager.shared.voiceResourceFor(
                url: url, type: .im, cachePath: cachePath)
            { [weak self] path in
                guard let self else { return }
                guard playingUrl == uuid, curPlayer == nil else { return }
                playVoice(path)
            }
        }
    }
    
    func stop() {
        curPlayer?.stop()
        curPlayer = nil
        stopTimer()
        duration = 0.0
        currentTime = 0.0
        notifyStopPlay()
        playingUrl = nil
    }
    
    func setSpeed(speed: Float) {
        curPlayer?.rate = speed
    }
}

extension LNVoicePlayer {
    private func playVoice(_ path: String) {
        guard FileManager.default.fileExists(atPath: path) else { return }
        
        try? AVAudioSession.sharedInstance().setCategory(.playback)
        
        guard let player = try? AVAudioPlayer.init(contentsOf: URL(fileURLWithPath: path)) else { return }
        
        player.delegate = self
        player.enableRate = true
        
        if player.play() {
            duration = player.duration
            curPlayer = player
            notifyStartPlay()
            startTimer()
        } else {
            stop()
        }
    }
}

extension LNVoicePlayer {
    private func startTimer() {
        stopTimer()
        currentTime = 0.0
        
        let timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: { [weak self] _ in
            guard let self else { return }
            currentTime = curPlayer?.currentTime ?? 0
            notifyDuration(cur: currentTime, total: curPlayer?.duration ?? 0)
        })
        RunLoop.main.add(timer, forMode: .common)
        durationTimer = timer
    }
    
    private func stopTimer() {
        durationTimer?.invalidate()
        durationTimer = nil
    }
}

extension LNVoicePlayer: AVAudioPlayerDelegate {
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        stop()
        notifyStopPlay()
    }
}

extension LNVoicePlayer {
    private func notifyStartPlay() {
        guard let url = playingUrl, !url.isEmpty else { return }
        LNEventDeliver.notifyEvent {
            ($0 as? LNVoicePlayerNotify)?.onAudioStartPlay(path: url)
        }
    }
    
    private func notifyStopPlay() {
        guard let url = playingUrl, !url.isEmpty else { return }
        LNEventDeliver.notifyEvent {
            ($0 as? LNVoicePlayerNotify)?.onAudioStopPlay(path: url)
        }
    }
    
    private func notifyDuration(cur: TimeInterval, total: TimeInterval) {
        guard let url = playingUrl, !url.isEmpty else { return }
        LNEventDeliver.notifyEvent {
            ($0 as? LNVoicePlayerNotify)?.onAudioUpdateDuration(path: url, cur: cur, total: total)
        }
    }
}
