package app

import (
	"os"
	"path/filepath"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/internal/app"
	"fyne.io/fyne/v2/internal/async"
	"fyne.io/fyne/v2/internal/build"
	"fyne.io/fyne/v2/theme"
)

// SettingsSchema is used for loading and storing global settings
type SettingsSchema struct {
	// these items are used for global settings load
	ThemeName         string  `json:"theme"`
	Scale             float32 `json:"scale"`
	PrimaryColor      string  `json:"primary_color"`
	CloudName         string  `json:"cloud_name"`
	CloudConfig       string  `json:"cloud_config"`
	DisableAnimations bool    `json:"no_animations"`
}

// StoragePath returns the location of the settings storage
func (sc *SettingsSchema) StoragePath() string {
	return filepath.Join(app.RootConfigDir(), "settings.json")
}

// Declare conformity with Settings interface
var _ fyne.Settings = (*settings)(nil)

type settings struct {
	theme          fyne.Theme
	themeSpecified bool
	variant        fyne.ThemeVariant

	listeners       []func(fyne.Settings)
	changeListeners async.Map[chan fyne.Settings, bool]
	watcher         any // normally *fsnotify.Watcher or nil - avoid import in this file

	schema SettingsSchema
}

func (s *settings) BuildType() fyne.BuildType {
	return build.Mode
}

func (s *settings) PrimaryColor() string {
	return s.schema.PrimaryColor
}

// OverrideTheme allows the settings app to temporarily preview different theme details.
// Please make sure that you remember the original settings and call this again to revert the change.
//
// Deprecated: Use container.NewThemeOverride to change the appearance of part of your application.
func (s *settings) OverrideTheme(theme fyne.Theme, name string) {
	s.schema.PrimaryColor = name
	s.theme = theme
}

func (s *settings) Theme() fyne.Theme {
	if s == nil {
		fyne.LogError("Attempt to access current Fyne theme when no app is started", nil)
		return nil
	}
	return s.theme
}

func (s *settings) SetTheme(theme fyne.Theme) {
	s.themeSpecified = true
	s.applyTheme(theme, s.variant)
}

func (s *settings) ShowAnimations() bool {
	return !s.schema.DisableAnimations && !build.NoAnimations
}

func (s *settings) ThemeVariant() fyne.ThemeVariant {
	return s.variant
}

func (s *settings) applyTheme(theme fyne.Theme, variant fyne.ThemeVariant) {
	s.variant = variant
	s.theme = theme
	s.apply()
}

func (s *settings) Scale() float32 {
	if s.schema.Scale < 0.0 {
		return 1.0 // catching any really old data still using the `-1`  value for "auto" scale
	}
	return s.schema.Scale
}

func (s *settings) AddChangeListener(listener chan fyne.Settings) {
	s.changeListeners.Store(listener, true) // the boolean is just a dummy value here.
}

func (s *settings) AddListener(listener func(fyne.Settings)) {
	s.listeners = append(s.listeners, listener)
}

func (s *settings) apply() {
	s.changeListeners.Range(func(listener chan fyne.Settings, _ bool) bool {
		select {
		case listener <- s:
		default:
			l := listener
			go func() { l <- s }()
		}
		return true
	})

	for _, l := range s.listeners {
		l(s)
	}
}

func (s *settings) fileChanged() {
	s.load()
	s.apply()
}

func (s *settings) setupTheme() {
	name := s.schema.ThemeName
	if env := os.Getenv("FYNE_THEME"); env != "" {
		name = env
	}

	variant := app.DefaultVariant()
	effectiveTheme := s.theme
	if !s.themeSpecified {
		effectiveTheme = theme.DefaultTheme()
	}
	switch name {
	case "light":
		variant = theme.VariantLight
	case "dark":
		variant = theme.VariantDark
	}

	s.applyTheme(effectiveTheme, variant)
}

func loadSettings() *settings {
	s := &settings{}
	s.load()

	return s
}
