package ui

import (
	"fmt"
	"os"
	"sync"
	"time"
)

// Spinner provides an animated loading indicator that displays while
// long-running operations are in progress. It writes directly to stderr
// using a goroutine-based animation loop, avoiding Bubble Tea's terminal
// capability queries that can leak escape sequences (mode 2026 DECRPM).
//
// The KITT-style frames are generated by knightRiderFrames() in stream.go
// (same package) and use the active theme colors.
type Spinner struct {
	frames   []string
	fps      time.Duration
	done     chan struct{}
	finished chan struct{} // closed by run() after cleanup
	once     sync.Once
}

// NewSpinner creates a new animated KITT-style spinner using theme colors.
func NewSpinner() *Spinner {
	return &Spinner{
		frames:   knightRiderFrames(),
		fps:      time.Second / 14,
		done:     make(chan struct{}),
		finished: make(chan struct{}),
	}
}

// Start begins the spinner animation in a separate goroutine. The spinner
// will continue animating until Stop is called.
func (s *Spinner) Start() {
	go s.run()
}

// Stop halts the spinner animation and blocks until the animation goroutine
// has exited and the line is cleared. Safe to call multiple times.
func (s *Spinner) Stop() {
	s.once.Do(func() { close(s.done) })
	<-s.finished
}

// run is the animation loop that renders spinner frames to stderr.
func (s *Spinner) run() {
	defer close(s.finished) // unblock Stop()

	ticker := time.NewTicker(s.fps)
	defer ticker.Stop()

	var frame int
	for {
		select {
		case <-s.done:
			// Clear the spinner line and return.
			fmt.Fprint(os.Stderr, "\r\033[K")
			return
		case <-ticker.C:
			f := s.frames[frame%len(s.frames)]
			fmt.Fprintf(os.Stderr, "\r  %s", f)
			frame++
		}
	}
}
