package theme

import (
	"embed"
	"encoding/json"
	"fmt"
	"image/color"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/charmbracelet/lipgloss/v2"
	"github.com/charmbracelet/lipgloss/v2/compat"
)

//go:embed themes/*.json
var themesFS embed.FS

type JSONTheme struct {
	Defs  map[string]any `json:"defs,omitempty"`
	Theme map[string]any `json:"theme"`
}

type LoadedTheme struct {
	BaseTheme
	name string
}

type colorRef struct {
	value    any
	resolved bool
}

func LoadThemesFromJSON() error {
	entries, err := themesFS.ReadDir("themes")
	if err != nil {
		return fmt.Errorf("failed to read themes directory: %w", err)
	}

	for _, entry := range entries {
		if !strings.HasSuffix(entry.Name(), ".json") {
			continue
		}
		themeName := strings.TrimSuffix(entry.Name(), ".json")
		data, err := themesFS.ReadFile(path.Join("themes", entry.Name()))
		if err != nil {
			return fmt.Errorf("failed to read theme file %s: %w", entry.Name(), err)
		}
		theme, err := parseJSONTheme(themeName, data)
		if err != nil {
			return fmt.Errorf("failed to parse theme %s: %w", themeName, err)
		}
		RegisterTheme(themeName, theme)
	}

	return nil
}

// LoadThemesFromDirectories loads themes from user directories in the correct override order.
// The hierarchy is (from lowest to highest priority):
// 1. Built-in themes (embedded)
// 2. USER_CONFIG/opencode/themes/*.json
// 3. PROJECT_ROOT/.opencode/themes/*.json
// 4. CWD/.opencode/themes/*.json
func LoadThemesFromDirectories(userConfig, projectRoot, cwd string) error {
	if err := LoadThemesFromJSON(); err != nil {
		return fmt.Errorf("failed to load built-in themes: %w", err)
	}

	dirs := []string{
		filepath.Join(userConfig, "themes"),
		filepath.Join(projectRoot, ".opencode", "themes"),
	}
	if cwd != projectRoot {
		dirs = append(dirs, filepath.Join(cwd, ".opencode", "themes"))
	}

	for _, dir := range dirs {
		if err := loadThemesFromDirectory(dir); err != nil {
			fmt.Printf("Warning: Failed to load themes from %s: %v\n", dir, err)
		}
	}

	return nil
}

func loadThemesFromDirectory(dir string) error {
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		return nil // Directory doesn't exist, which is fine
	}

	entries, err := os.ReadDir(dir)
	if err != nil {
		return fmt.Errorf("failed to read directory: %w", err)
	}

	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
			continue
		}

		themeName := strings.TrimSuffix(entry.Name(), ".json")
		filePath := filepath.Join(dir, entry.Name())

		data, err := os.ReadFile(filePath)
		if err != nil {
			fmt.Printf("Warning: Failed to read theme file %s: %v\n", filePath, err)
			continue
		}

		theme, err := parseJSONTheme(themeName, data)
		if err != nil {
			fmt.Printf("Warning: Failed to parse theme %s: %v\n", filePath, err)
			continue
		}

		RegisterTheme(themeName, theme)
	}

	return nil
}

func parseJSONTheme(name string, data []byte) (Theme, error) {
	var jsonTheme JSONTheme
	if err := json.Unmarshal(data, &jsonTheme); err != nil {
		return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
	}
	theme := &LoadedTheme{
		name: name,
	}
	colorMap := make(map[string]*colorRef)
	for key, value := range jsonTheme.Defs {
		colorMap[key] = &colorRef{value: value, resolved: false}
	}
	for key, value := range jsonTheme.Theme {
		colorMap[key] = &colorRef{value: value, resolved: false}
	}
	resolver := &colorResolver{
		colors:  colorMap,
		visited: make(map[string]bool),
	}
	for key, value := range jsonTheme.Theme {
		resolved, err := resolver.resolveColor(key, value)
		if err != nil {
			return nil, fmt.Errorf("failed to resolve color %s: %w", key, err)
		}
		adaptiveColor, err := parseResolvedColor(resolved)
		if err != nil {
			return nil, fmt.Errorf("failed to parse color %s: %w", key, err)
		}
		if err := setThemeColor(theme, key, adaptiveColor); err != nil {
			return nil, fmt.Errorf("failed to set color %s: %w", key, err)
		}
	}

	return theme, nil
}

type colorResolver struct {
	colors  map[string]*colorRef
	visited map[string]bool
}

func (r *colorResolver) resolveColor(key string, value any) (any, error) {
	if r.visited[key] {
		return nil, fmt.Errorf("circular reference detected for color %s", key)
	}
	r.visited[key] = true
	defer func() { r.visited[key] = false }()

	switch v := value.(type) {
	case string:
		if strings.HasPrefix(v, "#") || v == "none" {
			return v, nil
		}
		return r.resolveReference(v)
	case float64:
		return v, nil
	case map[string]any:
		resolved := make(map[string]any)

		if dark, ok := v["dark"]; ok {
			resolvedDark, err := r.resolveColorValue(dark)
			if err != nil {
				return nil, fmt.Errorf("failed to resolve dark variant: %w", err)
			}
			resolved["dark"] = resolvedDark
		}

		if light, ok := v["light"]; ok {
			resolvedLight, err := r.resolveColorValue(light)
			if err != nil {
				return nil, fmt.Errorf("failed to resolve light variant: %w", err)
			}
			resolved["light"] = resolvedLight
		}

		return resolved, nil
	default:
		return nil, fmt.Errorf("invalid color value type: %T", value)
	}
}

func (r *colorResolver) resolveColorValue(value any) (any, error) {
	switch v := value.(type) {
	case string:
		if strings.HasPrefix(v, "#") || v == "none" {
			return v, nil
		}
		return r.resolveReference(v)
	case float64:
		return v, nil
	default:
		return nil, fmt.Errorf("invalid color value type: %T", value)
	}
}

func (r *colorResolver) resolveReference(ref string) (any, error) {
	colorRef, exists := r.colors[ref]
	if !exists {
		return nil, fmt.Errorf("color reference '%s' not found", ref)
	}

	if colorRef.resolved {
		return colorRef.value, nil
	}

	resolved, err := r.resolveColor(ref, colorRef.value)
	if err != nil {
		return nil, err
	}

	colorRef.value = resolved
	colorRef.resolved = true

	return resolved, nil
}

func parseResolvedColor(value any) (compat.AdaptiveColor, error) {
	switch v := value.(type) {
	case string:
		if v == "none" {
			return compat.AdaptiveColor{
				Dark:  lipgloss.NoColor{},
				Light: lipgloss.NoColor{},
			}, nil
		}
		return compat.AdaptiveColor{
			Dark:  lipgloss.Color(v),
			Light: lipgloss.Color(v),
		}, nil
	case float64:
		colorStr := fmt.Sprintf("%d", int(v))
		return compat.AdaptiveColor{
			Dark:  lipgloss.Color(colorStr),
			Light: lipgloss.Color(colorStr),
		}, nil
	case map[string]any:
		dark, darkOk := v["dark"]
		light, lightOk := v["light"]

		if !darkOk || !lightOk {
			return compat.AdaptiveColor{}, fmt.Errorf("color object must have both 'dark' and 'light' keys")
		}
		darkColor, err := parseColorValue(dark)
		if err != nil {
			return compat.AdaptiveColor{}, fmt.Errorf("failed to parse dark color: %w", err)
		}
		lightColor, err := parseColorValue(light)
		if err != nil {
			return compat.AdaptiveColor{}, fmt.Errorf("failed to parse light color: %w", err)
		}
		return compat.AdaptiveColor{
			Dark:  darkColor,
			Light: lightColor,
		}, nil
	default:
		return compat.AdaptiveColor{}, fmt.Errorf("invalid resolved color type: %T", value)
	}
}

func parseColorValue(value any) (color.Color, error) {
	switch v := value.(type) {
	case string:
		if v == "none" {
			return lipgloss.NoColor{}, nil
		}
		return lipgloss.Color(v), nil
	case float64:
		return lipgloss.Color(fmt.Sprintf("%d", int(v))), nil
	default:
		return nil, fmt.Errorf("invalid color value type: %T", value)
	}
}

func setThemeColor(theme *LoadedTheme, key string, color compat.AdaptiveColor) error {
	switch key {
	case "primary":
		theme.PrimaryColor = color
	case "secondary":
		theme.SecondaryColor = color
	case "accent":
		theme.AccentColor = color
	case "error":
		theme.ErrorColor = color
	case "warning":
		theme.WarningColor = color
	case "success":
		theme.SuccessColor = color
	case "info":
		theme.InfoColor = color
	case "text":
		theme.TextColor = color
	case "textMuted":
		theme.TextMutedColor = color
	case "background":
		theme.BackgroundColor = color
	case "backgroundPanel":
		theme.BackgroundPanelColor = color
	case "backgroundElement":
		theme.BackgroundElementColor = color
	case "border":
		theme.BorderColor = color
	case "borderActive":
		theme.BorderActiveColor = color
	case "borderSubtle":
		theme.BorderSubtleColor = color
	case "diffAdded":
		theme.DiffAddedColor = color
	case "diffRemoved":
		theme.DiffRemovedColor = color
	case "diffContext":
		theme.DiffContextColor = color
	case "diffHunkHeader":
		theme.DiffHunkHeaderColor = color
	case "diffHighlightAdded":
		theme.DiffHighlightAddedColor = color
	case "diffHighlightRemoved":
		theme.DiffHighlightRemovedColor = color
	case "diffAddedBg":
		theme.DiffAddedBgColor = color
	case "diffRemovedBg":
		theme.DiffRemovedBgColor = color
	case "diffContextBg":
		theme.DiffContextBgColor = color
	case "diffLineNumber":
		theme.DiffLineNumberColor = color
	case "diffAddedLineNumberBg":
		theme.DiffAddedLineNumberBgColor = color
	case "diffRemovedLineNumberBg":
		theme.DiffRemovedLineNumberBgColor = color
	case "markdownText":
		theme.MarkdownTextColor = color
	case "markdownHeading":
		theme.MarkdownHeadingColor = color
	case "markdownLink":
		theme.MarkdownLinkColor = color
	case "markdownLinkText":
		theme.MarkdownLinkTextColor = color
	case "markdownCode":
		theme.MarkdownCodeColor = color
	case "markdownBlockQuote":
		theme.MarkdownBlockQuoteColor = color
	case "markdownEmph":
		theme.MarkdownEmphColor = color
	case "markdownStrong":
		theme.MarkdownStrongColor = color
	case "markdownHorizontalRule":
		theme.MarkdownHorizontalRuleColor = color
	case "markdownListItem":
		theme.MarkdownListItemColor = color
	case "markdownListEnumeration":
		theme.MarkdownListEnumerationColor = color
	case "markdownImage":
		theme.MarkdownImageColor = color
	case "markdownImageText":
		theme.MarkdownImageTextColor = color
	case "markdownCodeBlock":
		theme.MarkdownCodeBlockColor = color
	case "syntaxComment":
		theme.SyntaxCommentColor = color
	case "syntaxKeyword":
		theme.SyntaxKeywordColor = color
	case "syntaxFunction":
		theme.SyntaxFunctionColor = color
	case "syntaxVariable":
		theme.SyntaxVariableColor = color
	case "syntaxString":
		theme.SyntaxStringColor = color
	case "syntaxNumber":
		theme.SyntaxNumberColor = color
	case "syntaxType":
		theme.SyntaxTypeColor = color
	case "syntaxOperator":
		theme.SyntaxOperatorColor = color
	case "syntaxPunctuation":
		theme.SyntaxPunctuationColor = color
	default:
		// Ignore unknown keys for forward compatibility
		return nil
	}
	return nil
}
