import Foundation
#if canImport(AVFoundation)
import AVFoundation
#elseif os(Android)
import SkipAV   // Skip's AVAudioPlayer, over Android's MediaPlayer
import SkipFuse // for the log
#endif
import PokerCore
import PokerSession

#if canImport(AVFoundation) || os(Android)
/// The table sounds.
///
/// Four short files, synthesised by `Scripts/make-sounds.py` so nothing has to be licensed and the
/// repo stays self-contained. They are deliberately quiet: a sound you hear a hundred times an
/// hour has to sit under the game rather than on top of it.
@MainActor
final class Sound {
    static let shared = Sound()

    enum Cue: String, CaseIterable {
        case chips, fold, knock, pot
    }

    /// Off means silent, and it stays that way between launches.
    var enabled: Bool {
        didSet { UserDefaults.standard.set(enabled, forKey: Self.defaultsKey) }
    }

    private static var defaultsKey: String { AppVariant.current.key("soundEnabled") }
    /// Two players per cue, alternated, so a quick call-then-raise does not cut itself off.
    private var players: [Cue: [AVAudioPlayer]] = [:]
    private var next: [Cue: Int] = [:]

    private init() {
        if UserDefaults.standard.object(forKey: Self.defaultsKey) == nil {
            enabled = true
        } else {
            enabled = UserDefaults.standard.bool(forKey: Self.defaultsKey)
        }
        configureSession()
        for cue in Cue.allCases {
            guard let url = Bundle.module.url(forResource: cue.rawValue, withExtension: "wav") else {
                #if os(Android)
                Logger(subsystem: AppVariant.current.bundleID, category: "sound").warning("no sound file for \(cue.rawValue)")
                #endif
                continue
            }
            #if os(Android)
            Logger(subsystem: AppVariant.current.bundleID, category: "sound").info("sound \(cue.rawValue) at \(url.path)")
            #endif
            players[cue] = (0..<2).compactMap { _ in
                guard let player = try? AVAudioPlayer(contentsOf: url) else { return nil }
                player.volume = cue == .fold ? 0.5 : 0.65
                player.prepareToPlay()
                return player
            }
            next[cue] = 0
        }
    }

    /// On a phone this makes the game respect the silent switch and duck out of the way of anything
    /// already playing, which is the polite behaviour for a game with incidental sound.
    private func configureSession() {
        #if os(iOS)
        try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
        try? AVAudioSession.sharedInstance().setActive(true)
        #endif
    }

    func play(_ cue: Cue) {
        guard enabled, let bank = players[cue], !bank.isEmpty else { return }
        let index = (next[cue] ?? 0) % bank.count
        next[cue] = index + 1
        let player = bank[index]
        player.currentTime = 0
        player.play()
    }

    /// What one decision sounds like.
    func play(for applied: TableSession.AppliedAction) {
        switch applied.action {
        case .fold: play(.fold)
        case .check: play(.knock)
        case .call, .bet, .raise: play(.chips)
        }
    }
}
#else
/// No sound here: the table plays silently, and the setting still exists so the screens and the
/// suite do not have to know.
@MainActor
final class Sound {
    static let shared = Sound()

    enum Cue: String, CaseIterable {
        case chips, fold, knock, pot
    }

    var enabled: Bool {
        didSet { UserDefaults.standard.set(enabled, forKey: Self.defaultsKey) }
    }

    private static var defaultsKey: String { AppVariant.current.key("soundEnabled") }

    private init() {
        if UserDefaults.standard.object(forKey: Self.defaultsKey) == nil {
            enabled = true
        } else {
            enabled = UserDefaults.standard.bool(forKey: Self.defaultsKey)
        }
    }

    func play(_ cue: Cue) {}
    func play(for applied: TableSession.AppliedAction) {}
}
#endif
