package main

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"unsafe"

	"github.com/jchv/go-webview2"
	"golang.org/x/sys/windows/registry"
)

//go:embed web/settings.html
var settingsHTML string

var (
	settingsMu   sync.Mutex
	settingsOpen bool
	settingsHwnd uintptr
)

const runKeyPath = `Software\Microsoft\Windows\CurrentVersion\Run`

func isAutoStartEnabled() bool {
	k, err := registry.OpenKey(registry.CURRENT_USER, runKeyPath, registry.QUERY_VALUE)
	if err != nil {
		return false
	}
	defer k.Close()
	_, _, err = k.GetStringValue("u-desktop")
	return err == nil
}

func setAutoStart(enabled bool) {
	exePath, _ := os.Executable()
	if enabled {
		k, _, err := registry.CreateKey(registry.CURRENT_USER, runKeyPath, registry.SET_VALUE)
		if err != nil {
			log.Println("注册表写入失败:", err)
			return
		}
		defer k.Close()
		k.SetStringValue("u-desktop", exePath)
		log.Println("已开启开机启动")
	} else {
		k, err := registry.OpenKey(registry.CURRENT_USER, runKeyPath, registry.SET_VALUE)
		if err != nil {
			return
		}
		defer k.Close()
		k.DeleteValue("u-desktop")
		log.Println("已关闭开机启动")
	}
}

func isSystemLightTheme() bool {
	k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
	if err != nil {
		return false
	}
	defer k.Close()
	v, _, err := k.GetIntegerValue("AppsUseLightTheme")
	if err != nil {
		return false
	}
	return v == 1
}

var themeNames = []struct {
	Name  ThemeName
	Label string
}{
	{ThemeAurora, "极光"},
	{ThemeStar, "星空"},
	{ThemeGradient, "渐变"},
	{ThemeParticle, "粒子"},
	{ThemeFractal, "极光流体"},
	{ThemeText, "文字"},
	{ThemeAudioViz, "音频可视化"},
}

func refreshVisibleCards(cfg, oldCfg *Config) {
	if oldCfg.HideWeather && !cfg.HideWeather {
		go fetchAndPushWeather(getCurrentCity())
	}
	if oldCfg.HideZodiac && !cfg.HideZodiac {
		triggerHoroscopeRefresh(cfg.Zodiac)
	}
	if oldCfg.HideKnowledge && !cfg.HideKnowledge {
		if cfg.KnowledgeKeyword != "" {
			triggerKnowledgeRefresh()
		} else {
			pushKnowledgePlaceholder()
		}
	}
	if oldCfg.HideAINews && !cfg.HideAINews {
		triggerAINewsRefresh()
	}
}

func openSettingsWindow() {
	settingsMu.Lock()
	if settingsOpen {
		settingsMu.Unlock()
		if settingsHwnd != 0 {
			procShowWindow.Call(settingsHwnd, 9)
			procGetForegroundWindow.Call(settingsHwnd)
		}
		return
	}
	settingsOpen = true
	settingsMu.Unlock()

	go func() {
		runtime.LockOSThread()
		defer func() {
			settingsMu.Lock()
			settingsOpen = false
			settingsHwnd = 0
			settingsMu.Unlock()
			runtime.UnlockOSThread()
		}()

		dataDir := filepath.Join(configDir(), "settings-data")
		os.MkdirAll(dataDir, 0755)

		w := webview2.NewWithOptions(webview2.WebViewOptions{
			AutoFocus: true,
			DataPath:  dataDir,
			WindowOptions: webview2.WindowOptions{
				Title:  "桌面设置",
				Width:  760,
				Height: 1350,
			},
		})
		if w == nil {
			log.Println("设置窗口: 创建失败")
			return
		}

		w.Bind("loadAllSettings", func() string {
			cfg := loadConfig()

			seen := map[string]bool{}
			var provinces []string
			citiesByProv := map[string][]map[string]string{}
			for _, c := range cities {
				if !seen[c.Adm1] {
					seen[c.Adm1] = true
					provinces = append(provinces, c.Adm1)
				}
				citiesByProv[c.Adm1] = append(citiesByProv[c.Adm1], map[string]string{
					"id": c.ID, "name": c.Name,
				})
			}

			type themeJSON struct {
				Value string `json:"value"`
				Label string `json:"label"`
			}
			var tl []themeJSON
			for _, t := range themeNames {
				tl = append(tl, themeJSON{Value: string(t.Name), Label: t.Label})
			}

			data, _ := json.Marshal(map[string]interface{}{
				"lightTheme":       isSystemLightTheme(),
				"wallpaper":        !cfg.HideWallpaper,
				"time":             !cfg.HideTime,
				"weather":          !cfg.HideWeather,
				"zodiacCard":       !cfg.HideZodiac,
				"knowledgeCard":    !cfg.HideKnowledge,
				"layout":           string(cfg.Layout),
				"zodiac":           cfg.Zodiac,
				"city":             cfg.City,
				"provinces":        provinces,
				"citiesByProv":     citiesByProv,
				"wallpaperType":    string(cfg.WallpaperType),
				"theme":            string(cfg.Theme),
				"themes":           tl,
				"color1":           cfg.Color1,
				"color2":           cfg.Color2,
				"colorGradient":    cfg.ColorGradient,
				"savedColors":      cfg.SavedColors,
				"bingAutoRefresh":  cfg.BingAutoRefresh,
				"knowledgeKeyword": cfg.KnowledgeKeyword,
				"knowledgePrompt":  cfg.KnowledgePrompt,
				"wallpaperText":    cfg.WallpaperText,
				"imagePath":        cfg.ImagePath,
				"showSeconds":      cfg.ShowSeconds,
				"ainewsCard":       !cfg.HideAINews,
				"photoDir":         cfg.PhotoDir,
				"photoInterval":    cfg.PhotoInterval,
				"photoCard":        !cfg.HidePhoto,
				"photoFrameMode":   cfg.PhotoFrameMode,
				"autoStart":        isAutoStartEnabled(),
				"audioVizStyle":    cfg.AudioVizStyle,
				"audioSensitivity": cfg.AudioSensitivity,
				"audioSmoothing":   cfg.AudioSmoothing,
				"audioColorScheme": cfg.AudioColorScheme,
			})
			return string(data)
		})

		w.Bind("saveToggles", func(jsonStr string) string {
			var data map[string]bool
			if json.Unmarshal([]byte(jsonStr), &data) != nil {
				return ""
			}
			cfg := loadConfig()
			oldCfg := *cfg
			if v, ok := data["wallpaper"]; ok {
				cfg.HideWallpaper = !v
				evalJS(fmt.Sprintf("if(window.setWallpaperVisible) setWallpaperVisible(%v)", v))
			}
			if v, ok := data["time"]; ok {
				cfg.HideTime = !v
				evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('time',%v)", v))
			}
			if v, ok := data["weather"]; ok {
				cfg.HideWeather = !v
				evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('weather',%v)", v))
			}
			if v, ok := data["zodiacCard"]; ok {
				cfg.HideZodiac = !v
				evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('zodiac',%v)", v))
			}
			if v, ok := data["knowledgeCard"]; ok {
				cfg.HideKnowledge = !v
				evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('knowledge',%v)", v))
			}
			if v, ok := data["ainewsCard"]; ok {
				cfg.HideAINews = !v
				evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('ainews',%v)", v))
			}
			if v, ok := data["showSeconds"]; ok {
				cfg.ShowSeconds = v
				evalJS(fmt.Sprintf("if(window.setShowSeconds) setShowSeconds(%v)", v))
			}
			if v, ok := data["autoStart"]; ok {
				setAutoStart(v)
			}
			if v, ok := data["photoFrameMode"]; ok {
				cfg.PhotoFrameMode = v
				evalJS(fmt.Sprintf("if(window.setPhotoFrameMode) setPhotoFrameMode(%v)", v))
				if v {
					cfg.HideWallpaper = true
					cfg.HideTime = true
					cfg.HideWeather = true
					cfg.HideZodiac = true
					cfg.HideAINews = true
					cfg.HideKnowledge = true
					evalJS(`if(window.setWallpaperVisible) setWallpaperVisible(false)`)
					for _, card := range []string{"time", "weather", "zodiac", "ainews", "knowledge"} {
						evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('%s',false)", card))
					}
				} else {
					reloadWallpaper()
				}
			}
			if v, ok := data["photoCard"]; ok {
				cfg.HidePhoto = !v
				evalJS(fmt.Sprintf("if(window.setCardVisible) setCardVisible('photo',%v)", v))
				if cfg.PhotoDir != "" {
					if v {
						restartPhotoLoop()
					} else {
						stopPhotoLoop()
					}
				}
			}
			saveConfig(cfg)
			refreshVisibleCards(cfg, &oldCfg)
			return ""
		})

		w.Bind("saveLayout", func(layout string) string {
			cfg := loadConfig()
			cfg.Layout = Layout(layout)
			saveConfig(cfg)
			reloadWallpaper()
			return ""
		})

		w.Bind("saveZodiac", func(zodiac string) string {
			cfg := loadConfig()
			cfg.Zodiac = zodiac
			saveConfig(cfg)
			evalJS(fmt.Sprintf(`window.userZodiac = %q; if(window.updateTime) updateTime();`, zodiac))
			triggerHoroscopeRefresh(zodiac)
			return ""
		})

		w.Bind("saveCity", func(cityID string) string {
			cfg := loadConfig()
			cfg.City = cityID
			saveConfig(cfg)
			for _, c := range cities {
				if c.ID == cityID {
					go fetchAndPushWeather(c)
					break
				}
			}
			return ""
		})

		w.Bind("saveWallpaperType", func(wpType, theme string) string {
			cfg := loadConfig()
			cfg.WallpaperType = WallpaperType(wpType)
			cfg.Theme = ThemeName(theme)
			saveConfig(cfg)
			reloadWallpaper()
			return ""
		})

		w.Bind("pickLocalImage", func() string {
			hwnd := uintptr(w.Window())
			path := openFileDialog(hwnd)
			if path == "" {
				return ""
			}
			cfg := loadConfig()
			cfg.WallpaperType = WPImage
			cfg.ImagePath = path
			saveConfig(cfg)
			reloadWallpaper()
			return path
		})

		w.Bind("enableBing", func() string {
			cfg := loadConfig()
			cfg.WallpaperType = WPBing
			saveConfig(cfg)
			reloadWallpaper()
			go fetchBingHistory()
			return ""
		})

		w.Bind("bingPrev", func() string {
			bingPrev()
			return bingCurrentState()
		})
		w.Bind("bingNext", func() string {
			bingNext()
			return bingCurrentState()
		})
		w.Bind("bingToggleFavorite", func() string {
			bingToggleFavorite()
			return bingCurrentState()
		})
		w.Bind("getBingInfo", func() string {
			return bingCurrentState()
		})
		w.Bind("saveBingAutoRefresh", func(val bool) string {
			cfg := loadConfig()
			cfg.BingAutoRefresh = val
			saveConfig(cfg)
			return ""
		})
		w.Bind("getBingFavorites", func() string {
			return bingFavoritesJSON()
		})
		w.Bind("bingSetByIdx", func(idx int) string {
			return bingSetByIdx(idx)
		})
		w.Bind("bingThumbDataURI", func(filename string) string {
			return bingThumbDataURI(filename)
		})

		w.Bind("pickSolidColor", func() string {
			hwnd := uintptr(w.Window())
			color := colorPickerDialog(hwnd, "")
			if color == "" {
				return ""
			}
			cfg := loadConfig()
			cfg.WallpaperType = WPColor
			cfg.Color1 = color
			cfg.ColorGradient = false
			saveConfig(cfg)
			reloadWallpaper()
			return color
		})

		w.Bind("pickGradientColor", func() string {
			hwnd := uintptr(w.Window())
			cfg := loadConfig()
			c1 := colorPickerDialog(hwnd, cfg.Color1)
			if c1 == "" {
				return ""
			}
			c2 := colorPickerDialog(hwnd, cfg.Color2)
			if c2 == "" {
				c2 = "#16213e"
			}
			cfg.WallpaperType = WPColor
			cfg.Color1 = c1
			cfg.Color2 = c2
			cfg.ColorGradient = true
			saveConfig(cfg)
			reloadWallpaper()
			return c1 + "," + c2
		})

		w.Bind("addSavedColor", func(c1, c2 string, gradient bool) string {
			cfg := loadConfig()
			cfg.SavedColors = append(cfg.SavedColors, SavedColor{Color1: c1, Color2: c2, Gradient: gradient})
			saveConfig(cfg)
			return ""
		})

		w.Bind("removeSavedColor", func(idx int) string {
			cfg := loadConfig()
			if idx >= 0 && idx < len(cfg.SavedColors) {
				cfg.SavedColors = append(cfg.SavedColors[:idx], cfg.SavedColors[idx+1:]...)
				saveConfig(cfg)
			}
			return ""
		})

		w.Bind("applySavedColor", func(idx int) string {
			cfg := loadConfig()
			if idx >= 0 && idx < len(cfg.SavedColors) {
				sc := cfg.SavedColors[idx]
				cfg.WallpaperType = WPColor
				cfg.Color1 = sc.Color1
				cfg.Color2 = sc.Color2
				cfg.ColorGradient = sc.Gradient
				saveConfig(cfg)
				reloadWallpaper()
			}
			return ""
		})

		w.Bind("saveWallpaperText", func(text string) string {
			cfg := loadConfig()
			cfg.WallpaperText = text
			saveConfig(cfg)
			reloadWallpaper()
			return ""
		})

		w.Bind("saveKnowledgeKeyword", func(keyword string) string {
			cfg := loadConfig()
			cfg.KnowledgeKeyword = keyword
			saveConfig(cfg)
			if keyword != "" {
				triggerKnowledgeRefresh()
			} else {
				pushKnowledgePlaceholder()
			}
			return ""
		})
		w.Bind("saveKnowledgePrompt", func(prompt string) string {
			cfg := loadConfig()
			cfg.KnowledgePrompt = prompt
			saveConfig(cfg)
			return ""
		})

		w.Bind("pickPhotoDir", func() string {
			hwnd := uintptr(w.Window())
			dir := browseForFolderDialog(hwnd)
			if dir == "" {
				return ""
			}
			cfg := loadConfig()
			cfg.PhotoDir = dir
			if cfg.PhotoInterval <= 0 {
				cfg.PhotoInterval = 15
			}
			saveConfig(cfg)
			restartPhotoLoop()
			return dir
		})

		w.Bind("clearPhotoDir", func() string {
			cfg := loadConfig()
			cfg.PhotoDir = ""
			saveConfig(cfg)
			restartPhotoLoop()
			return ""
		})

		w.Bind("savePhotoInterval", func(val int) string {
			cfg := loadConfig()
			cfg.PhotoInterval = val
			saveConfig(cfg)
			if cfg.PhotoDir != "" {
				restartPhotoLoop()
			}
			return ""
		})

		w.Bind("saveAudioVizStyle", func(style string) string {
			cfg := loadConfig()
			cfg.AudioVizStyle = style
			saveConfig(cfg)
			if cfg.Theme == ThemeAudioViz {
				evalJS(fmt.Sprintf(`window.__audioVizStyle=%q; if(window.setAudioVizStyle) setAudioVizStyle(%q)`, style, style))
			}
			return ""
		})

		w.Bind("saveAudioSensitivity", func(val float64) string {
			cfg := loadConfig()
			cfg.AudioSensitivity = val
			saveConfig(cfg)
			if cfg.Theme == ThemeAudioViz {
				evalJS(fmt.Sprintf(`window.__audioVizSensitivity=%f; if(window.setAudioSensitivity) setAudioSensitivity(%f)`, val, val))
			}
			return ""
		})

		w.Bind("saveAudioSmoothing", func(val float64) string {
			cfg := loadConfig()
			cfg.AudioSmoothing = val
			saveConfig(cfg)
			return ""
		})

		w.Bind("saveAudioColorScheme", func(scheme string) string {
			cfg := loadConfig()
			cfg.AudioColorScheme = scheme
			saveConfig(cfg)
			if cfg.Theme == ThemeAudioViz {
				evalJS(fmt.Sprintf(`window.__audioVizColors=%q; if(window.setAudioColorScheme) setAudioColorScheme(%q)`, scheme, scheme))
			}
			return ""
		})

		w.SetHtml(settingsHTML)

		hwnd := uintptr(w.Window())
		// disable resize + hide from taskbar
			style, _, _ := procGetWindowLongPtrW.Call(hwnd, gwlStyle)
			procSetWindowLongPtrW.Call(hwnd, gwlStyle, style & ^uintptr(wsSizebox|wsMaxbox))
			procSetWindowLongPtrW.Call(hwnd, gwlExStyle, wsExToolwindow)

		// resizeToFit: JS measures content, Go adjusts window frame
		w.Bind("resizeToFit", func(contentW, contentH int) string {
			type rect struct{ Left, Top, Right, Bottom int32 }
			var wr, cr rect
			procGetWindowRect.Call(hwnd, uintptr(unsafe.Pointer(&wr)))
			procGetClientRect.Call(hwnd, uintptr(unsafe.Pointer(&cr)))
			frameW := int(wr.Right-wr.Left) - int(cr.Right-cr.Left)
			frameH := int(wr.Bottom-wr.Top) - int(cr.Bottom-cr.Top)
			winW := contentW + frameW
			winH := contentH + frameH
			screenW, screenH := getScreenSize()
			if winH > int(screenH)-60 {
				winH = int(screenH) - 60
			}
			if winW > int(screenW)-60 {
				winW = int(screenW) - 60
			}
			x := (int(screenW) - winW) / 2
			y := (int(screenH) - winH) / 2
			procMoveWindow.Call(hwnd, uintptr(x), uintptr(y), uintptr(winW), uintptr(winH), 1)
			return ""
		})

		settingsMu.Lock()
		settingsHwnd = hwnd
		settingsMu.Unlock()

		log.Println("设置窗口已打开")
		w.Run()
		log.Println("设置窗口已关闭")
	}()
}
