package d2scenebuild

import (
	
	
	
	
	
	
	

	
	
	
	
	
	
)

var defaultImageAspect = d2scene.AspectRatio{
	Align: d2scene.AlignXMidYMid,
	Fit:   d2scene.AspectMeet,
}

const unavailableImageAssetID d2scene.AssetID = "image:unavailable"

func ( *builder) () error {
	for ,  := range .diagram.Shapes {
		if  := .ctx.Err();  != nil {
			return 
		}
		if !shapeAssetIsEmitted() {
			continue
		}
		if ,  := .resolveImageAsset(fmt.Sprintf("shape %q", .ID), .Icon.String());  != nil {
			return 
		}
	}
	for ,  := range .diagram.Shapes {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Opacity == 0 || .Label == "" || .Language != "latex" {
			continue
		}
		if ,  := .resolveLatexAsset(fmt.Sprintf("shape %q", .ID), .Label, .Stroke);  != nil {
			return 
		}
	}
	for ,  := range .diagram.Connections {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Icon == nil {
			continue
		}
		if ,  := .resolveImageAsset(fmt.Sprintf("connection %q", .ID), .Icon.String());  != nil {
			return 
		}
	}
	for ,  := range .diagram.Connections {
		if  := .ctx.Err();  != nil {
			return 
		}
		if .Opacity == 0 || .Label == "" || .Language != "latex" {
			continue
		}
		if ,  := .resolveLatexAsset(fmt.Sprintf("connection %q", .ID), .Label, .Color);  != nil {
			return 
		}
	}
	if .diagram.Legend != nil {
		for ,  := range .diagram.Legend.Shapes {
			if  := .ctx.Err();  != nil {
				return 
			}
			if .Label == "" {
				continue
			}
			 := legendShapeIconTarget(, )
			if !shapeAssetIsEmitted() {
				continue
			}
			if ,  := .resolveImageAsset(legendShapeObject(, .ID), .Icon.String());  != nil {
				return 
			}
		}
	}
	return nil
}

// shapeAssetIsEmitted reports whether a shape contributes an image asset.
// Image, class, and table shapes retain their image when opacity is zero;
// ordinary and code icons do not.
func shapeAssetIsEmitted( d2target.Shape) bool {
	if .Icon == nil {
		return false
	}
	return .Opacity != 0 || .Type == d2target.ShapeImage ||
		.Type == d2target.ShapeClass || .Type == d2target.ShapeSQLTable
}

func ( *builder) (,  string) (d2scene.AssetID, error) {
	if  := .sourceAssetIDs[];  != "" {
		return , nil
	}
	if .options.Assets == nil || .options.Assets.Resolver == nil {
		return "", unsupported(, "image/icon asset without a configured resolver")
	}
	,  := .options.Assets.Resolver.Resolve(.ctx, )
	if  != nil {
		if  := .ctx.Err();  != nil {
			return "", 
		}
		if errors.Is(, imageasset.ErrUnavailable) {
			return .resolveUnavailableImageAsset(), nil
		}
		return "", fmt.Errorf("scene: %s image asset: %w", , )
	}
	if  == nil {
		return "", fmt.Errorf("scene: %s image asset resolver returned nil", )
	}
	,  := .BytesContext(.ctx)
	if  != nil {
		return "", fmt.Errorf("scene: %s image asset copy: %w", , )
	}
	,  := hashImageAsset(.ctx, .Kind(), .MIMEType(), )
	if  != nil {
		return "", fmt.Errorf("scene: %s image asset hash: %w", , )
	}
	if  := .assetIDs[];  != "" {
		.sourceAssetIDs[] = 
		return , nil
	}

	 := d2scene.AssetID(fmt.Sprintf("image:%x", [:]))
	var  d2scene.Asset
	switch .Kind() {
	case imageasset.KindRaster:
		 = d2scene.RasterAsset{
			MIMEType:     .MIMEType(),
			Data:         ,
			PixelWidth:   .PixelWidth(),
			PixelHeight:  .PixelHeight(),
			DecodedBytes: .DecodedBytes(),
		}
	case imageasset.KindSVG:
		,  := .remainingSVGImportLimits()
		if  != nil {
			return "", 
		}
		,  := d2svgimport.ImportNode(.ctx, , , )
		if  != nil {
			return "", fmt.Errorf("scene: %s SVG asset: %w", , )
		}
		if  := .reserveSVGImport(, .Metrics);  != nil {
			return "", 
		}
		if  := .mergeSVGImportAssets(, .Assets);  != nil {
			return "", 
		}
		 := d2scene.NewNode(nil)
		.ID = string() + ":content"
		.Transform = .ViewportTransform
		.Children = []*d2scene.Node{.Root}

		 := d2scene.Box{Width: .Width, Height: .Height}
		 := d2scene.NewNode(nil)
		.ID = string() + ":viewport"
		.Clip = boxClip()
		.Children = []*d2scene.Node{}
		 = d2scene.VectorAsset{ViewBox: , Root: }
	default:
		return "", fmt.Errorf("scene: %s image asset has unsupported resolved kind %s", , .Kind())
	}
	.assets[] = 
	.assetIDs[] = 
	.sourceAssetIDs[] = 
	return , nil
}

func ( *builder) ( string) d2scene.AssetID {
	if ,  := .assets[unavailableImageAssetID]; ! {
		.assets[unavailableImageAssetID] = newUnavailableImageAsset()
	}
	.sourceAssetIDs[] = unavailableImageAssetID
	return unavailableImageAssetID
}

func newUnavailableImageAsset() d2scene.VectorAsset {
	 := d2scene.SolidPaint{Color: color.NRGBA{R: 0xf7, G: 0xf8, B: 0xfa, A: 0xff}}
	 := d2scene.SolidPaint{Color: color.NRGBA{R: 0x7b, G: 0x83, B: 0x8c, A: 0xff}}
	 := func( float64) *d2scene.Stroke {
		return &d2scene.Stroke{Paint: , Width: , Cap: d2scene.CapRound, Join: d2scene.JoinRound}
	}

	 := d2scene.NewNode(d2scene.Path{
		Commands: []d2scene.PathCommand{
			d2scene.MoveTo(7, 3), d2scene.LineTo(42, 3), d2scene.LineTo(57, 18),
			d2scene.LineTo(57, 61), d2scene.LineTo(7, 61), d2scene.ClosePath(),
		},
		Fill: , Stroke: (2),
	})
	.ID = "image-unavailable:page"

	 := d2scene.NewNode(d2scene.Rect{
		Box:  d2scene.Box{X: 13, Y: 23, Width: 38, Height: 30},
		Fill: d2scene.SolidPaint{Color: color.NRGBA{R: 0xb8, G: 0xd7, B: 0xf2, A: 0xff}},
	})
	.ID = "image-unavailable:sky"

	 := d2scene.NewNode(d2scene.Ellipse{
		Center: d2scene.Point{X: 23, Y: 31}, RadiusX: 4, RadiusY: 4,
		Fill: d2scene.SolidPaint{Color: color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}},
	})
	.ID = "image-unavailable:sun"

	 := d2scene.NewNode(d2scene.Path{
		Commands: []d2scene.PathCommand{
			d2scene.MoveTo(13, 49), d2scene.LineTo(24, 37), d2scene.LineTo(32, 45),
			d2scene.LineTo(39, 39), d2scene.LineTo(51, 49), d2scene.LineTo(51, 53),
			d2scene.LineTo(13, 53), d2scene.ClosePath(),
		},
		Fill: d2scene.SolidPaint{Color: color.NRGBA{R: 0x55, G: 0xb9, B: 0x4b, A: 0xff}},
	})
	.ID = "image-unavailable:hills"

	 := d2scene.NewNode(d2scene.Path{
		Commands: []d2scene.PathCommand{d2scene.MoveTo(37, 54), d2scene.LineTo(51, 40)},
		Stroke:   &d2scene.Stroke{Paint: , Width: 5, Cap: d2scene.CapSquare, Join: d2scene.JoinRound},
	})
	.ID = "image-unavailable:break"

	 := d2scene.NewNode(d2scene.Rect{
		Box: d2scene.Box{X: 13, Y: 23, Width: 38, Height: 30}, Stroke: (1),
	})
	.ID = "image-unavailable:image-outline"

	 := d2scene.NewNode(d2scene.Path{
		Commands: []d2scene.PathCommand{
			d2scene.MoveTo(42, 3), d2scene.LineTo(42, 18), d2scene.LineTo(57, 18),
		},
		Stroke: (2),
	})
	.ID = "image-unavailable:fold"

	 := d2scene.NewNode(nil)
	.ID = "image-unavailable"
	.Children = []*d2scene.Node{, , , , , , }
	return d2scene.VectorAsset{ViewBox: d2scene.Box{Width: 64, Height: 64}, Root: }
}

// mergeSVGImportAssets transfers the importer's owned, immutable embedded
// raster resources into the document asset table. Sorting makes cancellation
// and collision failures deterministic even though Result.Assets is a map.
// The importer content-addresses these resources, so an existing equal entry
// is a normal cross-SVG deduplication while unequal content is an invariant
// violation rather than something that may be silently overwritten.
func ( *builder) ( string,  map[d2scene.AssetID]d2scene.Asset) error {
	 := make([]string, 0, len())
	for  := range  {
		 = append(, string())
	}
	sort.Strings()

	type  struct {
		    d2scene.AssetID
		 d2scene.RasterAsset
	}
	 := make([], 0, len())
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return 
		}
		 := d2scene.AssetID()
		,  := [].(d2scene.RasterAsset)
		if ! {
			return fmt.Errorf("scene: %s SVG asset %q has unsupported imported resource type %T", , , [])
		}
		if ,  := .assets[];  {
			,  := .(d2scene.RasterAsset)
			if ! || !equalRasterAsset(, ) {
				return fmt.Errorf("scene: %s SVG asset %q collides with a different document resource", , )
			}
			continue
		}
		 = append(, {: , : })
	}
	for ,  := range  {
		.assets[.] = .
	}
	return nil
}

func equalRasterAsset(,  d2scene.RasterAsset) bool {
	return .MIMEType == .MIMEType && .PixelWidth == .PixelWidth &&
		.PixelHeight == .PixelHeight && .DecodedBytes == .DecodedBytes &&
		bytes.Equal(.Data, .Data)
}

func hashImageAsset( context.Context,  imageasset.Kind,  string,  []byte) ([sha256.Size]byte, error) {
	 := sha256.New()
	_, _ = .Write([]byte{byte(), 0})
	_, _ = .Write([]byte())
	_, _ = .Write([]byte{0})
	const  = 32 << 10
	for  := 0;  < len();  +=  {
		if  := .Err();  != nil {
			return [sha256.Size]byte{}, 
		}
		 :=  + 
		if  > len() {
			 = len()
		}
		_, _ = .Write([:])
	}
	if  := .Err();  != nil {
		return [sha256.Size]byte{}, 
	}
	var  [sha256.Size]byte
	copy([:], .Sum(nil))
	return , nil
}

func ( *builder) ( string) (d2svgimport.Limits, error) {
	 := .options.Assets.SVGImportBudget
	if .MaxSourceBytes <= 0 || .MaxElements <= 0 || .MaxAttributes <= 0 || .MaxAttributeBytes <= 0 ||
		.MaxPathCommands <= 0 || .MaxTransformFunctions <= 0 ||
		.MaxDeclaredResources <= 0 || .MaxExpandedUseInstances <= 0 {
		return d2svgimport.Limits{}, fmt.Errorf("scene: %s SVG asset requires positive document-wide import budgets", )
	}
	type  struct {
		      string
		 int
		     func(*d2svgimport.Limits, int)
	}
	 := .options.Assets.SVGImportLimits
	 := []{
		{"source bytes", .MaxSourceBytes - .svgSourceBytes, func( *d2svgimport.Limits,  int) { .MaxBytes = minInt(.MaxBytes, ) }},
		{"element count", .MaxElements - .svgElements, func( *d2svgimport.Limits,  int) { .MaxElements = minInt(.MaxElements, ) }},
		{"attribute count", .MaxAttributes - .svgAttributes, func( *d2svgimport.Limits,  int) {
			.MaxAttributes = minInt(.MaxAttributes, )
		}},
		{"attribute bytes", .MaxAttributeBytes - .svgAttributeBytes, func( *d2svgimport.Limits,  int) {
			.MaxAttributeBytes = minInt(.MaxAttributeBytes, )
		}},
		{"path command count", .MaxPathCommands - .svgCommands, func( *d2svgimport.Limits,  int) {
			.MaxPathCommands = minInt(.MaxPathCommands, )
		}},
		{"transform function count", .MaxTransformFunctions - .svgTransforms, func( *d2svgimport.Limits,  int) {
			.MaxTransformFunctions = minInt(.MaxTransformFunctions, )
		}},
		{"declared resource count", .MaxDeclaredResources - .svgDeclaredResources, func( *d2svgimport.Limits,  int) { .MaxResources = minInt(.MaxResources, ) }},
		{"expanded use instance count", .MaxExpandedUseInstances - .svgExpandedUseInstances, func( *d2svgimport.Limits,  int) { .MaxResources = minInt(.MaxResources, ) }},
	}
	for ,  := range  {
		if . <= 0 {
			return d2svgimport.Limits{}, fmt.Errorf("scene: %s SVG asset has no remaining cumulative %s budget", , .)
		}
		.(&, .)
	}
	return , nil
}

func ( *builder) ( string,  d2svgimport.Metrics) error {
	 := .options.Assets.SVGImportBudget
	 := maxInt(.ParsedElements, .EmittedElements)
	 := maxInt(.ParsedPathCommands, .EmittedPathCommands)
	type  struct {
		   string
		 int
		   int
		  int
	}
	 := []{
		{"source bytes", .SourceBytes, .svgSourceBytes, .MaxSourceBytes},
		{"element count", , .svgElements, .MaxElements},
		{"attribute count", .ParsedAttributes, .svgAttributes, .MaxAttributes},
		{"attribute bytes", .ParsedAttributeBytes, .svgAttributeBytes, .MaxAttributeBytes},
		{"path command count", , .svgCommands, .MaxPathCommands},
		{"transform function count", .ParsedTransformFuncs, .svgTransforms, .MaxTransformFunctions},
		{"declared resource count", .DeclaredResources, .svgDeclaredResources, .MaxDeclaredResources},
		{"expanded use instance count", .ExpandedUseInstances, .svgExpandedUseInstances, .MaxExpandedUseInstances},
	}
	for ,  := range  {
		if . > .-. {
			return fmt.Errorf("scene: %s SVG asset causes cumulative %s to exceed limit %d", , ., .)
		}
	}
	.svgSourceBytes += .SourceBytes
	.svgElements += 
	.svgAttributes += .ParsedAttributes
	.svgAttributeBytes += .ParsedAttributeBytes
	.svgCommands += 
	.svgTransforms += .ParsedTransformFuncs
	.svgDeclaredResources += .DeclaredResources
	.svgExpandedUseInstances += .ExpandedUseInstances
	return nil
}

func minInt(,  int) int {
	if  <  {
		return 
	}
	return 
}

func maxInt(,  int) int {
	if  >  {
		return 
	}
	return 
}

func ( *builder) (, ,  string,  d2scene.Box,  float64,  bool) (*d2scene.Node, error) {
	,  := .resolveImageAsset(, )
	if  != nil {
		return nil, 
	}
	 := d2scene.NewNode(d2scene.Image{Asset: , Box: , Aspect: defaultImageAspect})
	.ID = 
	 = clampBorderRadius(, .Width, .Height)
	if  > 0 {
		if  {
			.Clip = imageShapeClip(, )
		} else {
			.Clip = roundedBoxClip(, )
		}
	}
	return , nil
}

func ( *builder) ( d2target.Shape) (*d2scene.Node, error) {
	 := fmt.Sprintf("shape %q", .ID)
	if .Icon == nil {
		return nil, invalidField(, "icon", nil, "must identify an image source for an image shape")
	}
	return .buildImageNode(, .ID+":image", .Icon.String(), d2scene.Box{
		X: float64(.Pos.X), Y: float64(.Pos.Y),
		Width: float64(.Width), Height: float64(.Height),
	}, float64(.IconBorderRadius), true)
}

func ( *builder) ( d2target.Shape,  bool) (*d2scene.Node, error) {
	if .Icon == nil || .Type == d2target.ShapeImage {
		return nil, nil
	}
	 := fmt.Sprintf("shape %q", .ID)
	 := label.FromString(.IconPosition)
	if !.IsShapePosition() {
		return nil, invalidField(, "iconPosition", .IconPosition, "must identify a supported icon position")
	}
	var  *geo.Box
	if  {
		// Class and SQL table rendering uses the raw target box, including for
		// positions that ordinary shapes place in an inner box.
		 = geo.NewBox(
			geo.NewPoint(float64(.Pos.X), float64(.Pos.Y)),
			float64(.Width), float64(.Height),
		)
	} else {
		 := targetGeometry()
		if .IsOutside() {
			 = .GetBox()
		} else {
			 = .GetInnerBox()
		}
	}
	 := d2target.GetIconSize(, .IconPosition)
	 := .GetPointOnBox(, label.PADDING, float64(), float64())
	if  == nil {
		return nil, invalidField(, "iconPosition", .IconPosition, "must identify a supported icon position")
	}
	 := float64(.IconBorderRadius)
	if  {
		// The dedicated class/table SVG paths do not apply IconBorderRadius.
		 = 0
	}
	return .buildImageNode(, .ID+":icon", .Icon.String(), d2scene.Box{
		X: .X, Y: .Y, Width: float64(), Height: float64(),
	}, , false)
}

func ( *builder) ( d2target.Connection) (*d2scene.Node, error) {
	if .Icon == nil {
		return nil, nil
	}
	 := fmt.Sprintf("connection %q", .ID)
	 := .GetIconPosition()
	if  == nil {
		return nil, invalidField(, "iconPosition", .IconPosition, "does not resolve on the route")
	}
	return .buildImageNode(, .ID+":icon", .Icon.String(), d2scene.Box{
		X: .X, Y: .Y,
		Width: d2target.DEFAULT_ICON_SIZE, Height: d2target.DEFAULT_ICON_SIZE,
	}, .IconBorderRadius, false)
}

func boxClip( d2scene.Box) *d2scene.Clip {
	return &d2scene.Clip{Path: d2scene.Path{Commands: []d2scene.PathCommand{
		d2scene.MoveTo(.X, .Y),
		d2scene.LineTo(.X+.Width, .Y),
		d2scene.LineTo(.X+.Width, .Y+.Height),
		d2scene.LineTo(.X, .Y+.Height),
		d2scene.ClosePath(),
	}}, Transform: d2scene.Identity()}
}

func roundedBoxClip( d2scene.Box,  float64) *d2scene.Clip {
	,  := .X, .Y
	,  := .X+.Width, .Y+.Height
	return &d2scene.Clip{Path: d2scene.Path{Commands: []d2scene.PathCommand{
		d2scene.MoveTo(+, ),
		d2scene.LineTo(-, ),
		d2scene.ArcTo(, , 0, false, true, , +),
		d2scene.LineTo(, -),
		d2scene.ArcTo(, , 0, false, true, -, ),
		d2scene.LineTo(+, ),
		d2scene.ArcTo(, , 0, false, true, , -),
		d2scene.LineTo(, +),
		d2scene.ArcTo(, , 0, false, true, +, ),
		d2scene.ClosePath(),
	}}, Transform: d2scene.Identity()}
}

// imageShapeClip uses smooth cubics whose first control is the current endpoint
// after each line, rather than true quarter-circle arcs.
func imageShapeClip( d2scene.Box,  float64) *d2scene.Clip {
	,  := .X, .Y
	,  := .X+.Width, .Y+.Height
	return &d2scene.Clip{Path: d2scene.Path{Commands: []d2scene.PathCommand{
		d2scene.MoveTo(, +),
		d2scene.LineTo(, +),
		d2scene.CubicTo(, +, , , +, ),
		d2scene.LineTo(-, ),
		d2scene.LineTo(-, ),
		d2scene.CubicTo(-, , , , , +),
		d2scene.LineTo(, -),
		d2scene.CubicTo(, -, , , -, ),
		d2scene.LineTo(+, ),
		d2scene.CubicTo(+, , , , , -),
		d2scene.LineTo(, +),
		d2scene.ClosePath(),
	}}, Transform: d2scene.Identity()}
}