package ui

import (
	"math"
	"math/rand/v2"
	"testing"
	"time"
)

// benchSampleRate matches the default mixer sample rate used elsewhere in the package.
const benchSampleRate = 44100

// benchSamplesSine fills a buffer with a 440 Hz sine wave — a clean, single-tone
// workload that exercises the FFT without triggering any silence short-circuit.
func benchSamplesSine(n int) []float64 {
	buf := make([]float64, n)
	for i := range buf {
		buf[i] = math.Sin(2 * math.Pi * 440 * float64(i) / float64(benchSampleRate))
	}
	return buf
}

// benchSamplesNoise fills a buffer with deterministic white noise — the worst
// case for per-band averaging (energy spread across every bin).
func benchSamplesNoise(n int) []float64 {
	r := rand.New(rand.NewPCG(1, 2))
	buf := make([]float64, n)
	for i := range buf {
		buf[i] = r.Float64()*2 - 1
	}
	return buf
}

// benchSamplesSilence returns a zeroed buffer — the common case between tracks
// or during pause, where ideally we'd skip most of the FFT pipeline.
func benchSamplesSilence(n int) []float64 {
	return make([]float64, n)
}

func BenchmarkAnalyze(b *testing.B) {
	spec := spectrumAnalysisSpec(DefaultSpectrumBands)
	cases := []struct {
		name    string
		samples []float64
	}{
		{"Sine440", benchSamplesSine(defaultFFTSize)},
		{"Noise", benchSamplesNoise(defaultFFTSize)},
		{"Silence", benchSamplesSilence(defaultFFTSize)},
	}
	for _, tc := range cases {
		b.Run(tc.name, func(b *testing.B) {
			v := NewVisualizer(benchSampleRate)
			b.ReportAllocs()
			b.ResetTimer()
			for b.Loop() {
				v.Analyze(tc.samples, spec)
			}
		})
	}
}

// benchDriverModes covers the representative rendering shapes: pure spectrum
// bars, peak-cap animation, frame-driven animation, waveform oscilloscope.
var benchDriverModes = []VisMode{
	VisBars,
	VisBarsDot,
	VisBarsOutline,
	VisClassicPeak,
	VisMatrix,
	VisRain,
	VisFlame,
	VisLogo,
	VisPulse,
	VisWave,
	VisScope,
}

func BenchmarkRender(b *testing.B) {
	samples := benchSamplesSine(defaultFFTSize)
	for _, mode := range benchDriverModes {
		name := visModes[mode].name
		b.Run(name, func(b *testing.B) {
			v := NewVisualizer(benchSampleRate)
			v.Mode = mode
			v.Rows = 5
			// Prime driver state and bands so Render reflects realistic output.
			ctx := VisTickContext{
				Now:     time.Now(),
				Playing: true,
				Analyze: func(spec VisAnalysisSpec) []float64 {
					return v.Analyze(samples, spec)
				},
			}
			for range 4 {
				v.Tick(ctx)
			}
			b.ReportAllocs()
			b.ResetTimer()
			for b.Loop() {
				_ = v.Render()
			}
		})
	}
}

func BenchmarkTickPipeline(b *testing.B) {
	samples := benchSamplesSine(defaultFFTSize)
	for _, mode := range benchDriverModes {
		name := visModes[mode].name
		b.Run(name, func(b *testing.B) {
			v := NewVisualizer(benchSampleRate)
			v.Mode = mode
			v.Rows = 5
			ctx := VisTickContext{
				Now:     time.Now(),
				Playing: true,
				Analyze: func(spec VisAnalysisSpec) []float64 {
					return v.Analyze(samples, spec)
				},
			}
			b.ReportAllocs()
			b.ResetTimer()
			for b.Loop() {
				v.Tick(ctx)
				_ = v.Render()
			}
		})
	}
}
