// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package config

import (
	"fmt"
	"os"

	"github.com/derailed/k9s/internal/config/data"
	"github.com/derailed/k9s/internal/config/json"
	"github.com/derailed/tcell/v2"
	"github.com/derailed/tview"
	"gopkg.in/yaml.v3"
)

// StyleListener represents a skin's listener.
type StyleListener interface {
	// StylesChanged notifies listener the skin changed.
	StylesChanged(*Styles)
}

// TextStyle tracks text styles.
type TextStyle string

const (
	// TextStyleNormal is the default text style.
	TextStyleNormal TextStyle = "normal"

	// TextStyleBold is the bold text style.
	TextStyleBold TextStyle = "bold"

	// TextStyleDim is the dim text style.
	TextStyleDim TextStyle = "dim"
)

// ToShortString returns a short string representation of the text style.
func (ts TextStyle) ToShortString() string {
	switch ts {
	case TextStyleNormal:
		return "-"
	case TextStyleBold:
		return "b"
	case TextStyleDim:
		return "d"
	default:
		return "d"
	}
}

type (
	// Styles tracks K9s styling options.
	Styles struct {
		K9s       Style `json:"k9s" yaml:"k9s"`
		listeners []StyleListener
	}

	// Style tracks K9s styles.
	Style struct {
		Body   Body   `json:"body" yaml:"body"`
		Prompt Prompt `json:"prompt" yaml:"prompt"`
		Help   Help   `json:"help" yaml:"help"`
		Frame  Frame  `json:"frame" yaml:"frame"`
		Info   Info   `json:"info" yaml:"info"`
		Views  Views  `json:"views" yaml:"views"`
		Dialog Dialog `json:"dialog" yaml:"dialog"`
	}

	// Prompt tracks command styles
	Prompt struct {
		FgColor      Color        `json:"fgColor" yaml:"fgColor"`
		BgColor      Color        `json:"bgColor" yaml:"bgColor"`
		SuggestColor Color        `json:"" yaml:"suggestColor"`
		Border       PromptBorder `json:"" yaml:"border"`
	}

	// PromptBorder tracks the color of the prompt depending on its kind (e.g., command or filter)
	PromptBorder struct {
		CommandColor Color `json:"command" yaml:"command"`
		DefaultColor Color `json:"default" yaml:"default"`
	}

	// Help tracks help styles.
	Help struct {
		FgColor      Color `json:"fgColor" yaml:"fgColor"`
		BgColor      Color `json:"bgColor" yaml:"bgColor"`
		SectionColor Color `json:"sectionColor" yaml:"sectionColor"`
		KeyColor     Color `json:"keyColor" yaml:"keyColor"`
		NumKeyColor  Color `json:"numKeyColor" yaml:"numKeyColor"`
	}

	// Body tracks body styles.
	Body struct {
		FgColor        Color `json:"fgColor" yaml:"fgColor"`
		BgColor        Color `json:"bgColor" yaml:"bgColor"`
		LogoColor      Color `json:"logoColor" yaml:"logoColor"`
		LogoColorMsg   Color `json:"logoColorMsg" yaml:"logoColorMsg"`
		LogoColorInfo  Color `json:"logoColorInfo" yaml:"logoColorInfo"`
		LogoColorWarn  Color `json:"logoColorWarn" yaml:"logoColorWarn"`
		LogoColorError Color `json:"logoColorError" yaml:"logoColorError"`
	}

	// Dialog tracks dialog styles.
	Dialog struct {
		FgColor            Color `json:"fgColor" yaml:"fgColor"`
		BgColor            Color `json:"bgColor" yaml:"bgColor"`
		ButtonFgColor      Color `json:"buttonFgColor" yaml:"buttonFgColor"`
		ButtonBgColor      Color `json:"buttonBgColor" yaml:"buttonBgColor"`
		ButtonFocusFgColor Color `json:"buttonFocusFgColor" yaml:"buttonFocusFgColor"`
		ButtonFocusBgColor Color `json:"buttonFocusBgColor" yaml:"buttonFocusBgColor"`
		LabelFgColor       Color `json:"labelFgColor" yaml:"labelFgColor"`
		FieldFgColor       Color `json:"fieldFgColor" yaml:"fieldFgColor"`
	}

	// Frame tracks frame styles.
	Frame struct {
		Title  Title  `json:"title" yaml:"title"`
		Border Border `json:"border" yaml:"border"`
		Menu   Menu   `json:"menu" yaml:"menu"`
		Crumb  Crumb  `json:"crumbs" yaml:"crumbs"`
		Status Status `json:"status" yaml:"status"`
	}

	// Views tracks individual view styles.
	Views struct {
		Table  Table  `json:"table" yaml:"table"`
		Xray   Xray   `json:"xray" yaml:"xray"`
		Charts Charts `json:"charts" yaml:"charts"`
		Yaml   Yaml   `json:"yaml" yaml:"yaml"`
		Picker Picker `json:"picker" yaml:"picker"`
		Log    Log    `json:"logs" yaml:"logs"`
	}

	// Status tracks resource status styles.
	Status struct {
		NewColor       Color `json:"newColor" yaml:"newColor"`
		ModifyColor    Color `json:"modifyColor" yaml:"modifyColor"`
		AddColor       Color `json:"addColor" yaml:"addColor"`
		PendingColor   Color `json:"pendingColor" yaml:"pendingColor"`
		ErrorColor     Color `json:"errorColor" yaml:"errorColor"`
		HighlightColor Color `json:"highlightColor" yaml:"highlightColor"`
		KillColor      Color `json:"killColor" yaml:"killColor"`
		CompletedColor Color `json:"completedColor" yaml:"completedColor"`
	}

	// Log tracks Log styles.
	Log struct {
		FgColor   Color        `json:"fgColor" yaml:"fgColor"`
		BgColor   Color        `json:"bgColor" yaml:"bgColor"`
		Indicator LogIndicator `json:"indicator" yaml:"indicator"`
	}

	// Picker tracks color when selecting containers
	Picker struct {
		MainColor     Color `json:"mainColor" yaml:"mainColor"`
		FocusColor    Color `json:"focusColor" yaml:"focusColor"`
		ShortcutColor Color `json:"shortcutColor" yaml:"shortcutColor"`
	}

	// LogIndicator tracks log view indicator.
	LogIndicator struct {
		FgColor        Color `json:"fgColor" yaml:"fgColor"`
		BgColor        Color `json:"bgColor" yaml:"bgColor"`
		ToggleOnColor  Color `json:"toggleOnColor" yaml:"toggleOnColor"`
		ToggleOffColor Color `json:"toggleOffColor" yaml:"toggleOffColor"`
	}

	// Yaml tracks yaml styles.
	Yaml struct {
		KeyColor   Color `json:"keyColor" yaml:"keyColor"`
		ValueColor Color `json:"valueColor" yaml:"valueColor"`
		ColonColor Color `json:"colonColor" yaml:"colonColor"`
	}

	// Title tracks title styles.
	Title struct {
		FgColor        Color `json:"fgColor" yaml:"fgColor"`
		BgColor        Color `json:"bgColor" yaml:"bgColor"`
		HighlightColor Color `json:"highlightColor" yaml:"highlightColor"`
		CounterColor   Color `json:"counterColor" yaml:"counterColor"`
		FilterColor    Color `json:"filterColor" yaml:"filterColor"`
	}

	// Info tracks info styles.
	Info struct {
		SectionColor Color `json:"sectionColor" yaml:"sectionColor"`
		FgColor      Color `json:"fgColor" yaml:"fgColor"`
	}

	// Border tracks border styles.
	Border struct {
		FgColor    Color `json:"fgColor" yaml:"fgColor"`
		FocusColor Color `json:"focusColor" yaml:"focusColor"`
	}

	// Crumb tracks crumbs styles.
	Crumb struct {
		FgColor     Color `json:"fgColor" yaml:"fgColor"`
		BgColor     Color `json:"bgColor" yaml:"bgColor"`
		ActiveColor Color `json:"activeColor" yaml:"activeColor"`
	}

	// Table tracks table styles.
	Table struct {
		FgColor       Color       `json:"fgColor" yaml:"fgColor"`
		BgColor       Color       `json:"bgColor" yaml:"bgColor"`
		CursorFgColor Color       `json:"cursorFgColor" yaml:"cursorFgColor"`
		CursorBgColor Color       `json:"cursorBgColor" yaml:"cursorBgColor"`
		MarkColor     Color       `json:"markColor" yaml:"markColor"`
		Header        TableHeader `json:"header" yaml:"header"`
	}

	// TableHeader tracks table header styles.
	TableHeader struct {
		FgColor     Color `json:"fgColor" yaml:"fgColor"`
		BgColor     Color `json:"bgColor" yaml:"bgColor"`
		SorterColor Color `json:"sorterColor" yaml:"sorterColor"`
	}

	// Xray tracks xray styles.
	Xray struct {
		FgColor         Color `json:"fgColor" yaml:"fgColor"`
		BgColor         Color `json:"bgColor" yaml:"bgColor"`
		CursorColor     Color `json:"cursorColor" yaml:"cursorColor"`
		CursorTextColor Color `json:"cursorTextColor" yaml:"cursorTextColor"`
		GraphicColor    Color `json:"graphicColor" yaml:"graphicColor"`
	}

	// Menu tracks menu styles.
	Menu struct {
		FgColor     Color     `json:"fgColor" yaml:"fgColor"`
		FgStyle     TextStyle `json:"fgStyle" yaml:"fgStyle"`
		KeyColor    Color     `json:"keyColor" yaml:"keyColor"`
		NumKeyColor Color     `json:"numKeyColor" yaml:"numKeyColor"`
	}

	// Charts tracks charts styles.
	Charts struct {
		BgColor            Color             `json:"bgColor" yaml:"bgColor"`
		DialBgColor        Color             `json:"dialBgColor" yaml:"dialBgColor"`
		ChartBgColor       Color             `json:"chartBgColor" yaml:"chartBgColor"`
		DefaultDialColors  Colors            `json:"defaultDialColors" yaml:"defaultDialColors"`
		DefaultChartColors Colors            `json:"defaultChartColors" yaml:"defaultChartColors"`
		ResourceColors     map[string]Colors `json:"resourceColors" yaml:"resourceColors"`
	}
)

func newStyle() Style {
	return Style{
		Body:   newBody(),
		Prompt: newPrompt(),
		Help:   newHelp(),
		Frame:  newFrame(),
		Info:   newInfo(),
		Views:  newViews(),
		Dialog: newDialog(),
	}
}

func newDialog() Dialog {
	return Dialog{
		FgColor:            "cadetblue",
		BgColor:            "black",
		ButtonBgColor:      "darkslateblue",
		ButtonFgColor:      "black",
		ButtonFocusBgColor: "dodgerblue",
		ButtonFocusFgColor: "black",
		LabelFgColor:       "white",
		FieldFgColor:       "white",
	}
}

func newPrompt() Prompt {
	return Prompt{
		FgColor:      "cadetblue",
		BgColor:      "black",
		SuggestColor: "dodgerblue",
		Border: PromptBorder{
			DefaultColor: "seagreen",
			CommandColor: "aqua",
		},
	}
}

func newCharts() Charts {
	return Charts{
		BgColor:            "black",
		DialBgColor:        "black",
		ChartBgColor:       "black",
		DefaultDialColors:  Colors{Color("palegreen"), Color("orangered")},
		DefaultChartColors: Colors{Color("palegreen"), Color("orangered")},
		ResourceColors: map[string]Colors{
			CPU: {Color("dodgerblue"), Color("darkslateblue")},
			MEM: {Color("yellow"), Color("goldenrod")},
		},
	}
}

func newViews() Views {
	return Views{
		Table:  newTable(),
		Xray:   newXray(),
		Charts: newCharts(),
		Yaml:   newYaml(),
		Picker: newPicker(),
		Log:    newLog(),
	}
}

func newFrame() Frame {
	return Frame{
		Title:  newTitle(),
		Border: newBorder(),
		Menu:   newMenu(),
		Crumb:  newCrumb(),
		Status: newStatus(),
	}
}

func newHelp() Help {
	return Help{
		FgColor:      "cadetblue",
		BgColor:      "black",
		SectionColor: "green",
		KeyColor:     "dodgerblue",
		NumKeyColor:  "fuchsia",
	}
}

func newBody() Body {
	return Body{
		FgColor:        "cadetblue",
		BgColor:        "black",
		LogoColor:      "orange",
		LogoColorMsg:   "white",
		LogoColorInfo:  "green",
		LogoColorWarn:  "mediumvioletred",
		LogoColorError: "red",
	}
}

func newStatus() Status {
	return Status{
		NewColor:       "lightskyblue",
		ModifyColor:    "greenyellow",
		AddColor:       "dodgerblue",
		PendingColor:   "darkorange",
		ErrorColor:     "orangered",
		HighlightColor: "aqua",
		KillColor:      "mediumpurple",
		CompletedColor: "lightslategray",
	}
}

func newPicker() Picker {
	return Picker{
		MainColor:     "white",
		FocusColor:    "aqua",
		ShortcutColor: "aqua",
	}
}

func newLog() Log {
	return Log{
		FgColor:   "lightskyblue",
		BgColor:   "black",
		Indicator: newLogIndicator(),
	}
}

func newLogIndicator() LogIndicator {
	return LogIndicator{
		FgColor:        "dodgerblue",
		BgColor:        "black",
		ToggleOnColor:  "limegreen",
		ToggleOffColor: "gray",
	}
}

func newYaml() Yaml {
	return Yaml{
		KeyColor:   "steelblue",
		ColonColor: "white",
		ValueColor: "papayawhip",
	}
}

func newTitle() Title {
	return Title{
		FgColor:        "aqua",
		BgColor:        "black",
		HighlightColor: "fuchsia",
		CounterColor:   "papayawhip",
		FilterColor:    "seagreen",
	}
}

func newInfo() Info {
	return Info{
		SectionColor: "white",
		FgColor:      "orange",
	}
}

func newXray() Xray {
	return Xray{
		FgColor:         "aqua",
		BgColor:         "black",
		CursorColor:     "dodgerblue",
		CursorTextColor: "black",
		GraphicColor:    "cadetblue",
	}
}

func newTable() Table {
	return Table{
		FgColor:       "aqua",
		BgColor:       "black",
		CursorFgColor: "black",
		CursorBgColor: "aqua",
		MarkColor:     "palegreen",
		Header:        newTableHeader(),
	}
}

func newTableHeader() TableHeader {
	return TableHeader{
		FgColor:     "white",
		BgColor:     "black",
		SorterColor: "aqua",
	}
}

func newCrumb() Crumb {
	return Crumb{
		FgColor:     "black",
		BgColor:     "aqua",
		ActiveColor: "orange",
	}
}

func newBorder() Border {
	return Border{
		FgColor:    "dodgerblue",
		FocusColor: "lightskyblue",
	}
}

func newMenu() Menu {
	return Menu{
		FgColor:     "white",
		KeyColor:    "dodgerblue",
		NumKeyColor: "fuchsia",
	}
}

// NewStyles creates a new default config.
func NewStyles() *Styles {
	var s Styles
	if err := yaml.Unmarshal(stockSkinTpl, &s); err == nil {
		return &s
	}

	return &Styles{
		K9s: newStyle(),
	}
}

// Reset resets styles.
func (s *Styles) Reset() {
	if err := yaml.Unmarshal(stockSkinTpl, s); err != nil {
		s.K9s = newStyle()
	}
}

// FgColor returns the foreground color.
func (s *Styles) FgColor() tcell.Color {
	return s.Body().FgColor.Color()
}

// BgColor returns the background color.
func (s *Styles) BgColor() tcell.Color {
	return s.Body().BgColor.Color()
}

// AddListener registers a new listener.
func (s *Styles) AddListener(l StyleListener) {
	s.listeners = append(s.listeners, l)
}

// RemoveListener removes a listener.
func (s *Styles) RemoveListener(l StyleListener) {
	victim := -1
	for i, lis := range s.listeners {
		if lis == l {
			victim = i
			break
		}
	}
	if victim == -1 {
		return
	}
	s.listeners = append(s.listeners[:victim], s.listeners[victim+1:]...)
}

func (s *Styles) fireStylesChanged() {
	for _, list := range s.listeners {
		list.StylesChanged(s)
	}
}

// Body returns body styles.
func (s *Styles) Body() Body {
	return s.K9s.Body
}

// Prompt returns prompt styles.
func (s *Styles) Prompt() Prompt {
	return s.K9s.Prompt
}

// Frame returns frame styles.
func (s *Styles) Frame() Frame {
	return s.K9s.Frame
}

// Crumb returns crumb styles.
func (s *Styles) Crumb() Crumb {
	return s.Frame().Crumb
}

// Title returns title styles.
func (s *Styles) Title() Title {
	return s.Frame().Title
}

// Charts returns charts styles.
func (s *Styles) Charts() Charts {
	return s.K9s.Views.Charts
}

// Dialog returns dialog styles.
func (s *Styles) Dialog() Dialog {
	return s.K9s.Dialog
}

// Table returns table styles.
func (s *Styles) Table() Table {
	return s.K9s.Views.Table
}

// Xray returns xray styles.
func (s *Styles) Xray() Xray {
	return s.K9s.Views.Xray
}

// Views returns views styles.
func (s *Styles) Views() Views {
	return s.K9s.Views
}

// Load K9s configuration from file.
func (s *Styles) Load(path string) error {
	bb, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	if err := data.JSONValidator.Validate(json.SkinSchema, bb); err != nil {
		return err
	}
	if err := yaml.Unmarshal(bb, s); err != nil {
		return err
	}

	return nil
}

// Update apply terminal colors based on styles.
func (s *Styles) Update() {
	tview.Styles.PrimitiveBackgroundColor = s.BgColor()
	tview.Styles.ContrastBackgroundColor = s.BgColor()
	tview.Styles.MoreContrastBackgroundColor = s.BgColor()
	tview.Styles.PrimaryTextColor = s.FgColor()
	tview.Styles.BorderColor = s.K9s.Frame.Border.FgColor.Color()
	tview.Styles.FocusColor = s.K9s.Frame.Border.FocusColor.Color()
	tview.Styles.TitleColor = s.FgColor()
	tview.Styles.GraphicsColor = s.FgColor()
	tview.Styles.SecondaryTextColor = s.FgColor()
	tview.Styles.TertiaryTextColor = s.FgColor()
	tview.Styles.InverseTextColor = s.FgColor()
	tview.Styles.ContrastSecondaryTextColor = s.FgColor()

	s.fireStylesChanged()
}

// Dump for debug.
func (s *Styles) Dump() {
	bb, _ := yaml.Marshal(s)
	fmt.Println(string(bb))
}
