package ui

import (
	"math"
	"strings"
)

// renderFirework draws firework bursts using Braille dots. Each burst launches
// from the bottom with a rising trail, then explodes into a sphere of particles
// that drift downward with gravity and fade. Audio energy drives the number of
// simultaneous bursts and the size of each explosion.
func (v *Visualizer) renderFirework(bands []float64) string {
	height := v.Rows
	dotRows := height * 4
	dotCols := PanelWidth * 2
	if dotRows < 4 || dotCols < 4 {
		return strings.Repeat("\n", max(0, height-1))
	}

	grid := make([]bool, dotRows*dotCols)

	var totalEnergy float64
	for _, e := range bands {
		totalEnergy += e
	}
	avgEnergy := totalEnergy / float64(len(bands))

	// Number of simultaneous firework bursts: 5 quiet, up to 14 loud.
	numBursts := 5 + int(avgEnergy*9)
	cycleLen := uint64(48)
	launchLen := uint64(10)

	for i := range numBursts {
		// Seed changes each cycle so bursts appear in new positions.
		cycle := (v.frame + uint64(i)*7) / cycleLen
		seed := cycle*104729 + uint64(i)*7919

		// Stagger starts so bursts don't all fire simultaneously.
		offset := (uint64(i)*cycleLen/uint64(numBursts) + (seed/3)%5)
		localFrame := (v.frame + offset) % cycleLen

		// Burst center — spread across the panel, upper portion.
		cx := int((seed * 6271) % uint64(dotCols))
		cy := int((seed*4391)%uint64(dotRows/2)) + dotRows/8

		// Associated band for energy-driven sizing.
		bandIdx := int(seed % uint64(len(bands)))
		energy := bands[bandIdx]

		if localFrame < launchLen {
			// Rising trail from bottom to burst center.
			progress := float64(localFrame) / float64(launchLen)
			trailY := dotRows - 1 - int(float64(dotRows-1-cy)*progress)
			// Short trail of a few dots.
			for dy := range 4 {
				ty := trailY + dy
				if ty >= 0 && ty < dotRows && cx >= 0 && cx < dotCols {
					grid[ty*dotCols+cx] = true
				}
			}
		} else {
			// Burst expansion and fade.
			burstT := float64(localFrame-launchLen) / float64(cycleLen-launchLen)

			maxRadius := 3.0 + energy*8.0
			// Fast expansion, then slow drift.
			radius := maxRadius * math.Min(burstT*3.0, 1.0)
			// Gravity pulls particles down over time.
			gravity := burstT * burstT * 5.0
			// Particles fade out over time.
			fade := max(0.0, 1.0-burstT*1.3)

			numParticles := 18 + int(energy*18)
			for p := range numParticles {
				angle := float64(p) / float64(numParticles) * 2 * math.Pi
				pSeed := seed + uint64(p)*2909
				speed := 0.6 + float64(pSeed%400)/1000.0

				px := cx + int(math.Cos(angle)*radius*speed)
				py := cy + int(math.Sin(angle)*radius*speed+gravity)

				// Stochastic fade — more particles disappear as time passes.
				if scatterHash(bandIdx, p, int(seed%100), v.frame) > fade {
					continue
				}

				if px >= 0 && px < dotCols && py >= 0 && py < dotRows {
					grid[py*dotCols+px] = true
				}
			}
		}
	}

	// Convert dot grid to Braille characters with row-based spectrum color.
	lines := make([]string, height)
	for row := range height {
		var content strings.Builder
		for ch := range PanelWidth {
			var braille rune = '\u2800'
			for dr := range 4 {
				for dc := range 2 {
					if grid[(row*4+dr)*dotCols+ch*2+dc] {
						braille |= brailleBit[dr][dc]
					}
				}
			}
			content.WriteRune(braille)
		}
		// Top rows bright (red), bottom dimmer (green) — fireworks in a night sky.
		lines[row] = specWrap(float64(height-1-row)/float64(height), content.String())
	}

	return strings.Join(lines, "\n")
}
