package spinner

import (
	"errors"
	"time"

	"github.com/charmbracelet/tea"
	"github.com/muesli/termenv"
)

// Spinner denotes a type of spinner
type Spinner = int

// Available types of spinners
const (
	Line Spinner = iota
	Dot
)

var (
	// Spinner frames
	spinners = map[Spinner][]string{
		Line: []string{"|", "/", "-", "\\"},
		Dot:  []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "},
	}

	assertionErr = errors.New("could not perform assertion on model to what the spinner expects. are you sure you passed the right value?")

	color = termenv.ColorProfile().Color
)

// Model contains the state for the spinner. Use NewModel to create new models
// rather than using Model as a struct literal.
type Model struct {
	Type            Spinner
	FPS             int
	ForegroundColor string
	BackgroundColor string

	frame int
}

// NewModel returns a model with default values
func NewModel() Model {
	return Model{
		Type:  Line,
		FPS:   9,
		frame: 0,
	}
}

// TickMsg indicates that the timer has ticked and we should render a frame
type TickMsg struct{}

// Update is the Tea update function
func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
	switch msg.(type) {
	case TickMsg:
		m.frame++
		if m.frame >= len(spinners[m.Type]) {
			m.frame = 0
		}
		return m, nil
	default:
		return m, nil
	}
}

// View renders the model's view
func View(model Model) string {
	s := spinners[model.Type]
	if model.frame >= len(s) {
		return "[error]"
	}

	str := s[model.frame]

	if model.ForegroundColor != "" || model.BackgroundColor != "" {
		return termenv.
			String(str).
			Foreground(color(model.ForegroundColor)).
			Background(color(model.BackgroundColor)).
			String()
	}

	return str
}

// Sub is the subscription that allows the spinner to spin
func Sub(model tea.Model) tea.Msg {
	m, ok := model.(Model)
	if !ok {
		return assertionErr
	}
	time.Sleep(time.Second / time.Duration(m.FPS))
	return TickMsg{}
}