package theme

import (
	"encoding/hex"
	"errors"
	"fmt"
	"image/color"
	"io"
	"os"
	"slices"
	"strings"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/theme"
	"github.com/pelletier/go-toml/v2"
)

var validThemeVersions = []string{"0.1", "0.2"}

type ThemeFileHeader struct {
	Name          string
	Version       string
	SupportsDark  bool
	SupportsLight bool
}

type ThemeFile struct {
	SupersonicTheme ThemeFileHeader

	DarkColors  ThemeColors
	LightColors ThemeColors
}

type ThemeColors struct {
	// Supersonic-specific colors

	// Background color of the header row in list views.
	ListHeader string

	// Background color for each page. The main color in the app.
	PageBackground string

	// Color of the background rectangle around the Album, Artist, Playlist page headers.
	//
	// Since: Supersonic theme file version 0.2
	PageHeader string

	// Fyne colors

	Background string

	Button string

	DisabledButton string

	Disabled string

	Error string

	Focus string

	Foreground string

	Hover string

	// since Supersonic theme file version 0.2 (Fyne version 2.4)
	Hyperlink string

	InputBackground string

	InputBorder string

	MenuBackground string

	OverlayBackground string

	Placeholder string

	Pressed string

	Primary string

	ScrollBar string

	Selection string

	Separator string

	Shadow string

	Success string

	Warning string
}

func ReadThemeFile(filePath string) (*ThemeFile, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	return DecodeThemeFile(f)
}

func DecodeThemeFile(reader io.Reader) (*ThemeFile, error) {
	theme := &ThemeFile{}
	if err := toml.NewDecoder(reader).Decode(theme); err != nil {
		return nil, err
	}

	if theme.SupersonicTheme.Name == "" || !slices.Contains(validThemeVersions, theme.SupersonicTheme.Version) {
		return nil, errors.New("invalid theme file name or version")
	}
	if !(theme.SupersonicTheme.SupportsDark || theme.SupersonicTheme.SupportsLight) {
		return nil, errors.New("invalid theme file: must support one or both of light/dark")
	}
	updateThemeToLatestVersion(theme)

	return theme, nil
}

func (t *ThemeFile) SupportsVariant(v fyne.ThemeVariant) bool {
	if v == theme.VariantDark {
		return t.SupersonicTheme.SupportsDark
	}
	return t.SupersonicTheme.SupportsLight
}

// Parses a CSS-style #RRGGBB or #RRGGBBAA string
func ColorStringToColor(colorStr string) (color.Color, error) {
	if !strings.HasPrefix(colorStr, "#") || !slices.Contains([]int{7, 9}, len(colorStr)) {
		return color.Black, errors.New("invalid color string")
	}
	colorBytes := make([]byte, 4)
	n, err := hex.Decode(colorBytes, []byte(colorStr[1:]))
	if err != nil {
		return color.Black, fmt.Errorf("invalid color string: %s", err.Error())
	}
	if n == 3 {
		colorBytes[3] = 255 // opaque alpha
	}
	return color.RGBA{R: colorBytes[0], G: colorBytes[1], B: colorBytes[2], A: colorBytes[3]}, nil
}

func updateThemeToLatestVersion(themeFile *ThemeFile) {
	switch themeFile.SupersonicTheme.Version {
	case "0.1":
		// in version 0.1, hyperlinks were drawn with the Primary color
		themeFile.DarkColors.Hyperlink = themeFile.DarkColors.Primary
		themeFile.LightColors.Hyperlink = themeFile.LightColors.Primary
	}
}
