package svg

import (
	
	
	
	
	
	
)

// Attribute is an SVG attribute whose name has been validated and whose value
// is escaped when rendered. Its fields are intentionally private so callers
// cannot accidentally construct an unescaped attribute fragment.
type Attribute struct {
	name  string
	value string
}

// Name returns the validated attribute name. Values remain private so they
// cannot be serialized without escaping.
func ( Attribute) () (string, error) {
	if !validAttributeName(.name) {
		return "", fmt.Errorf("invalid zero-value SVG attribute")
	}
	return .name, nil
}

// Attr constructs an SVG attribute. The name must be a static XML attribute
// name and must not be an event-handler attribute. Invalid names panic because
// attribute names are a programmer-controlled part of the renderer, never
// diagram input.
func (,  string) Attribute {
	if !validAttributeName() {
		panic(fmt.Sprintf("invalid SVG attribute name %q", ))
	}
	return Attribute{name: , value: }
}

func ( string,  int) Attribute {
	return Attr(, strconv.Itoa())
}

func ( string,  float64) Attribute {
	if math.IsNaN() || math.IsInf(, 0) {
		panic(fmt.Sprintf("non-finite SVG attribute %q", ))
	}
	return Attr(, FormatFloat())
}

// RenderAttributes serializes attributes in the supplied order. The returned
// string is empty or begins with one space, so it can be appended directly to
// an opening element name.
func ( ...Attribute) string {
	if len() == 0 {
		return ""
	}

	var  strings.Builder
	 := make(map[string]struct{}, len())
	for ,  := range  {
		if !validAttributeName(.name) {
			panic(fmt.Sprintf("invalid zero-value SVG attribute"))
		}
		if ,  := [.name];  {
			panic(fmt.Sprintf("duplicate SVG attribute %q", .name))
		}
		[.name] = struct{}{}
		.WriteByte(' ')
		.WriteString(.name)
		.WriteString(`="`)
		.WriteString(EscapeAttribute(.value))
		.WriteByte('"')
	}
	return .String()
}

// ParseAttributes converts the legacy textual attribute-list form into typed
// attributes. It rejects malformed XML, duplicate attributes, and event
// handlers instead of copying the fragment into renderer output.
func ( string) ([]Attribute, error) {
	if strings.TrimSpace() == "" {
		return nil, nil
	}
	 := xml.NewDecoder(strings.NewReader("<d2 " +  + "></d2>"))
	.Strict = true
	,  := .RawToken()
	if  != nil {
		return nil, fmt.Errorf("invalid SVG attributes: %w", )
	}
	,  := .(xml.StartElement)
	if ! || .Name.Local != "d2" || .Name.Space != "" {
		return nil, fmt.Errorf("invalid SVG attributes")
	}
	 := make([]Attribute, 0, len(.Attr))
	 := make(map[string]struct{}, len(.Attr))
	for ,  := range .Attr {
		 := .Name.Local
		if .Name.Space != "" {
			 = .Name.Space + ":" + 
		}
		if !validAttributeName() {
			return nil, fmt.Errorf("invalid SVG attribute name %q", )
		}
		if ,  := [];  {
			return nil, fmt.Errorf("duplicate SVG attribute %q", )
		}
		[] = struct{}{}
		 = append(, Attribute{name: , value: .Value})
	}
	,  = .RawToken()
	if  != nil {
		return nil, fmt.Errorf("invalid SVG attributes: %w", )
	}
	,  := .(xml.EndElement)
	if ! || .Name.Local != "d2" || .Name.Space != "" {
		return nil, fmt.Errorf("SVG attribute fragment contains markup")
	}
	if ,  := .RawToken();  != io.EOF {
		if  == nil {
			return nil, fmt.Errorf("SVG attribute fragment contains trailing markup")
		}
		return nil, fmt.Errorf("invalid SVG attributes: %w", )
	}
	return , nil
}

// HrefAttributes returns the SVG 2 href attribute and its SVG 1.1
// compatibility equivalent. This function only serializes the URL; callers
// must continue to apply the appropriate link or asset policy first.
func ( string) []Attribute {
	return []Attribute{
		Attr("href", ),
		Attr("xlink:href", ),
	}
}

// OpenElement and EmptyElement serialize element boundaries while validating
// the renderer-controlled tag name.
func ( string,  ...Attribute) string {
	validateElementName()
	return "<" +  + RenderAttributes(...) + ">"
}

func ( string,  ...Attribute) string {
	validateElementName()
	return "<" +  + RenderAttributes(...) + " />"
}

// CompactEmptyElement is equivalent to EmptyElement but preserves the compact
// `/>` syntax used by older renderer output.
func ( string,  ...Attribute) string {
	validateElementName()
	return "<" +  + RenderAttributes(...) + "/>"
}

func ( string) string {
	validateElementName()
	return "</" +  + ">"
}

func validateElementName( string) {
	if !validXMLName() || strings.EqualFold(, "script") {
		panic(fmt.Sprintf("invalid SVG element name %q", ))
	}
}

func validAttributeName( string) bool {
	if !validXMLName() {
		return false
	}
	 := 
	if  := strings.LastIndexByte(, ':');  >= 0 {
		 = [+1:]
	}
	return !strings.HasPrefix(strings.ToLower(), "on")
}

func validXMLName( string) bool {
	if  == "" || strings.Count(, ":") > 1 {
		return false
	}
	for ,  := range strings.Split(, ":") {
		if !validXMLNamePart() {
			return false
		}
	}
	return true
}

func validXMLNamePart( string) bool {
	if  == "" {
		return false
	}
	for  := 0;  < len(); ++ {
		 := []
		if  == 0 {
			if !isASCIIAlpha() &&  != '_' {
				return false
			}
			continue
		}
		if !isASCIIAlpha() && !isASCIIDigit() &&  != '_' &&  != '-' &&  != '.' {
			return false
		}
	}
	return true
}

func isASCIIAlpha( byte) bool {
	return  >= 'a' &&  <= 'z' ||  >= 'A' &&  <= 'Z'
}

func isASCIIDigit( byte) bool {
	return  >= '0' &&  <= '9'
}

// ID is an identifier that is safe to use both as an XML id and in an
// unquoted local IRI such as url(#identifier).
type ID struct {
	value string
}

// LiteralID constructs an ID from a renderer-generated value. It panics if the
// value contains syntax that could change the meaning of a local IRI.
func ( string) ID {
	if !validID() {
		panic(fmt.Sprintf("invalid SVG id %q", ))
	}
	return ID{value: }
}

// EncodedID encodes an arbitrary non-empty value using SVGID's stable base32
// representation.
func ( string) ID {
	 := SVGID()
	if  == "" {
		panic("cannot encode an empty SVG id")
	}
	return ID{value: }
}

const scopedIDEncodingPrefix = "d2e-"

// ScopedID builds a stable identifier from trusted prefix/suffix strings and
// an arbitrary component. Ordinary safe components retain their spelling.
// Unsafe values and values beginning with the reserved encoding prefix are
// base32 encoded, keeping the mapping injective without changing common IDs.
func (, ,  string) ID {
	if !validIDPart() || !validIDPart() {
		panic("invalid SVG id prefix or suffix")
	}
	 := 
	if  == "" || !validIDPart() || strings.HasPrefix(, scopedIDEncodingPrefix) {
		 = scopedIDEncodingPrefix + SVGID()
	}
	return LiteralID( +  + )
}

func ( ID) () string {
	if !validID(.value) {
		panic("invalid zero-value SVG id")
	}
	return .value
}

// LocalIRI returns an SVG local-resource reference for id.
func ( ID) string {
	return "url(#" + .String() + ")"
}

// ParseLocalIRI validates the deprecated textual local-reference form.
func ( string) (ID, error) {
	if !strings.HasPrefix(, "url(#") || !strings.HasSuffix(, ")") {
		return ID{}, fmt.Errorf("invalid SVG local IRI %q", )
	}
	 := strings.TrimSuffix(strings.TrimPrefix(, "url(#"), ")")
	if !validID() {
		return ID{}, fmt.Errorf("invalid SVG local IRI %q", )
	}
	return ID{value: }, nil
}

func validID( string) bool {
	return  != "" && validIDPart()
}

func validIDPart( string) bool {
	for  := 0;  < len(); ++ {
		 := []
		if !isASCIIAlpha() && !isASCIIDigit() &&  != '_' &&  != '-' &&  != '.' &&  != ':' {
			return false
		}
	}
	return true
}

// Fragment is content that is already safe to place between SVG element tags.
// Use Text for untrusted strings and TrustedFragment only for
// renderer-generated SVG.
type Fragment struct {
	value string
}

func ( string) Fragment {
	return Fragment{value: EscapeText()}
}

func ( string) Fragment {
	validateXMLString()
	return Fragment{value: }
}

func ( ...Fragment) Fragment {
	var  strings.Builder
	for ,  := range  {
		.WriteString(.value)
	}
	return Fragment{value: .String()}
}

func ( Fragment) () string {
	return .value
}