package com.ariacockpit

import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import android.util.Log
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.modules.core.DeviceEventManagerModule

/**
 * Steuert Audio-Focus fuer Ducking/Muten anderer Apps + emittiert Loss-Events
 * an JS damit ARIA bei VoIP-Anrufen (WhatsApp/Signal/Discord/...) aufhoert
 * zu sprechen — diese Anrufe gehen nicht ueber TelephonyManager, sondern
 * requestn AudioFocus_GAIN_TRANSIENT_EXCLUSIVE was wir hier mitbekommen.
 *
 * - requestDuck()      → andere Apps werden leiser (ARIA spricht TTS)
 * - requestExclusive() → andere Apps werden pausiert (Mikrofon-Aufnahme)
 * - release()          → Focus abgeben, andere Apps duerfen wieder
 *
 * Events:
 * - "AudioFocusChanged" mit type:
 *     "loss"           — endgueltiger Verlust (Anruf, andere App permanent)
 *     "loss_transient" — vorruebergehender Verlust (kurze Unterbrechung)
 *     "gain"           — Fokus zurueck
 */
class AudioFocusModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName() = "AudioFocus"

    companion object { private const val TAG = "AudioFocus" }

    private var currentRequest: AudioFocusRequest? = null

    private fun audioManager(): AudioManager? =
        reactApplicationContext.getSystemService(Context.AUDIO_SERVICE) as? AudioManager

    private fun emitFocusChange(type: String) {
        try {
            val params = Arguments.createMap().apply { putString("type", type) }
            reactApplicationContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                .emit("AudioFocusChanged", params)
        } catch (e: Exception) {
            Log.w(TAG, "emit failed: ${e.message}")
        }
    }

    private val focusListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
        when (focusChange) {
            AudioManager.AUDIOFOCUS_LOSS -> {
                Log.i(TAG, "AUDIOFOCUS_LOSS (z.B. Anruf, anderer Player permanent)")
                emitFocusChange("loss")
            }
            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                Log.i(TAG, "AUDIOFOCUS_LOSS_TRANSIENT (kurze Unterbrechung)")
                emitFocusChange("loss_transient")
            }
            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
                // Notification-Sound o.ae. — wir ignorieren das, ARIA macht weiter
                Log.d(TAG, "AUDIOFOCUS_LOSS_CAN_DUCK ignoriert")
            }
            AudioManager.AUDIOFOCUS_GAIN -> {
                Log.i(TAG, "AUDIOFOCUS_GAIN")
                emitFocusChange("gain")
            }
        }
    }

    private fun requestFocus(durationHint: Int, usage: Int, promise: Promise) {
        val am = audioManager()
        if (am == null) {
            promise.reject("NO_AUDIO_MANAGER", "AudioManager nicht verfuegbar")
            return
        }

        release()

        val result: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val attrs = AudioAttributes.Builder()
                .setUsage(usage)
                .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                .build()
            val req = AudioFocusRequest.Builder(durationHint)
                .setAudioAttributes(attrs)
                .setOnAudioFocusChangeListener(focusListener)
                .build()
            currentRequest = req
            am.requestAudioFocus(req)
        } else {
            @Suppress("DEPRECATION")
            am.requestAudioFocus(focusListener, AudioManager.STREAM_MUSIC, durationHint)
        }

        promise.resolve(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
    }

    /** Andere Apps werden pausiert (TTS spricht).
     *
     *  TRANSIENT (statt TRANSIENT_MAY_DUCK): Spotify/YouTube pausieren komplett
     *  statt nur leiser zu werden. Verhindert auch das "kommt-wieder-hoch"-
     *  Problem mit MAY_DUCK, wo das System nach kurzer Zeit den Duck-Effekt
     *  wieder aufgehoben hat obwohl wir den Fokus noch hielten.
     */
    @ReactMethod
    fun requestDuck(promise: Promise) {
        requestFocus(
            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
            AudioAttributes.USAGE_ASSISTANT,
            promise,
        )
    }

    /** Andere Apps werden pausiert (Mikrofon-Aufnahme / Gespraech). */
    @ReactMethod
    fun requestExclusive(promise: Promise) {
        requestFocus(
            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
            AudioAttributes.USAGE_VOICE_COMMUNICATION,
            promise,
        )
    }

    /** Focus abgeben — andere Apps duerfen wieder volle Lautstaerke. */
    @ReactMethod
    fun release(promise: Promise) {
        release()
        promise.resolve(true)
    }

    /** Sanfter Spotify-Resume-Nudge: kurz USAGE_MEDIA mit TRANSIENT
     *  requesten und sofort abandonen. Spotify bekommt das als
     *  Focus-Frei-Signal und resumed automatisch — aber weil TRANSIENT
     *  (nicht GAIN permanent), interpretiert Spotify das NICHT als
     *  "user stopped" was Auto-Resume verhindert haette.
     *
     *  Hintergrund: ARIA spricht TTS via USAGE_ASSISTANT GAIN_TRANSIENT,
     *  Spotify pausiert. ARIA released. Spotify SOLLTE nach
     *  TRANSIENT-Loss + Abandon automatisch resumen, tut es aber bei
     *  manchen Versionen / Geraeten nicht zuverlaessig. Dieser Nudge
     *  triggert den Focus-Stack-Refresh ohne den Spotify-Auto-Stop-Bug
     *  der alten kickReleaseMedia mit GAIN permanent.
     */
    @ReactMethod
    fun nudgeMediaResume(promise: Promise) {
        val am = audioManager()
        if (am == null) {
            promise.resolve(false)
            return
        }
        Thread {
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    val attrs = AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build()
                    val nudgeListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ }
                    val nudgeReq = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                        .setAudioAttributes(attrs)
                        .setOnAudioFocusChangeListener(nudgeListener)
                        .build()
                    am.requestAudioFocus(nudgeReq)
                    Thread.sleep(100)
                    am.abandonAudioFocusRequest(nudgeReq)
                } else {
                    val nudgeListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ }
                    @Suppress("DEPRECATION")
                    am.requestAudioFocus(nudgeListener, AudioManager.STREAM_MUSIC,
                                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                    Thread.sleep(100)
                    @Suppress("DEPRECATION")
                    am.abandonAudioFocus(nudgeListener)
                }
                Log.i(TAG, "nudgeMediaResume: USAGE_MEDIA TRANSIENT request+abandon (Spotify-Resume-Trigger)")
            } catch (e: Exception) {
                Log.w(TAG, "nudgeMediaResume failed: ${e.message}")
            }
        }.start()
        promise.resolve(true)
    }

    /** Den USAGE_MEDIA-Focus-Stack im System aufmischen, damit Spotify/YouTube
     *  resumen wenn ein anderer Player (z.B. react-native-sound) seinen Focus
     *  nicht ordnungsgemaess released hat. Strategie: kurz selbst USAGE_MEDIA
     *  GAIN beanspruchen — das System invalidiert dabei den haengenden Stack-
     *  Eintrag des anderen Players — und sofort wieder abandonen. Spotify
     *  bekommt den Focus-Gain und resumed.
     *
     *  Workaround fuer das react-native-sound-Bug: Sound.stop()/release()
     *  laesst den AudioFocusRequest haengen.
     *
     *  ⚠️ ACHTUNG: nutzt AUDIOFOCUS_GAIN (permanent), Spotify kann das als
     *  "user-action stopp" interpretieren und Auto-Resume verhindern.
     *  Fuer Spotify-Resume nach TTS lieber nudgeMediaResume() nehmen (sanfter).
     */
    @ReactMethod
    fun kickReleaseMedia(promise: Promise) {
        val am = audioManager()
        if (am == null) {
            promise.resolve(false)
            return
        }
        // Async laufen lassen — wir wollen einen request, Pause, dann abandon.
        // Ohne Pause merkt das System (und damit Spotify) die kurze Owner-
        // Wechsel oft gar nicht. 250ms reicht erfahrungsgemaess fuer den
        // Focus-Stack-Refresh.
        Thread {
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    val attrs = AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build()
                    val kickListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ }
                    val kickReq = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                        .setAudioAttributes(attrs)
                        .setOnAudioFocusChangeListener(kickListener)
                        .build()
                    am.requestAudioFocus(kickReq)
                    Thread.sleep(250)
                    am.abandonAudioFocusRequest(kickReq)
                } else {
                    val kickListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ }
                    @Suppress("DEPRECATION")
                    am.requestAudioFocus(kickListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
                    Thread.sleep(250)
                    @Suppress("DEPRECATION")
                    am.abandonAudioFocus(kickListener)
                }
                Log.i(TAG, "kickReleaseMedia: USAGE_MEDIA-Stack aufgemischt (250ms Pause)")
            } catch (e: Exception) {
                Log.w(TAG, "kickReleaseMedia failed: ${e.message}")
            }
        }.start()
        promise.resolve(true)
    }

    private fun release() {
        val am = audioManager() ?: return
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            currentRequest?.let { am.abandonAudioFocusRequest(it) }
        } else {
            @Suppress("DEPRECATION")
            am.abandonAudioFocus(focusListener)
        }
        currentRequest = null
    }

    /** Aktueller Audio-Mode: NORMAL=0, IN_CALL=2, IN_COMMUNICATION=3, CALL_SCREENING=4.
     *  IN_COMMUNICATION ist der typische VoIP-Anruf-Mode (WhatsApp, Signal, etc.) —
     *  kann gepollt werden um zu erkennen wann der Anruf vorbei ist (zurueck NORMAL). */
    @ReactMethod
    fun getMode(promise: Promise) {
        val am = audioManager()
        if (am == null) {
            promise.resolve(0)
            return
        }
        promise.resolve(am.mode)
    }

    @ReactMethod fun addListener(eventName: String) {}
    @ReactMethod fun removeListeners(count: Int) {}
}
