package dev.adriankuta.visualizer

import android.media.audiofx.Visualizer
import kotlinx.coroutines.*
import timber.log.Timber

/**
 * Simple wrapper over Android Visualizer to capture global output mix (audio session 0).
 */
class VisualizerController(
    private val audioSessionId: Int = 0,
    private val onWaveform: (ByteArray) -> Unit,
    private val onFft: (ByteArray) -> Unit,
) {
    private var visualizer: Visualizer? = null
    private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private var loggingJob: Job? = null
    private var latestWaveform: ByteArray? = null
    private var latestFft: ByteArray? = null

    /**
     * Starts capturing audio data via [Visualizer].
     *
     * Safe to call multiple times; subsequent calls are no-ops if already started.
     * If initialization fails (e.g., missing microphone permission or device restriction),
     * the controller performs cleanup and rethrows the underlying exception.
     */
    @Synchronized
    fun start() {
        if (visualizer != null) return
        try {
            Timber.d("Starting audio visualizer listening (audioSessionId: $audioSessionId)")
            val v = Visualizer(audioSessionId)
            // Use the maximum supported capture size for better resolution
            val captureSize = Visualizer.getCaptureSizeRange().let { it[1] }
            v.captureSize = captureSize
            val rate = Visualizer.getMaxCaptureRate() / 2 // decent rate
            v.setDataCaptureListener(object : Visualizer.OnDataCaptureListener {
                override fun onWaveFormDataCapture(
                    visualizer: Visualizer?,
                    waveform: ByteArray?,
                    samplingRate: Int
                ) {
                    if (waveform != null) {
                        latestWaveform = waveform.clone()
                        onWaveform(waveform)
                    }
                }

                override fun onFftDataCapture(
                    visualizer: Visualizer?,
                    fft: ByteArray?,
                    samplingRate: Int
                ) {
                    if (fft != null) {
                        latestFft = fft.clone()
                        onFft(fft)
                    }
                }
            }, rate, true, true)
            v.enabled = true
            visualizer = v

            // Start coroutine-based logging for raw data samples every 1 second
            startDataLogging()

            Timber.i("Audio visualizer listening started successfully")
        } catch (e: Throwable) {
            // If Visualizer cannot be initialized (e.g. permission or device restriction), ensure cleanup
            Timber.e(e, "Failed to start audio visualizer listening")
            stop()
            throw e
        }
    }

    /**
     * Stops capturing audio data and releases the underlying [Visualizer].
     * Safe to call multiple times.
     */
    @Synchronized
    fun stop() {
        Timber.d("Stopping audio visualizer listening")

        // Stop the logging coroutine
        loggingJob?.cancel()
        loggingJob = null

        visualizer?.enabled = false
        visualizer?.release()
        visualizer = null

        // Clear latest data
        latestWaveform = null
        latestFft = null

        Timber.i("Audio visualizer listening stopped")
    }

    /**
     * Starts a coroutine that logs raw data samples every 1 second.
     */
    private fun startDataLogging() {
        loggingJob?.cancel() // Cancel any existing logging job
        loggingJob = coroutineScope.launch {
            while (isActive) {
                delay(1000) // Wait for 1 second

                val waveform = latestWaveform
                val fft = latestFft

                if (waveform != null && fft != null) {
                    // Log sample of raw data (first few bytes to avoid too much log spam)
                    val waveformSample = waveform.take(8).joinToString(", ") { it.toString() }
                    val fftSample = fft.take(8).joinToString(", ") { it.toString() }

                    Timber.d("Raw data sample - Waveform {size: ${waveform.size}, sample: [$waveformSample...]}, FFT {size: {${fft.size}}, sample: [$fftSample...]}")
                } else {
                    Timber.d("Raw data sample - No data available yet")
                }
            }
        }
    }
}
