package expo.modules.astrascope

import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import expo.modules.kotlin.typedarray.Float32Array

/**
 * JS surface for the realtime scope. Both functions are synchronous (JSI):
 * [getSpectrumFrame] is pulled once per render frame from the JS thread and
 * fills a JS-preallocated Float32Array in place (no per-frame allocation, no
 * event-emitter traffic). The PCM that feeds it arrives on the audio thread via
 * the vendored kotlin-audio tap -> [ScopeBridge].
 */
class AstraScopeModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("AstraScope")

    // Gate the audio-thread tap (off when backgrounded/paused/reduced-motion).
    Function("setActive") { active: Boolean ->
      ScopeBridge.active = active
    }

    // Fill `out` with the latest dB spectrum; returns the number of bins written.
    Function("getSpectrumFrame") { out: Float32Array, smoothing: Double ->
      ScopeBridge.nativeFillSpectrum(out.toDirectBuffer(), out.length, smoothing.toFloat())
    }

    // Fill `out` with render-ready oscilloscope points (~[-1,1]).
    Function("getOscilloscopeFrame") { out: Float32Array ->
      ScopeBridge.nativeFillOscilloscope(out.toDirectBuffer(), out.length)
    }

    // --- M4: post-EQ spectrum (EQ screen overlay) ---
    // Gate the post-EQ tap (true only while the EQ screen is open).
    Function("setActivePostEq") { active: Boolean ->
      ScopeBridge.postEqActive = active
    }

    Function("getSpectrumFramePostEq") { out: Float32Array, smoothing: Double ->
      ScopeBridge.nativeFillSpectrumPostEq(out.toDirectBuffer(), out.length, smoothing.toFloat())
    }

    // --- M4: EQ params + per-track gain (consumed by the kotlin-audio processors) ---
    Function("setEqEnabled") { enabled: Boolean ->
      EqBridge.enabled = enabled
      EqBridge.revision += 1
    }

    Function("setEqPreamp") { linear: Double ->
      EqBridge.preampLinear = linear.toFloat()
      EqBridge.revision += 1
    }

    // Flat band params: 5 floats per band [typeOrdinal, frequency, gain, Q, enabled?1:0].
    Function("setEqBands") { params: FloatArray ->
      EqBridge.bands = params
      EqBridge.revision += 1
    }

    // Glide to an explicit gain (unity paths: no track / remote track / disabled).
    Function("setNormalizationGain") { linear: Double ->
      GainBridge.setGainSmooth(linear.toFloat())
    }

    // Register a queued track's gain by URL so the player can switch to it natively at
    // the exact media-item transition (no JS round-trip on track change).
    Function("setTrackGain") { url: String, linear: Double ->
      GainBridge.putGain(url, linear.toFloat())
    }

    // Bulk-register the whole queue's gains in one bridge call (gainRegistry.ts).
    Function("setTrackGains") { entries: Map<String, Double>, clearExisting: Boolean ->
      GainBridge.putGains(entries.mapValues { it.value.toFloat() }, clearExisting)
    }

    // Make the registered gain for this URL active now, as a smooth glide (mount /
    // settings change / late measurement — no media-item transition fires for these).
    Function("activateTrackGain") { url: String ->
      GainBridge.activateSmoothFor(url)
    }

    // Hard-prime while paused; unlike activateTrackGain this must not spend the
    // first audible second gliding down from the process default (unity).
    Function("primeTrackGain") { url: String ->
      GainBridge.primeFor(url)
    }

    // Conservative temp gain applied when a transition hits an unregistered URL
    // (unanalyzed track). JS keeps this at 1 while normalization is disabled.
    Function("setFallbackGain") { linear: Double ->
      GainBridge.fallbackGain = linear.toFloat()
    }
  }
}
