/*
 * Bitstream audio passthrough adapted from Moonfin's TV backends.
 *
 * Moonfin: https://github.com/Moonfin-Client/Moonfin-Core
 *
 * Modifications Copyright (C) 2026 Memby contributors
 * SPDX-License-Identifier: GPL-2.0-only
 */

package com.ponzischeme89.memby.ui.player

import android.content.Context
import androidx.media3.common.C
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.audio.AudioCapabilities
import androidx.media3.exoplayer.audio.AudioSink
import androidx.media3.exoplayer.audio.DefaultAudioSink
import com.ponzischeme89.memby.data.playback.AudioPassthroughMode
import com.ponzischeme89.memby.data.playback.AudioPassthroughPreference
import com.ponzischeme89.memby.data.playback.DeviceAudioCapabilities
import com.ponzischeme89.memby.data.playback.SurroundCodec
import com.ponzischeme89.memby.data.playback.deviceAudioCapabilities
import com.ponzischeme89.memby.data.playback.encoding
import com.ponzischeme89.memby.data.playback.passthroughFor

/**
 * The renderers factory that decides whether a surround track reaches the receiver as a
 * bitstream or is decoded here and sent as PCM.
 *
 * There is exactly one difference between the two modes and it is deliberate:
 *
 * - **Auto** overrides nothing. [DefaultAudioSink] built with a Context keeps its own
 *   capabilities receiver registered, so a soundbar switched on half an hour into an
 *   evening is noticed. A fixed snapshot taken at player construction could not be.
 * - **Manual** supplies a fixed [AudioCapabilities] built from the viewer's switches. It
 *   is an override rather than a filter, because the case it exists for is a television
 *   whose platform reports the wrong answer — one that could only ever subtract would be
 *   no help to it.
 *
 * Whatever is not bitstreamed falls through to the platform decoder and comes out as PCM,
 * so the worst a wrong switch can do is silence one format until it is switched back, and
 * every track still plays with all of them off.
 */
@UnstableApi
internal class MembyRenderersFactory(
    context: Context,
    private val preference: AudioPassthroughPreference,
    private val capabilities: DeviceAudioCapabilities = deviceAudioCapabilities,
) : DefaultRenderersFactory(context) {

    override fun buildAudioSink(
        context: Context,
        enableFloatOutput: Boolean,
        enableAudioTrackPlaybackParams: Boolean,
    ): AudioSink {
        val manualCapabilities = manualAudioCapabilities(preference, capabilities)
        val builder = if (manualCapabilities == null) {
            DefaultAudioSink.Builder(context)
        } else {
            fixedCapabilitiesAudioSinkBuilder(manualCapabilities)
        }
        return builder
            .setEnableFloatOutput(enableFloatOutput)
            .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
            .build()
    }
}

/**
 * Builds the fixed-capabilities sink used by manual mode.
 *
 * Media3 1.5 ignores [DefaultAudioSink.Builder.setAudioCapabilities] when the builder has
 * a Context, because the live route receiver replaces the supplied value. Its deprecated
 * context-free builder is therefore the only 1.5 API that can honour a viewer override.
 */
@Suppress("DEPRECATION")
private fun fixedCapabilitiesAudioSinkBuilder(
    capabilities: AudioCapabilities,
): DefaultAudioSink.Builder = DefaultAudioSink.Builder()
    .setAudioCapabilities(capabilities)

/**
 * The fixed capabilities a manual choice becomes, or null in automatic mode — where the
 * sink is left to track the hardware itself.
 *
 * PCM is always in the list. It is not a surround format anybody chose; it is the output
 * every decoded track is written as, and a capabilities object omitting it describes a
 * television that cannot play audio at all.
 */
@UnstableApi
@Suppress("DEPRECATION")
internal fun manualAudioCapabilities(
    preference: AudioPassthroughPreference,
    capabilities: DeviceAudioCapabilities,
): AudioCapabilities? {
    if (preference.mode != AudioPassthroughMode.MANUAL) return null
    val bitstreamed = capabilities.passthroughFor(preference)
    val encodings = buildList {
        add(C.ENCODING_PCM_16BIT)
        SurroundCodec.entries.filter { it in bitstreamed }.forEach { add(it.encoding()) }
    }
    val channels = if (bitstreamed.isEmpty()) {
        maxOf(capabilities.maxChannelCount, DeviceAudioCapabilities.STEREO_CHANNELS)
    } else {
        DeviceAudioCapabilities.BITSTREAM_CHANNELS
    }
    return AudioCapabilities(encodings.toIntArray(), channels)
}
