package d2svg

import (
	
	
	

	
)

// pruneThemeCSS specializes a complete ThemeCSS stylesheet to the rendered SVG
// fragments. ThemeCSS itself remains available with its complete rule set.
func pruneThemeCSS( string,  ...string) string {
	return pruneThemeCSSWithClasses(, nil, ...)
}

// The font scan can supply body classes so only the small background fragments
// need a second XML pass. The map belongs to the current Render call.
func pruneThemeCSSWithClasses( string,  map[string]bool,  ...string) string {
	if  == "" {
		return 
	}
	// appendix.Append adds this separator after Render, without an inline color.
	// Its text color uses the separate .appendix rule, which is never pruned.
	if  == nil {
		 = make(map[string]bool)
	}
	["stroke-B2"] = true
	for ,  := range  {
		 := xml.NewDecoder(strings.NewReader())
		for {
			,  := .Token()
			if  == io.EOF {
				break
			}
			if  != nil {
				// Preserve the existing output for custom/malformed SVG fragments.
				return 
			}
			,  := .(xml.StartElement)
			if ! {
				continue
			}
			if strings.EqualFold(.Name.Local, "script") {
				return 
			}
			for ,  := range .Attr {
				// Custom icon SVG can mutate classes at runtime. Keep all rules
				// when scripts, event handlers, or SMIL make class use dynamic.
				 := strings.ToLower(.Name.Local)
				if strings.HasPrefix(, "on") ||  == "attributename" && strings.EqualFold(strings.TrimSpace(.Value), "class") {
					return 
				}
				if  == "href" {
					 := strings.TrimSpace(.Value)
					if strings.EqualFold(.Name.Local, "use") && !strings.HasPrefix(, "#") || strings.HasPrefix(strings.ToLower(), "javascript:") {
						return 
					}
				}
				if .Name.Local == "class" {
					for ,  := range strings.Fields(.Value) {
						if isPaletteClass() {
							[] = true
						}
					}
				}
			}
		}
	}

	return pruneThemeRules(, )
}

// Scan the simple palette selectors emitted by ThemeCSS, leaving all other
// rules intact. This recognizes the same grammar as the original regexp,
// including its ASCII whitespace and class names, without repeatedly matching
// the complete stylesheet and then matching each rule again.
func pruneThemeRules( string,  map[string]bool) string {
	var  strings.Builder
	 := 0
	for  := 0;  < len(); {
		 := strings.IndexByte([:], '{')
		if  < 0 {
			break
		}
		 :=  + 
		 =  + 1
		, ,  := themeRuleSelector(, )
		if ! {
			continue
		}
		 := strings.IndexAny([:], "{}")
		if  < 0 {
			break
		}
		 += 
		if [] != '}' {
			continue
		}
		 =  + 1
		if !isPaletteClass() || [] {
			continue
		}
		for  >  && themeRuleWhitespace([-1]) {
			--
		}
		if .Cap() == 0 {
			.Grow(len())
		}
		.WriteString([:])
		 = 
	}
	if  == 0 {
		return 
	}
	.WriteString([:])
	return .String()
}

func themeRuleSelector( string,  int) ( int,  string,  bool) {
	 = 
	for  > 0 && themeRuleWord([-1]) {
		--
	}
	if  == 0 || [-1] != '.' {
		return 0, "", false
	}
	 = [:]
	 := ""
	if strings.HasPrefix(, "sketch-overlay-") {
		 = [len("sketch-overlay-"):]
		--
	} else {
		for ,  := range []string{"fill-", "stroke-", "background-color-", "color-"} {
			if strings.HasPrefix(, ) {
				 = [len():]
				break
			}
		}
		// The scoped form has exactly one space between its two classes.
		if  < 3 || [-2] != ' ' {
			return 0, "", false
		}
		 :=  - 2
		 = 
		for  > 0 && themeRuleWord([-1]) {
			--
		}
		if  ==  ||  == 0 || [-1] != '.' {
			return 0, "", false
		}
		--
	}
	if  == "" {
		return 0, "", false
	}
	for  := range  {
		if  := []; !( >= 'A' &&  <= 'Z' ||  >= '0' &&  <= '9') {
			return 0, "", false
		}
	}
	return , , true
}

func themeRuleWord( byte) bool {
	return  >= 'a' &&  <= 'z' ||  >= 'A' &&  <= 'Z' ||  >= '0' &&  <= '9' ||  == '_' ||  == '-'
}

func themeRuleWhitespace( byte) bool {
	return  == ' ' ||  == '\t' ||  == '\n' ||  == '\r' ||  == '\f'
}

func isPaletteClass( string) bool {
	for ,  := range []string{"fill-", "stroke-", "background-color-", "color-", "sketch-overlay-"} {
		if ,  := strings.CutPrefix(, );  {
			return color.IsThemeColor()
		}
	}
	return false
}