package main

import (
	"bytes"
	"fmt"
	"image"
	"image/draw"
	_ "image/png"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
)

func iconThemeDirs() []string {
	dirs := []string{}

	if home := os.Getenv("HOME"); home != "" {
		dirs = append(dirs, filepath.Join(home, ".local/share/icons"))
	}

	xdgDirs := os.Getenv("XDG_DATA_DIRS")
	if xdgDirs == "" {
		xdgDirs = "/usr/local/share:/usr/share"
	}
	for _, d := range filepath.SplitList(xdgDirs) {
		dirs = append(dirs, filepath.Join(d, "icons"))
	}

	return uniquePaths(dirs)
}

func uniquePaths(paths []string) []string {
	seen := make(map[string]bool)
	res := make([]string, 0, len(paths))
	for _, p := range paths {
		if !seen[p] {
			seen[p] = true
			res = append(res, p)
		}
	}
	return res
}

func preferredThemes() []string {
	theme := detectGtkTheme()
	if theme != "" {
		return []string{theme, "hicolor", "Adwaita", "Papirus", "Humanity", "breeze", "gnome"}
	}
	return []string{"hicolor", "Adwaita", "Papirus", "Humanity", "breeze", "gnome"}
}

func detectGtkTheme() string {
	data, err := os.ReadFile(filepath.Join(os.Getenv("HOME"), ".config/gtk-4.0/settings.ini"))
	if err != nil {
		data, err = os.ReadFile(filepath.Join(os.Getenv("HOME"), ".config/gtk-3.0/settings.ini"))
		if err != nil {
			return ""
		}
	}
	for _, line := range strings.Split(string(data), "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "gtk-icon-theme-name=") {
			return strings.TrimSpace(line[len("gtk-icon-theme-name="):])
		}
	}
	return ""
}

func sizeDirs(target int) []string {
	sizes := []int{}

	for _, base := range []int{16, 22, 24, 32, 48, 64, 72, 96, 128, 192, 256} {
		sizes = append(sizes, base)
	}

	sort.Slice(sizes, func(i, j int) bool {
		di := abs(sizes[i] - target)
		dj := abs(sizes[j] - target)
		if di != dj {
			return di < dj
		}
		return sizes[i] > sizes[j]
	})

	seen := make(map[int]bool)
	res := make([]string, 0, len(sizes))
	for _, s := range sizes {
		if seen[s] {
			continue
		}
		seen[s] = true
		res = append(res, fmt.Sprintf("%dx%d", s, s))
	}
	res = append(res, "scalable")
	return res
}

func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

func iconCategories() []string {
	return []string{"actions", "apps", "categories", "devices", "emblems", "mimetypes", "places", "status"}
}

func findSystemIcon(name string, targetSize int) (string, error) {
	themes := preferredThemes()
	dirs := iconThemeDirs()
	sDirs := sizeDirs(targetSize)
	cats := iconCategories()

	for _, base := range dirs {
		for _, theme := range themes {
			themeDir := filepath.Join(base, theme)
			if _, err := os.Stat(themeDir); os.IsNotExist(err) {
				continue
			}

			for _, sd := range sDirs {
				for _, cat := range cats {
					for _, ext := range []string{"png", "xpm"} {
						p := filepath.Join(themeDir, sd, cat, name+"."+ext)
						if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
							return p, nil
						}
					}
				}
			}

			for _, sd := range sDirs {
				for _, cat := range cats {
					p := filepath.Join(themeDir, sd, cat, name+".svg")
					if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
						return p, nil
					}
				}
			}
		}
	}

	return "", fmt.Errorf("system icon %q not found", name)
}

func svgToPNG(svgPath string, targetSize int, scale float64) (image.Image, error) {
	if scale <= 0 {
		scale = 0.55
	}

	renderSize := int(float64(targetSize) * scale)
	if renderSize > targetSize {
		renderSize = targetSize
	}
	if renderSize < 1 {
		renderSize = 1
	}

	cmd := exec.Command("rsvg-convert",
		"-w", strconv.Itoa(renderSize),
		"-h", strconv.Itoa(renderSize),
		"-f", "png",
		svgPath,
	)
	out, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("rsvg-convert: %w", err)
	}
	img, _, err := image.Decode(bytes.NewReader(out))
	if err != nil {
		return nil, fmt.Errorf("decode converted svg: %w", err)
	}

	if renderSize < targetSize {
		canvas := image.NewRGBA(image.Rect(0, 0, targetSize, targetSize))
		offX := (targetSize - renderSize) / 2
		offY := (targetSize - renderSize) / 2
		draw.Draw(canvas, image.Rect(offX, offY, offX+renderSize, offY+renderSize), img, image.Point{}, draw.Over)
		img = canvas
	}

	return img, nil
}

var iconSizeCache sync.Map

func loadSystemIcon(name string, targetSize int) (string, error) {
	type cacheKey struct {
		name string
		size int
	}
	key := cacheKey{name, targetSize}
	if cached, ok := iconSizeCache.Load(key); ok {
		return cached.(string), nil
	}
	path, err := findSystemIcon(name, targetSize)
	if err != nil {
		return "", err
	}
	iconSizeCache.Store(key, path)
	return path, nil
}

func isSystemIconRef(path string) bool {
	return strings.HasPrefix(path, "@")
}

func systemIconName(path string) string {
	return strings.TrimPrefix(path, "@")
}

func parseSizeDir(dirName string) (int, bool) {
	parts := strings.SplitN(dirName, "x", 2)
	if len(parts) != 2 {
		return 0, false
	}
	s, err := strconv.Atoi(parts[0])
	if err != nil {
		return 0, false
	}
	return s, true
}
