package color

import (
	"fmt"
	"strings"
)

/*************************************************************
 * 16 color Style
 *************************************************************/

// Style a 16 color style. can add: fg color, bg color, color options
//
// Example:
//
//	color.Style{color.FgGreen}.Print("message")
type Style []Color

// New create a custom style
//
// Usage:
//
//	color.New(color.FgGreen).Print("message")
//	equals to:
//	color.Style{color.FgGreen}.Print("message")
func New(colors ...Color) Style {
	return colors
}

// Save to global styles map
func (s Style) Save(name string) {
	AddStyle(name, s)
}

// Add to global styles map
func (s *Style) Add(cs ...Color) {
	*s = append(*s, cs...)
}

// Render colored text
//
// Usage:
//
//	color.New(color.FgGreen).Render("text")
//	color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text")
func (s Style) Render(a ...any) string {
	return RenderCode(s.String(), a...)
}

// Renderln render text with newline.
// like Println, will add spaces for each argument
//
// Usage:
//
//	color.New(color.FgGreen).Renderln("text", "more")
//	color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text", "more")
func (s Style) Renderln(a ...any) string {
	return RenderWithSpaces(s.String(), a...)
}

// Sprint is alias of the 'Render'
func (s Style) Sprint(a ...any) string {
	return RenderCode(s.String(), a...)
}

// Sprintf format and render message.
func (s Style) Sprintf(format string, a ...any) string {
	return RenderString(s.String(), fmt.Sprintf(format, a...))
}

// Print render and Print text
func (s Style) Print(a ...any) {
	doPrintV2(s.String(), fmt.Sprint(a...))
}

// Printf render and print text
func (s Style) Printf(format string, a ...any) {
	doPrintV2(s.Code(), fmt.Sprintf(format, a...))
}

// Println render and print text line
func (s Style) Println(a ...any) {
	doPrintlnV2(s.String(), a)
}

// Code convert to code string. returns like "32;45;3"
func (s Style) Code() string {
	return s.String()
}

// String convert to code string. returns like "32;45;3"
func (s Style) String() string {
	return Colors2code(s...)
}

// IsEmpty style
func (s Style) IsEmpty() bool {
	return len(s) == 0
}

/*************************************************************
 * Theme(extended Style)
 *************************************************************/

// Theme definition. extends from Style
type Theme struct {
	// Name theme name
	Name string
	// Style for the theme
	Style
}

// NewTheme instance
func NewTheme(name string, style Style) *Theme {
	return &Theme{name, style}
}

// Save to themes map
func (t *Theme) Save() {
	AddTheme(t.Name, t.Style)
}

// Tips use name as title, only apply style for name
func (t *Theme) Tips(format string, a ...any) {
	// only apply style for name
	t.Print(strings.ToUpper(t.Name) + ": ")
	Printf(format+"\n", a...)
}

// Prompt use name as title, and apply style for message
func (t *Theme) Prompt(format string, a ...any) {
	title := strings.ToUpper(t.Name) + ":"
	t.Println(title, fmt.Sprintf(format, a...))
}

// Block like Prompt, but will wrap a empty line
func (t *Theme) Block(format string, a ...any) {
	title := strings.ToUpper(t.Name) + ":\n"

	t.Println(title, fmt.Sprintf(format, a...))
}

/*************************************************************
 * Theme: internal themes
 *************************************************************/

// internal themes(like bootstrap style)
// Usage:
//
//	color.Info.Print("message")
//	color.Info.Printf("a %s message", "test")
//	color.Warn.Println("message")
//	color.Error.Println("message")
var (
	// Info color style
	Info = &Theme{"info", Style{OpReset, FgGreen}}
	// Note color style
	Note = &Theme{"note", Style{OpBold, FgLightCyan}}
	// Warn color style
	Warn = &Theme{"warning", Style{OpBold, FgYellow}}
	// Light color style
	Light = &Theme{"light", Style{FgLightWhite, BgBlack}}
	// Error color style
	Error = &Theme{"error", Style{FgLightWhite, BgRed}}
	// Danger color style
	Danger = &Theme{"danger", Style{OpBold, FgRed}}
	// Debug color style
	Debug = &Theme{"debug", Style{OpReset, FgCyan}}
	// Notice color style
	Notice = &Theme{"notice", Style{OpBold, FgCyan}}
	// Comment color style
	Comment = &Theme{"comment", Style{OpReset, FgYellow}}
	// Success color style
	Success = &Theme{"success", Style{OpBold, FgGreen}}
	// Primary color style
	Primary = &Theme{"primary", Style{OpReset, FgBlue}}
	// Question color style
	Question = &Theme{"question", Style{OpReset, FgMagenta}}
	// Secondary color style
	Secondary = &Theme{"secondary", Style{FgDarkGray}}
)

// Themes internal defined themes.
// Usage:
//
//	color.Themes["info"].Println("message")
var Themes = map[string]*Theme{
	"info":  Info,
	"note":  Note,
	"light": Light,
	"error": Error,

	"debug":   Debug,
	"danger":  Danger,
	"notice":  Notice,
	"success": Success,
	"comment": Comment,
	"primary": Primary,
	"warning": Warn,

	"question":  Question,
	"secondary": Secondary,
}

// AddTheme add a theme and style
func AddTheme(name string, style Style) {
	Themes[name] = NewTheme(name, style)
	Styles[name] = style
}

// GetTheme get defined theme by name
func GetTheme(name string) *Theme {
	return Themes[name]
}

/*************************************************************
 * internal styles
 *************************************************************/

// Styles internal defined styles, like bootstrap styles.
// Usage:
//
//	color.Styles["info"].Println("message")
var Styles = map[string]Style{
	"info":  {OpReset, FgGreen},
	"note":  {OpBold, FgLightCyan},
	"light": {FgLightWhite, BgRed},
	"error": {FgLightWhite, BgRed},

	"danger":  {OpBold, FgRed},
	"notice":  {OpBold, FgCyan},
	"success": {OpBold, FgGreen},
	"comment": {OpReset, FgMagenta},
	"primary": {OpReset, FgBlue},
	"warning": {OpBold, FgYellow},

	"question":  {OpReset, FgMagenta},
	"secondary": {FgDarkGray},
}

// some style name alias
var styleAliases = map[string]string{
	"err":  "error",
	"suc":  "success",
	"warn": "warning",
}

// AddStyle add a style
func AddStyle(name string, s Style) {
	Styles[name] = s
}

// GetStyle get defined style by name
func GetStyle(name string) Style {
	if s, ok := Styles[name]; ok {
		return s
	}

	if realName, ok := styleAliases[name]; ok {
		return Styles[realName]
	}

	// empty style
	return New()
}

/*************************************************************
 * color scheme
 *************************************************************/

// Scheme struct
type Scheme struct {
	Name   string
	Styles map[string]Style
}

// NewScheme create new Scheme
func NewScheme(name string, styles map[string]Style) *Scheme {
	return &Scheme{Name: name, Styles: styles}
}

// NewDefaultScheme create an defuault color Scheme
func NewDefaultScheme(name string) *Scheme {
	return NewScheme(name, map[string]Style{
		"info":  {OpReset, FgGreen},
		"warn":  {OpBold, FgYellow},
		"error": {FgLightWhite, BgRed},
	})
}

// Style get by name
func (s *Scheme) Style(name string) Style {
	return s.Styles[name]
}

// Infof message print
func (s *Scheme) Infof(format string, a ...any) {
	s.Styles["info"].Printf(format, a...)
}

// Infoln message print
func (s *Scheme) Infoln(v ...any) {
	s.Styles["info"].Println(v...)
}

// Warnf message print
func (s *Scheme) Warnf(format string, a ...any) {
	s.Styles["warn"].Printf(format, a...)
}

// Warnln message print
func (s *Scheme) Warnln(v ...any) {
	s.Styles["warn"].Println(v...)
}

// Errorf message print
func (s *Scheme) Errorf(format string, a ...any) {
	s.Styles["error"].Printf(format, a...)
}

// Errorln message print
func (s *Scheme) Errorln(v ...any) {
	s.Styles["error"].Println(v...)
}
