package stereo

import (
	"math"

	"github.com/jan/fm-rds-tx/internal/audio"
	"github.com/jan/fm-rds-tx/internal/dsp"
)

const ssbHilbertTaps = 127

// Mode selects the stereo subcarrier modulation method.
type Mode int

const (
	ModeDSB Mode = iota // Standard DSB-SC (FCC §73.322 compliant)
	ModeSSB             // SSB-SC LSB only (Foti/Tarsio, experimental)
	ModeVSB             // Vestigial SB (0-200Hz DSB, above SSB; experimental)
)

// ParseMode converts a string to Mode. Returns ModeDSB for unknown values.
func ParseMode(s string) Mode {
	switch s {
	case "SSB", "ssb":
		return ModeSSB
	case "VSB", "vsb":
		return ModeVSB
	default:
		return ModeDSB
	}
}

// String returns the mode name.
func (m Mode) String() string {
	switch m {
	case ModeSSB:
		return "SSB"
	case ModeVSB:
		return "VSB"
	default:
		return "DSB"
	}
}

// Components holds the individual MPX components produced by the stereo encoder.
// All outputs are unity-normalized. The combiner controls actual injection levels.
type Components struct {
	Mono   float64 // (L+R)/2 baseband
	Stereo float64 // (L-R)/2 modulated onto 38 kHz subcarrier
	Pilot  float64 // sin(pilotPhase), unity amplitude
}

// sampleDelay is a simple fixed-sample delay line.
type sampleDelay struct {
	buf []float64
	pos int
}

func newSampleDelay(samples int) *sampleDelay {
	if samples <= 0 {
		return nil
	}
	return &sampleDelay{buf: make([]float64, samples)}
}

func (d *sampleDelay) Process(in float64) float64 {
	if d == nil || len(d.buf) == 0 {
		return in
	}
	out := d.buf[d.pos]
	d.buf[d.pos] = in
	d.pos++
	if d.pos >= len(d.buf) {
		d.pos = 0
	}
	return out
}

func (d *sampleDelay) Reset() {
	if d == nil {
		return
	}
	for i := range d.buf {
		d.buf[i] = 0
	}
	d.pos = 0
}

// StereoEncoder generates stereo MPX primitives from stereo audio frames.
// Supports DSB-SC (standard), SSB-SC (lower sideband only), and VSB modes.
type StereoEncoder struct {
	pilot     dsp.PilotGenerator
	lastPhase float64
	mode      Mode

	// SSB/VSB paths use a Hilbert transformer. The Hilbert FIR introduces a
	// fixed group delay, so the mono path must be delayed by the same amount.
	hilbert   *dsp.HilbertFilter
	monoDelay *sampleDelay

	// VSB remains experimental. The low band is kept as DSB, while the high band
	// is encoded as SSB. Mono delay compensation still applies so the decoded
	// stereo matrix does not produce comb/reverb artefacts.
	vsbLPF    *dsp.FilterChain
	hilbertHi *dsp.HilbertFilter
}

// NewStereoEncoder creates a StereoEncoder configured for the provided sample rate.
func NewStereoEncoder(sampleRate float64) StereoEncoder {
	return StereoEncoder{
		pilot: dsp.NewPilotGenerator(sampleRate),
		mode:  ModeDSB,
	}
}

// SetMode changes the stereo encoding mode and (re)initializes internal state.
func (s *StereoEncoder) SetMode(mode Mode, sampleRate float64) {
	s.mode = mode
	s.hilbert = nil
	s.hilbertHi = nil
	s.vsbLPF = nil
	s.monoDelay = nil

	switch mode {
	case ModeSSB:
		s.hilbert = dsp.NewHilbertFilter(ssbHilbertTaps)
		s.monoDelay = newSampleDelay((ssbHilbertTaps - 1) / 2)
	case ModeVSB:
		s.hilbert = dsp.NewHilbertFilter(ssbHilbertTaps)
		s.hilbertHi = dsp.NewHilbertFilter(ssbHilbertTaps)
		s.vsbLPF = dsp.NewLPF4(200, sampleRate)
		s.monoDelay = newSampleDelay((ssbHilbertTaps - 1) / 2)
	}
}

// Encode converts a stereo frame into MPX components.
func (s *StereoEncoder) Encode(frame audio.Frame) Components {
	pilotPhase := s.pilot.Phase()
	s.lastPhase = pilotPhase
	pilot := s.pilot.Sample()

	sub38sin := math.Sin(2 * math.Pi * 2 * pilotPhase)
	sub38cos := math.Cos(2 * math.Pi * 2 * pilotPhase)

	diff := float64(frame.Difference())
	mono := float64(frame.Mono())
	monoOut := mono

	var stereoOut float64

	switch s.mode {
	case ModeSSB:
		if s.hilbert == nil {
			stereoOut = diff * sub38sin
			break
		}
		monoOut = s.monoDelay.Process(mono)
		// SSB-LSB: s(t) = m_delayed(t)·sin(ωt) - m̂(t)·cos(ωt)
		// The - sign selects LSB (below 38 kHz).
		// ×2 compensates for the removed upper sideband (+6 dB).
		delayedDiff, hilb := s.hilbert.Process(diff)
		stereoOut = 2 * (delayedDiff*sub38sin - hilb*sub38cos)

	case ModeVSB:
		if s.hilbertHi == nil || s.vsbLPF == nil {
			stereoOut = diff * sub38sin
			break
		}
		monoOut = s.monoDelay.Process(mono)

		// Experimental VSB split:
		// - 0..200 Hz remains DSB to avoid aggressive low-frequency image shift.
		// - Above 200 Hz the residual is encoded as SSB-LSB.
		// The critical reverb bug was the mono-vs-diff timing mismatch; that is
		// fixed here by delaying mono by the Hilbert group delay.
		lo := s.vsbLPF.Process(diff)
		hi := diff - lo

		dsbPart := lo * sub38sin
		delayedHi, hilbHi := s.hilbertHi.Process(hi)
		ssbPart := 2 * (delayedHi*sub38sin - hilbHi*sub38cos)

		stereoOut = dsbPart + ssbPart

	default: // ModeDSB
		stereoOut = diff * sub38sin
	}

	return Components{
		Mono:   monoOut,
		Stereo: stereoOut,
		Pilot:  pilot,
	}
}

// Reset restarts the pilot generator and clears filter state.
func (s *StereoEncoder) Reset() {
	s.pilot.Reset()
	if s.hilbert != nil {
		s.hilbert.Reset()
	}
	if s.hilbertHi != nil {
		s.hilbertHi.Reset()
	}
	if s.vsbLPF != nil {
		s.vsbLPF.Reset()
	}
	if s.monoDelay != nil {
		s.monoDelay.Reset()
	}
}

// PilotPhase returns the pilot phase used in the most recent Encode() call.
func (s *StereoEncoder) PilotPhase() float64 {
	return s.lastPhase
}

// RDSCarrier returns sin(3 * pilotPhase * 2π) — the 57 kHz carrier
// phase-locked to the pilot, as required by the RDS standard.
func (s *StereoEncoder) RDSCarrier() float64 {
	return math.Sin(2 * math.Pi * 3 * s.lastPhase)
}