package d2scenebuild

import (
	
	
	
	
	

	
	
	
	
)

func ( *builder) () error {
	 := false
	for ,  := range .diagram.Shapes {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Link != "" || .Tooltip != "" || .PrettyLink != "" {
			 = true
			break
		}
	}
	if ! {
		for ,  := range .diagram.Connections {
			if  := .ctx.Err();  != nil {
				return 
			}
			if .Link != "" || .Tooltip != "" || .PrettyLink != "" {
				 = true
				break
			}
		}
	}
	if ! {
		return .ctx.Err()
	}
	 := .options.LinkBudget
	if .MaxRegions <= 0 || .MaxStringBytes <= 0 {
		return invalidField("options", "linkBudget", , "must provide positive MaxRegions and MaxStringBytes for link/tooltip metadata")
	}

	,  := len(.links), .linkBytes
	,  := len(.appendixItems), .appendixStringBytes
	 := false
	defer func() {
		if ! {
			.links = .links[:]
			.linkBytes = 
			.appendixItems = .appendixItems[:]
			.appendixStringBytes = 
		}
	}()
	for ,  := range .diagram.Shapes {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Link == "" && .Tooltip == "" && .PrettyLink == "" {
			continue
		}
		 := fmt.Sprintf("shape %q", .ID)
		,  := .shapeLinkRegion(, )
		if  != nil {
			return 
		}
		if  := .addLinkRegion(, );  != nil {
			return 
		}
		if  := .addAppendixItem(fmt.Sprintf("shape[%d] %q", , .ID), "tooltip", .Tooltip);  != nil {
			return 
		}
		if  := .addAppendixItem(fmt.Sprintf("shape[%d] %q", , .ID), "prettyLink", .PrettyLink);  != nil {
			return 
		}
	}
	for ,  := range .diagram.Connections {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Link == "" && .Tooltip == "" && .PrettyLink == "" {
			continue
		}
		 := fmt.Sprintf("connection %q", .ID)
		,  := .connectionLinkRegion(, )
		if  != nil {
			return 
		}
		if  := .addLinkRegion(, );  != nil {
			return 
		}
		if  := .addAppendixItem(fmt.Sprintf("connection[%d] %q", , .ID), "tooltip", .Tooltip);  != nil {
			return 
		}
		if  := .addAppendixItem(fmt.Sprintf("connection[%d] %q", , .ID), "prettyLink", .PrettyLink);  != nil {
			return 
		}
	}
	 = true
	return nil
}

func ( *builder) ( string,  d2target.Shape) (d2scene.LinkRegion, error) {
	if .PrettyLink != "" && .Link == "" {
		return d2scene.LinkRegion{}, invalidField(, "prettyLink", .PrettyLink, "requires a non-empty link")
	}
	if  := .validateLinkFields(, .Link, .Tooltip);  != nil {
		return d2scene.LinkRegion{}, 
	}
	if  := validateLinkTooltipSecurity(, .Link, .Tooltip);  != nil {
		return d2scene.LinkRegion{}, 
	}
	,  := linkDestination(.Link)
	 := float64(.StrokeWidth)
	 := d2scene.LinkRegion{
		// PDF/PPTX annotations expand a shape by its full stroke width on every
		// side.
		Box: d2scene.Box{
			X:      float64(.Pos.X) - ,
			Y:      float64(.Pos.Y) - ,
			Width:  float64(.Width) + 2*,
			Height: float64(.Height) + 2*,
		},
		URL: , Tooltip: .Tooltip, Target: ,
	}
	return , nil
}

func ( *builder) ( string,  d2target.Connection) (d2scene.LinkRegion, error) {
	if .PrettyLink != "" && .Link == "" {
		return d2scene.LinkRegion{}, invalidField(, "prettyLink", .PrettyLink, "requires a non-empty link")
	}
	if  := .validateLinkFields(, .Link, .Tooltip);  != nil {
		return d2scene.LinkRegion{}, 
	}
	if  := validateLinkTooltipSecurity(, .Link, .Tooltip);  != nil {
		return d2scene.LinkRegion{}, 
	}
	 := .GetLabelTopLeft()
	if  == nil {
		return d2scene.LinkRegion{}, invalidField(, "labelPosition", .LabelPosition, "does not resolve to a finite metadata hit region")
	}
	,  := linkDestination(.Link)
	return d2scene.LinkRegion{
		// Only the rounded connection-label box is interactive; the route, arrows,
		// endpoint labels, and icon are not.
		Box: d2scene.Box{
			X: math.Round(.X), Y: math.Round(.Y),
			Width: float64(.LabelWidth), Height: float64(.LabelHeight),
		},
		URL: , Tooltip: .Tooltip, Target: ,
	}, nil
}

func validateLinkTooltipSecurity(, ,  string) error {
	if textmeasure.IsDangerousLink() {
		return invalidField(, "link", , "uses an unsafe URL scheme")
	}
	if  == "" ||  == "" {
		return nil
	}
	,  := url.ParseRequestURI()
	if  == nil && .Host != "" {
		return invalidField(, "tooltip", , "must not be a URL when link is also set")
	}
	return nil
}

// linkDestination preserves the PDF/PPTX distinction: a parsed D2 key
// rooted at "root" is a board target; every other value, including relative
// paths and opaque application schemes, remains an external URL string.
func linkDestination( string) (,  string) {
	if  == "" {
		return "", ""
	}
	,  := d2parser.ParseKey()
	if  == nil && len(.Path) != 0 && .Path[0].Unbox().ScalarString() == "root" {
		return "", 
	}
	return , ""
}

func ( *builder) (, ,  string) error {
	 := .linkBytes
	for ,  := range []struct {
		  string
		 string
	}{{"link", }, {"tooltip", }} {
		if len(.) > .options.LinkBudget.MaxStringBytes- {
			return fmt.Errorf("scene: link metadata string bytes exceed limit %d", .options.LinkBudget.MaxStringBytes)
		}
		if  := validateLinkString(.ctx, , ., .);  != nil {
			return 
		}
		 += len(.)
	}
	return nil
}

func ( *builder) ( string,  d2scene.LinkRegion) error {
	 := .options.LinkBudget
	if len(.links) >= .MaxRegions {
		return fmt.Errorf("scene: link region count exceeds limit %d", .MaxRegions)
	}
	if !finite(.Box.X) || !finite(.Box.Y) || !finite(.Box.Width) || !finite(.Box.Height) ||
		.Box.Width <= 0 || .Box.Height <= 0 {
		return invalidField(, "linkRegion", .Box, "must be a finite box with positive dimensions")
	}
	if .URL != "" && .Target != "" {
		return fmt.Errorf("scene: %s link metadata has both URL and target destinations", )
	}
	if .URL == "" && .Tooltip == "" && .Target == "" {
		return fmt.Errorf("scene: %s link metadata is empty", )
	}

	 := .linkBytes
	for ,  := range []struct {
		  string
		 string
	}{{"URL", .URL}, {"tooltip", .Tooltip}, {"target", .Target}} {
		if len(.) > .MaxStringBytes- {
			return fmt.Errorf("scene: link metadata string bytes exceed limit %d", .MaxStringBytes)
		}
		 += len(.)
	}
	if  := .ctx.Err();  != nil {
		return 
	}
	.links = append(.links, )
	.linkBytes = 
	return nil
}

func validateLinkString( context.Context, , ,  string) error {
	 := 0
	for  := 0;  < len(); {
		if  >=  {
			if  := .Err();  != nil {
				return 
			}
			 =  + 4096
		}
		,  := utf8.DecodeRuneInString([:])
		if  == utf8.RuneError &&  == 1 {
			return invalidField(, , nil, "must be valid UTF-8")
		}
		if !validXMLMetadataRune() {
			return invalidField(, , nil, "contains a character forbidden by XML 1.0 metadata consumers")
		}
		 += 
	}
	return .Err()
}

func validXMLMetadataRune( rune) bool {
	return  == '\t' ||  == '\n' ||  == '\r' ||
		 >= 0x20 &&  <= 0xd7ff ||
		 >= 0xe000 &&  <= 0xfffd ||
		 >= 0x10000 &&  <= 0x10ffff
}

// visualBoundingBox removes non-pixel metadata from a shallow diagram copy.
// d2target.BoundingBox reserves appendix-icon space for ordinary Link and
// Tooltip metadata, but LinkRegions themselves must not perturb scene pixels
// or leave blank margins. Positioned tooltips are actual scene pixels, so keep
// precisely their tooltip fields and let d2target retain their svg bounds.
func ( *builder) () (d2target.Point, d2target.Point) {
	 := false
	for ,  := range .diagram.Shapes {
		if .Link != "" || .Tooltip != "" || .PrettyLink != "" || .TooltipPosition != "" {
			 = true
			break
		}
	}
	if ! {
		return .diagram.BoundingBox()
	}
	 := *.diagram
	.Shapes = append([]d2target.Shape(nil), .diagram.Shapes...)
	for  := range .Shapes {
		 := .Shapes[].Tooltip != "" && .Shapes[].TooltipPosition != ""
		.Shapes[].Link = ""
		.Shapes[].PrettyLink = ""
		if ! {
			.Shapes[].Tooltip = ""
			.Shapes[].TooltipPosition = ""
		}
	}
	return .BoundingBox()
}