package executor

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"regexp"
	"strings"

	"webby-builder/internal/models"
)

// WordPress compatibility targets — keep in sync with the literals in
// prompts/system-wordpress.md (asserted by TestWordPressPromptVersionsMatchConstants).
const (
	WPTargetVersion = "6.8" // Tested up to + $schema pin
	WPMinVersion    = "6.7" // Requires at least
)

func wpSchemaURL() string {
	return "https://schemas.wp.org/wp/" + WPTargetVersion + "/theme.json"
}

// applyWordPressDesign merges the resolved design system into theme.json (the
// WordPress mirror of ApplyDesignOverlay) and captures the playbook for prompt
// injection. Two-phase merge: handbook-grade enrichment defaults are merged
// UNDER the template's theme.json (template curation wins), then the design
// system's brand values (palette, fonts, $schema) are merged OVER everything.
// Non-blocking: a write failure is logged but does not abort.
func (e *Executor) applyWordPressDesign() {
	if e.designSystem == nil {
		return
	}
	if overlay, err := BuildThemeJSONMap(e.designSystem); err == nil && overlay != nil {
		// Bundle the design fonts locally (woff2 + fontFace) so installed
		// themes render the real fonts with no remote requests. Non-blocking.
		cacheDir := filepath.Join(filepath.Dir(filepath.Dir(e.file.workspacePath)), "fonts-cache")
		fonts, fErr := bundleThemeFonts(e.file.workspacePath, e.designSystem.Fonts, cacheDir)
		if fErr != nil && e.logger != nil {
			e.logger.WithField("error", fErr.Error()).Warn("Font bundling incomplete (non-blocking)")
		}
		attachFontFaces(overlay, fonts)

		dest := filepath.Join(e.file.workspacePath, "theme.json")
		existing, rErr := ReadJSONFile(dest)
		if rErr != nil {
			// Corrupt/unreadable template theme.json — start from the overlay alone.
			existing = map[string]interface{}{}
		}
		// Phase 1: handbook defaults under the template (template curation wins).
		merged := DeepMergeJSON(wpEnrichmentDefaults(), existing)
		// Phase 2: design-system brand over everything (palette/fonts/schema win).
		merged = DeepMergeJSON(merged, overlay)
		if werr := WriteJSONFileAtomic(dest, merged); werr != nil && e.logger != nil {
			e.logger.WithField("error", werr.Error()).Warn("Failed to write theme.json (non-blocking)")
		}
	}
	writeStyleVariations(e.file.workspacePath, e.designSystem)
	e.designPlaybook = e.designSystem.Playbook
}

// ApplyEagerDesignOverlay applies the configured design system to an eagerly
// extracted template, dispatching per build target: theme.json merge for
// WordPress block themes, src/index.css + fonts + components for websites.
// Returns the design playbook for prompt injection. No-op when no design
// system is set.
func (e *Executor) ApplyEagerDesignOverlay() (string, error) {
	if e.designSystem == nil {
		return "", nil
	}
	if !e.target().NeedsNodeBuild() {
		e.applyWordPressDesign()
		return e.designPlaybook, nil
	}
	pb, err := ApplyDesignOverlay(e.file.workspacePath, e.designSystem)
	if err == nil {
		e.designPlaybook = pb
	}
	return pb, err
}

// hslTokenToCSS converts an "H S% L%" design-system color token into a CSS
// color string for theme.json (which prefers concrete CSS color values).
func hslTokenToCSS(v string) string {
	v = strings.TrimSpace(v)
	if v == "" {
		return ""
	}
	if parts := strings.Fields(v); len(parts) == 3 {
		return fmt.Sprintf("hsl(%s %s %s)", parts[0], parts[1], parts[2])
	}
	return v
}

// capitalize (first-rune upper-case) is defined in aeo.go and reused here.

var fontFamilyRe = regexp.MustCompile(`family=([A-Za-z0-9+]+)`)

// extractFontFamilies pulls human font names out of Google Fonts <link>s.
func extractFontFamilies(fontsHTML string) []string {
	var out []string
	seen := map[string]bool{}
	for _, m := range fontFamilyRe.FindAllStringSubmatch(fontsHTML, -1) {
		name := strings.ReplaceAll(m[1], "+", " ")
		if !seen[name] {
			seen[name] = true
			out = append(out, name)
		}
	}
	return out
}

// BuildThemeJSON translates a resolved design system into an FSE theme.json
// (schema v3): the accent palette becomes settings.color.palette and the fonts
// become settings.typography.fontFamilies. This is the WordPress-target mirror
// of ApplyDesignOverlay (which writes src/index.css for the React target).
// Returns "" for a nil system (Automatic with nothing resolved yet).
func BuildThemeJSON(ds *models.DesignSystem) (string, error) {
	theme, err := BuildThemeJSONMap(ds)
	if err != nil || theme == nil {
		return "", err
	}

	b, err := json.MarshalIndent(theme, "", "\t")
	if err != nil {
		return "", err
	}
	return string(b), nil
}

// BuildThemeJSONMap builds the theme.json overlay as a map so callers can
// deep-merge it into a template's existing theme.json without losing the
// template's curated settings (layout, appearanceTools, styles, …). Only
// non-empty sections are included, so a design system without fonts never
// blanks a template's font families. Returns nil when the system is nil or
// contributes nothing.
func BuildThemeJSONMap(ds *models.DesignSystem) (map[string]interface{}, error) {
	if ds == nil {
		return nil, nil
	}

	// Curated palette from the design system's base tokens overlaid with the
	// chosen accent (base/contrast first — the de-facto core-theme slugs).
	palette := buildWPPalette(parseCSSVars(ds.Tokens, ":root"), ds.AccentLight)

	fontFamilies := make([]map[string]any, 0)
	for _, fam := range extractFontFamilies(ds.Fonts) {
		// WordPress/CSS convention: bare for single-word families, single-quoted
		// for multi-word ones. (Go's %q would emit invalid-looking double quotes.)
		var stack string
		if strings.Contains(fam, " ") {
			stack = fmt.Sprintf("'%s', sans-serif", fam)
		} else {
			stack = fmt.Sprintf("%s, sans-serif", fam)
		}
		fontFamilies = append(fontFamilies, map[string]any{
			"fontFamily": stack,
			"name":       fam,
			"slug":       strings.ToLower(strings.ReplaceAll(fam, " ", "-")),
		})
	}

	settings := map[string]interface{}{}
	if len(palette) > 0 {
		settings["color"] = map[string]interface{}{"palette": palette}
	}
	if len(fontFamilies) > 0 {
		settings["typography"] = map[string]interface{}{"fontFamilies": fontFamilies}
	}
	if len(settings) == 0 {
		return nil, nil
	}

	result := map[string]interface{}{
		"$schema":  wpSchemaURL(),
		"version":  3,
		"settings": settings,
	}
	if len(fontFamilies) > 0 {
		firstSlug := fontFamilies[0]["slug"].(string)
		result["styles"] = map[string]interface{}{
			"typography": map[string]interface{}{
				"fontFamily": "var:preset|font-family|" + firstSlug,
			},
		}
	}
	return result, nil
}

// wpEnrichmentDefaults are handbook-grade theme.json defaults merged UNDER the
// template's theme.json (template-curated values always win). Brand values
// (palette, fonts, $schema) are merged OVER it afterwards — see applyWordPressDesign.
func wpEnrichmentDefaults() map[string]interface{} {
	return map[string]interface{}{
		"settings": map[string]interface{}{
			"appearanceTools":               true,
			"useRootPaddingAwareAlignments": true,
			"color": map[string]interface{}{
				"defaultPalette":   false,
				"defaultGradients": false,
				"defaultDuotone":   false,
			},
			"typography": map[string]interface{}{
				"fluid":            true,
				"defaultFontSizes": false,
				"lineHeight":       true,
				"letterSpacing":    true,
				"fontSizes": []interface{}{
					map[string]interface{}{"slug": "small", "size": "0.875rem", "name": "Small", "fluid": false},
					map[string]interface{}{"slug": "medium", "size": "1rem", "name": "Medium", "fluid": false},
					map[string]interface{}{"slug": "large", "size": "1.375rem", "name": "Large",
						"fluid": map[string]interface{}{"min": "1.25rem", "max": "1.5rem"}},
					map[string]interface{}{"slug": "x-large", "size": "2rem", "name": "Extra Large",
						"fluid": map[string]interface{}{"min": "1.75rem", "max": "2.25rem"}},
					map[string]interface{}{"slug": "xx-large", "size": "3rem", "name": "Huge",
						"fluid": map[string]interface{}{"min": "2.25rem", "max": "3.5rem"}},
				},
			},
			"spacing": map[string]interface{}{
				"defaultSpacingSizes": false,
				"blockGap":            true,
				"spacingSizes": []interface{}{
					map[string]interface{}{"slug": "20", "size": "0.5rem", "name": "2X-Small"},
					map[string]interface{}{"slug": "30", "size": "1rem", "name": "X-Small"},
					map[string]interface{}{"slug": "40", "size": "1.5rem", "name": "Small"},
					map[string]interface{}{"slug": "50", "size": "2.5rem", "name": "Medium"},
					map[string]interface{}{"slug": "60", "size": "4rem", "name": "Large"},
					map[string]interface{}{"slug": "70", "size": "6rem", "name": "X-Large"},
					map[string]interface{}{"slug": "80", "size": "8rem", "name": "2X-Large"},
				},
			},
			"layout": map[string]interface{}{
				"contentSize": "660px",
				"wideSize":    "1200px",
			},
		},
		"styles": map[string]interface{}{
			"color": map[string]interface{}{
				"background": "var:preset|color|base",
				"text":       "var:preset|color|contrast",
			},
			"spacing": map[string]interface{}{
				"padding": map[string]interface{}{
					"left":  "var:preset|spacing|50",
					"right": "var:preset|spacing|50",
				},
			},
		},
	}
}
