package scoreboard

import (
	"fmt"
	"slices"
	"strings"
)

// Scoreboard represents a scoreboard that may be sent to a player. The scoreboard is shown on the right side
// of the player's screen.
// Scoreboard implements the io.Writer and io.StringWriter interfaces. fmt.Fprintf and fmt.Fprint may be used
// to write formatted text to the scoreboard.
type Scoreboard struct {
	name       string
	lines      []string
	padding    bool
	descending bool
}

// New returns a new scoreboard with the display name passed. Once returned, lines may be added to the
// scoreboard to add text to it. The name is formatted according to the rules of fmt.Sprintln.
// Changing the scoreboard after sending it to a player will not update the scoreboard of the player
// automatically: Player.SendScoreboard() must be called again to update it.
func New(name ...any) *Scoreboard {
	return &Scoreboard{name: strings.TrimSuffix(fmt.Sprintln(name...), "\n"), padding: true}
}

// Name returns the display name of the scoreboard, as passed during the construction of the scoreboard.
func (board *Scoreboard) Name() string {
	return board.name
}

// Write writes a slice of data as text to the scoreboard. Newlines may be written to create a new line on
// the scoreboard.
func (board *Scoreboard) Write(p []byte) (n int, err error) {
	return board.WriteString(string(p))
}

// WriteString writes a string of text to the scoreboard. Newlines may be written to create a new line on
// the scoreboard.
func (board *Scoreboard) WriteString(s string) (n int, err error) {
	lines := strings.Split(s, "\n")
	board.lines = append(board.lines, lines...)

	// Scoreboards can have up to 15 lines. (16 including the title.)
	if len(board.lines) >= 15 {
		return len(lines), fmt.Errorf("write scoreboard: maximum of 15 lines of text exceeded")
	}
	return len(lines), nil
}

// Set changes a specific line in the scoreboard and adds empty lines until this index is reached. Set panics if the
// index passed is negative or 15+.
func (board *Scoreboard) Set(index int, s string) {
	if index < 0 || index >= 15 {
		panic(fmt.Sprintf("index out of range %v", index))
	}
	if diff := index - (len(board.lines) - 1); diff > 0 {
		board.lines = append(board.lines, make([]string, diff)...)
	}
	// Remove new lines from the string
	board.lines[index] = strings.TrimSuffix(strings.TrimSuffix(s, "\n"), "\n")
}

// Remove removes a specific line from the scoreboard. Remove panics if the index passed is negative or 15+.
func (board *Scoreboard) Remove(index int) {
	if index < 0 || index >= 15 {
		panic(fmt.Sprintf("index out of range %v", index))
	}
	board.lines = append(board.lines[:index], board.lines[index+1:]...)
}

// RemovePadding removes the padding of one space that is added to the start of every line.
func (board *Scoreboard) RemovePadding() {
	board.padding = false
}

// Lines returns the data of the Scoreboard as a slice of strings.
func (board *Scoreboard) Lines() []string {
	lines := slices.Clone(board.lines)
	if board.padding {
		for i, line := range lines {
			if len(board.name)-len(line)-2 <= 0 {
				lines[i] = " " + line + " "
				continue
			}
			lines[i] = " " + line + strings.Repeat(" ", len(board.name)-len(line)-2)
		}
	}
	if board.descending {
		slices.Reverse(lines)
	}
	return lines
}

// Descending returns whether the scoreboard is sorted in descending order.
func (board *Scoreboard) Descending() bool {
	return board.descending
}

// SetDescending sets the scoreboard sort order to descending.
// When sending the scoreboard to the player, it will reverse the order of the lines to match when it's ascending.
func (board *Scoreboard) SetDescending() {
	board.descending = true
}
