/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
:                                                                               :
:   █▀ █ █▀▀   ·   Blazing-fast pentesting suite                                :
:   ▄█ █ █▀    ·   BSD 3-Clause License                                         :
:                                                                               :
:   (c) 2022-2026 vmfunc, xyzeva,                                               :
:                 lunchcat alumni & contributors                                :
:                                                                               :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/

package output

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

// Spinner frames using simple ASCII
var spinnerFrames = []string{"|", "/", "-", "\\"}

// Spinner displays an animated spinner for indeterminate operations
type Spinner struct {
	message  string
	running  bool
	done     chan struct{}
	mu       sync.Mutex
	interval time.Duration
}

// NewSpinner creates a new spinner with the given message
func NewSpinner(message string) *Spinner {
	return &Spinner{
		message:  message,
		interval: 100 * time.Millisecond,
		done:     make(chan struct{}),
	}
}

// Start begins the spinner animation
func (s *Spinner) Start() {
	if apiMode || silent {
		return
	}

	s.mu.Lock()
	if s.running {
		s.mu.Unlock()
		return
	}
	s.running = true
	s.done = make(chan struct{})
	s.mu.Unlock()

	// In non-TTY mode, just print the message once
	if !IsTTY {
		fmt.Fprintf(sink, "    %s...\n", s.message)
		return
	}

	go s.animate()
}

// Stop halts the spinner and clears the line
func (s *Spinner) Stop() {
	if apiMode || silent {
		return
	}

	s.mu.Lock()
	if !s.running {
		s.mu.Unlock()
		return
	}
	s.running = false
	close(s.done)
	s.mu.Unlock()

	// Give animation goroutine time to exit
	time.Sleep(s.interval)

	// Clear the spinner line
	if IsTTY {
		ClearLine()
	}
}

// Update changes the spinner message while running
func (s *Spinner) Update(message string) {
	s.mu.Lock()
	s.message = message
	s.mu.Unlock()
}

func (s *Spinner) animate() {
	frame := 0
	ticker := time.NewTicker(s.interval)
	defer ticker.Stop()

	for {
		select {
		case <-s.done:
			return
		case <-ticker.C:
			s.mu.Lock()
			msg := s.message
			s.mu.Unlock()

			spinnerChar := prefixInfo.Render(spinnerFrames[frame])
			line := fmt.Sprintf("\r    %s %s", spinnerChar, msg)

			fmt.Fprint(sink, "\033[2K") // Clear line
			fmt.Fprint(sink, line)

			frame = (frame + 1) % len(spinnerFrames)
		}
	}
}
