package dsp

import "math"

// HilbertFilter implements a FIR-based Hilbert transform with matched delay.
// It produces the analytic signal's imaginary part (90° phase shift of all
// frequency components) while also providing the appropriately delayed
// version of the original signal.
//
// Used for SSB-SC stereo encoding (Foti/Tarsio): the L-R difference signal
// is split into in-phase (delayed) and quadrature (Hilbert-transformed)
// components for single-sideband modulation.
type HilbertFilter struct {
	coeffs []float64 // FIR coefficients (length N, odd)
	delay  []float64 // circular buffer
	pos    int       // write position in buffer
	n      int       // filter length
	mid    int       // group delay = (N-1)/2
}

// NewHilbertFilter creates a Hilbert transform FIR filter.
// taps must be odd (typical: 127 for 228 kHz). Higher = better low-freq response.
func NewHilbertFilter(taps int) *HilbertFilter {
	if taps%2 == 0 {
		taps++ // must be odd
	}
	mid := (taps - 1) / 2
	coeffs := make([]float64, taps)

	// Hilbert FIR: h[n] = 2/(π·(n-mid)) for odd (n-mid), 0 for even, Hann windowed.
	for i := 0; i < taps; i++ {
		k := i - mid
		if k == 0 {
			coeffs[i] = 0 // center tap is always zero
		} else if k%2 != 0 {
			// Odd offset: ideal Hilbert coefficient × Hann window
			window := 0.5 * (1.0 - math.Cos(2*math.Pi*float64(i)/float64(taps)))
			coeffs[i] = (2.0 / (math.Pi * float64(k))) * window
		}
		// Even offset: coefficient is zero (already initialized)
	}

	return &HilbertFilter{
		coeffs: coeffs,
		delay:  make([]float64, taps),
		pos:    0,
		n:      taps,
		mid:    mid,
	}
}

// Process takes one input sample and returns (delayed, hilbert).
// - delayed: input delayed by (N-1)/2 samples (group delay matched)
// - hilbert: 90° phase-shifted version of the input
//
// Both outputs are time-aligned: delayed[n] corresponds to hilbert[n].
func (h *HilbertFilter) Process(in float64) (delayed, hilbert float64) {
	// Write new sample
	h.delay[h.pos] = in

	// Delayed output: sample from (N-1)/2 ago
	delayIdx := (h.pos - h.mid + h.n) % h.n
	delayed = h.delay[delayIdx]

	// Hilbert output: FIR convolution
	var sum float64
	for i := 0; i < h.n; i++ {
		idx := (h.pos - i + h.n) % h.n
		sum += h.delay[idx] * h.coeffs[i]
	}
	hilbert = sum

	h.pos = (h.pos + 1) % h.n
	return
}

// Reset clears the filter state.
func (h *HilbertFilter) Reset() {
	for i := range h.delay {
		h.delay[i] = 0
	}
	h.pos = 0
}
