package dsp

import "math"

// MPXLimiter is a look-ahead peak limiter for the composite MPX signal.
// It prevents overmodulation by keeping the composite output within
// the configured ceiling. The limiter applies gain reduction smoothly
// to avoid introducing audible artifacts.
type MPXLimiter struct {
	ceiling     float64 // maximum absolute output level (e.g. 1.0)
	attackCoeff float64 // attack smoothing coefficient
	releaseCoeff float64 // release smoothing coefficient
	gainReduction float64 // current gain reduction in linear scale (0 = no reduction)
}

// NewMPXLimiter creates a limiter with the given ceiling, attack and release
// times in milliseconds, and sample rate.
func NewMPXLimiter(ceiling, attackMs, releaseMs, sampleRate float64) *MPXLimiter {
	if ceiling <= 0 {
		ceiling = 1.0
	}
	if attackMs <= 0 {
		attackMs = 0.1 // fast attack for MPX
	}
	if releaseMs <= 0 {
		releaseMs = 50 // moderate release
	}
	attackSamples := attackMs * sampleRate / 1000
	releaseSamples := releaseMs * sampleRate / 1000

	return &MPXLimiter{
		ceiling:      ceiling,
		attackCoeff:  1.0 - math.Exp(-1.0/attackSamples),
		releaseCoeff: 1.0 - math.Exp(-1.0/releaseSamples),
	}
}

// Process applies limiting to a single composite sample.
func (l *MPXLimiter) Process(in float64) float64 {
	absIn := math.Abs(in)
	targetReduction := 0.0
	if absIn > l.ceiling {
		targetReduction = 1.0 - l.ceiling/absIn
	}

	if targetReduction > l.gainReduction {
		l.gainReduction += l.attackCoeff * (targetReduction - l.gainReduction)
	} else {
		l.gainReduction += l.releaseCoeff * (targetReduction - l.gainReduction)
	}

	gain := 1.0 - l.gainReduction
	return in * gain
}

// Reset clears the limiter state.
func (l *MPXLimiter) Reset() {
	l.gainReduction = 0
}

// HardClip provides a simple hard clipper as a safety net after the limiter.
func HardClip(sample, ceiling float64) float64 {
	if sample > ceiling {
		return ceiling
	}
	if sample < -ceiling {
		return -ceiling
	}
	return sample
}
