package d2isometricimg

import (
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
)

const (
	maxSurfaceIcons         = 7000
	maxSurfaceIconPixels    = 8 << 20
	maxSurfaceIconDimension = 512
	maxSurfaceIconBytes     = 16 << 20
	maxSurfaceIconDecoded   = 64 << 20
)

type surfaceIconKey struct {
	source        [32]byte
	width, height int
	radius        uint64
}

type surfaceIconAsset struct {
	asset  d2scene.Asset
	assets map[d2scene.AssetID]d2scene.Asset
}

// surfaceIconPainter owns one render's immutable icon assets and textures. It
// is used serially while constructing meshes, before any animation frames.
// A supplied resolver is the only authority for external asset I/O. The
// default resolver is guarded to accept already embedded data URIs only.
type surfaceIconPainter struct {
	ctx                   context.Context
	resolver              *imageasset.Resolver
	dataOnly              bool
	count, dimension      int
	outputDensity         float64
	pixels, bytes, decode int64
	limits                d2svgimport.Limits
	remaining             d2scenebuild.SVGImportBudget
	sources               map[[32]byte]*surfaceIconAsset
	content               map[[32]byte]*surfaceIconAsset
	textures              map[surfaceIconKey]*image.RGBA
}

func newSurfaceIconPainter( context.Context,  int,  *d2scenebuild.AssetOptions) (*surfaceIconPainter, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	if  < 0 ||  > maxSurfaceIcons {
		return nil, fmt.Errorf("isometric surface icon count exceeds %d", maxSurfaceIcons)
	}
	 := &surfaceIconPainter{
		ctx: , count: max(1, ), dataOnly: true,
		limits:    d2svgimport.Limits{MaxBytes: 2 << 20, MaxDepth: 64, MaxElements: 20000, MaxAttributes: 80000, MaxAttributeBytes: 2 << 20, MaxPathCommands: 100000, MaxTransformFunctions: 40000, MaxUseDepth: 32, MaxResources: 4096},
		remaining: d2scenebuild.SVGImportBudget{MaxSourceBytes: maxSurfaceIconBytes, MaxElements: 100000, MaxAttributes: 400000, MaxAttributeBytes: 8 << 20, MaxPathCommands: 500000, MaxTransformFunctions: 200000, MaxDeclaredResources: 16000, MaxExpandedUseInstances: 16000},
		sources:   make(map[[32]byte]*surfaceIconAsset), content: make(map[[32]byte]*surfaceIconAsset), textures: make(map[surfaceIconKey]*image.RGBA),
	}
	.dimension = min(maxSurfaceIconDimension, int(math.Sqrt(float64(maxSurfaceIconPixels/.count))))
	if  != nil {
		.resolver = .Resolver
		.dataOnly = .Resolver == nil
		// Honor stricter caller limits while retaining native render ceilings.
		.limitTo(.SVGImportLimits, .SVGImportBudget)
	}
	if .resolver == nil {
		var  error
		.resolver,  = imageasset.New(imageasset.Options{Limits: imageasset.Limits{
			MaxFetchedBytes: 12 << 20, MaxEncodedBytes: 8 << 20, MaxDecompressedBytes: 8 << 20, MaxSVGBytes: int64(.limits.MaxBytes),
			MaxDecodedWidth: 8192, MaxDecodedHeight: 8192, MaxDecodedPixels: 8 << 20,
			MaxAssets: maxSurfaceIcons, MaxCumulativeEncodedBytes: maxSurfaceIconBytes, MaxCumulativeDecodedBytes: maxSurfaceIconDecoded,
		}})
		if  != nil {
			return nil, 
		}
	}
	return , nil
}

func iconLimit(,  int) int {
	if  > 0 {
		return min(, )
	}
	return 
}

func ( *surfaceIconPainter) ( float64) {
	.outputDensity = 
}

func ( *surfaceIconPainter) ( d2svgimport.Limits,  d2scenebuild.SVGImportBudget) {
	.limits.MaxBytes = iconLimit(.limits.MaxBytes, .MaxBytes)
	.limits.MaxDepth = iconLimit(.limits.MaxDepth, .MaxDepth)
	.limits.MaxElements = iconLimit(.limits.MaxElements, .MaxElements)
	.limits.MaxAttributes = iconLimit(.limits.MaxAttributes, .MaxAttributes)
	.limits.MaxAttributeBytes = iconLimit(.limits.MaxAttributeBytes, .MaxAttributeBytes)
	.limits.MaxPathCommands = iconLimit(.limits.MaxPathCommands, .MaxPathCommands)
	.limits.MaxTransformFunctions = iconLimit(.limits.MaxTransformFunctions, .MaxTransformFunctions)
	.limits.MaxUseDepth = iconLimit(.limits.MaxUseDepth, .MaxUseDepth)
	.limits.MaxResources = iconLimit(.limits.MaxResources, .MaxResources)
	.remaining.MaxSourceBytes = iconLimit(.remaining.MaxSourceBytes, .MaxSourceBytes)
	.remaining.MaxElements = iconLimit(.remaining.MaxElements, .MaxElements)
	.remaining.MaxAttributes = iconLimit(.remaining.MaxAttributes, .MaxAttributes)
	.remaining.MaxAttributeBytes = iconLimit(.remaining.MaxAttributeBytes, .MaxAttributeBytes)
	.remaining.MaxPathCommands = iconLimit(.remaining.MaxPathCommands, .MaxPathCommands)
	.remaining.MaxTransformFunctions = iconLimit(.remaining.MaxTransformFunctions, .MaxTransformFunctions)
	.remaining.MaxDeclaredResources = iconLimit(.remaining.MaxDeclaredResources, .MaxDeclaredResources)
	.remaining.MaxExpandedUseInstances = iconLimit(.remaining.MaxExpandedUseInstances, .MaxExpandedUseInstances)
}

// texture returns top-left-origin, premultiplied RGBA suitable for the native
// triangle rasterizer. Aspect fitting and transparent letterboxing happen in
// d2raster, including EXIF orientation and deterministic first animated frames.
func ( *surfaceIconPainter) ( *url.URL, ,  float64,  ...float64) (*image.RGBA, error) {
	if  := .ctx.Err();  != nil {
		return nil, 
	}
	if  == nil || !captionFinite(, ) ||  <= 0 ||  <= 0 {
		return nil, fmt.Errorf("isometric surface icon requires a source and positive finite dimensions")
	}
	 := .String()
	if len() > 12<<20 {
		return nil, fmt.Errorf("isometric surface icon source exceeds byte limit")
	}
	if .dataOnly && !strings.EqualFold(.Scheme, "data") {
		return nil, fmt.Errorf("isometric surface icon requires an explicit asset resolver for external sources")
	}
	 := sha256.Sum256([]byte())
	,  := surfaceIconDimensions(, , .dimension)
	if .outputDensity > 0 && captionFinite(.outputDensity) {
		,  = surfaceTextureDimensionsAtDensity(, , 4096, maxSurfaceIconPixels/.count, .outputDensity)
	}
	 := 0.
	if len() > 0 {
		 = [0]
	}
	if !captionFinite() ||  < 0 {
		return nil, fmt.Errorf("isometric surface icon has invalid border radius")
	}
	 = min(, min(, )/2) * min(float64()/, float64()/)
	 := surfaceIconKey{source: , width: , height: , radius: math.Float64bits()}
	if  := .textures[];  != nil {
		return , nil
	}
	 := int64() * int64()
	if len(.textures) >= .count || .pixels+ > maxSurfaceIconPixels {
		return nil, fmt.Errorf("isometric surface icon textures exceed the render budget")
	}
	,  := .resolve(, )
	if  != nil {
		return nil, 
	}
	 := d2scene.NewNode(d2scene.Image{Asset: "surface-icon", Box: d2scene.Box{Width: float64(), Height: float64()}, Aspect: d2scene.AspectRatio{Align: d2scene.AlignXMidYMid, Fit: d2scene.AspectMeet}})
	if  > 0 {
		.Clip = roundedSurfaceIconClip(float64(), float64(), )
	}
	 := d2scene.NewDocument(d2scene.Box{Width: float64(), Height: float64()}, )
	for ,  := range .assets {
		.Assets[] = 
	}
	.Assets["surface-icon"] = .asset
	,  := d2raster.Render(.ctx, , d2raster.FrameOptions{
		Scale: 1, MaxWidth: , MaxHeight: , MaxPixels: ,
		MaxNodes: 100000, MaxDepth: 128, MaxPathCommands: 500000,
		MaxAnimationTracks: 1, MaxAnimationKeyframes: 1,
		MaxAssets: 4097, MaxAssetBytes: maxSurfaceIconBytes, MaxDecodedAssetBytes: maxSurfaceIconDecoded, MaxImportDepth: 4,
		MaxOffscreenBytes: 32 << 20, MaxEvenOddClipWork: 32 << 20, MaxScanlineWork: 100_000_000,
	})
	if  != nil {
		return nil, fmt.Errorf("isometric surface icon rasterization: %w", )
	}
	 := image.NewRGBA(.Bounds())
	// Copy by rows so cancellation also bounds the alpha conversion pass.
	for  := 0;  < ; ++ {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		draw.Draw(, image.Rect(0, , , +1), , image.Pt(0, ), draw.Src)
	}
	.pixels += 
	.textures[] = 
	if  := retainNativeVectorSurface(.ctx, , );  != nil {
		return nil, 
	}
	return , nil
}

func surfaceIconDimensions(,  float64,  int) (int, int) {
	// Normalize before multiplication, keeping even extreme accepted scene
	// aspect ratios finite. At least one sample remains on a very narrow face.
	 :=  / max(, )
	 :=  / max(, )
	 := min(float64(), max(1, max(, )*160))
	return max(1, int(math.Round(*))), max(1, int(math.Round(*)))
}

func ( *surfaceIconPainter) ( [32]byte,  string) (*surfaceIconAsset, error) {
	if  := .sources[];  != nil {
		return , nil
	}
	if len(.sources) >= .count {
		return nil, fmt.Errorf("isometric surface icon sources exceed the render budget")
	}
	,  := .resolver.Resolve(.ctx, )
	if  != nil {
		return nil, fmt.Errorf("isometric surface icon: %w", )
	}
	if  == nil {
		return nil, fmt.Errorf("isometric surface icon resolver returned no resource")
	}
	if .EncodedBytes() > 8<<20 || .DecodedBytes() > 32<<20 || .bytes+.EncodedBytes() > maxSurfaceIconBytes || .decode+.DecodedBytes() > maxSurfaceIconDecoded {
		return nil, fmt.Errorf("isometric surface icon assets exceed byte budget")
	}
	,  := .BytesContext(.ctx)
	if  != nil {
		return nil, 
	}
	 := sha256.Sum256()
	if  := .content[];  != nil {
		.sources[] = 
		return , nil
	}
	 := &surfaceIconAsset{}
	,  := int64(0), int64(0)
	switch .Kind() {
	case imageasset.KindRaster:
		.asset = d2scene.RasterAsset{MIMEType: .MIMEType(), Data: , PixelWidth: .PixelWidth(), PixelHeight: .PixelHeight(), DecodedBytes: .DecodedBytes()}
	case imageasset.KindSVG:
		,  := .importLimits()
		if  != nil {
			return nil, 
		}
		// The importer never resolves files or URLs referenced within an SVG.
		,  := d2svgimport.ImportNode(.ctx, "surface icon", , )
		if  != nil {
			return nil, fmt.Errorf("isometric surface icon SVG: %w", )
		}
		,  = int64(.Metrics.EmbeddedRasterBytes), .Metrics.DecodedRasterBytes
		if .bytes+.EncodedBytes()+ > maxSurfaceIconBytes || .decode+.DecodedBytes()+ > maxSurfaceIconDecoded {
			return nil, fmt.Errorf("isometric surface icon embedded assets exceed byte budget")
		}
		.reserveImport(.Metrics)
		 := d2scene.NewNode(nil)
		.Transform = .ViewportTransform
		.Children = []*d2scene.Node{.Root}
		 := d2scene.NewNode(nil)
		.Clip = &d2scene.Clip{Transform: d2scene.Identity(), Path: d2scene.Path{Commands: []d2scene.PathCommand{d2scene.MoveTo(0, 0), d2scene.LineTo(.Width, 0), d2scene.LineTo(.Width, .Height), d2scene.LineTo(0, .Height), d2scene.ClosePath()}}}
		.Children = []*d2scene.Node{}
		.asset = d2scene.VectorAsset{ViewBox: d2scene.Box{Width: .Width, Height: .Height}, Root: }
		.assets = .Assets
	default:
		return nil, fmt.Errorf("isometric surface icon has unsupported resource kind")
	}
	.bytes += .EncodedBytes() + 
	.decode += .DecodedBytes() + 
	.sources[], .content[] = , 
	return , nil
}

func ( *surfaceIconPainter) () (d2svgimport.Limits, error) {
	,  := .limits, .remaining
	.MaxBytes = min(.MaxBytes, .MaxSourceBytes)
	.MaxElements = min(.MaxElements, .MaxElements)
	.MaxAttributes = min(.MaxAttributes, .MaxAttributes)
	.MaxAttributeBytes = min(.MaxAttributeBytes, .MaxAttributeBytes)
	.MaxPathCommands = min(.MaxPathCommands, .MaxPathCommands)
	.MaxTransformFunctions = min(.MaxTransformFunctions, .MaxTransformFunctions)
	.MaxResources = min(.MaxResources, min(.MaxDeclaredResources, .MaxExpandedUseInstances))
	if .MaxBytes <= 0 || .MaxElements <= 0 || .MaxAttributes <= 0 || .MaxAttributeBytes <= 0 || .MaxPathCommands <= 0 || .MaxTransformFunctions <= 0 || .MaxResources <= 0 {
		return , fmt.Errorf("isometric surface icons exceed the SVG import budget")
	}
	return , nil
}

func ( *surfaceIconPainter) ( d2svgimport.Metrics) {
	.remaining.MaxSourceBytes -= .SourceBytes
	.remaining.MaxElements -= max(.ParsedElements, .EmittedElements)
	.remaining.MaxAttributes -= .ParsedAttributes
	.remaining.MaxAttributeBytes -= .ParsedAttributeBytes
	.remaining.MaxPathCommands -= max(.ParsedPathCommands, .EmittedPathCommands)
	.remaining.MaxTransformFunctions -= .ParsedTransformFuncs
	.remaining.MaxDeclaredResources -= .DeclaredResources
	.remaining.MaxExpandedUseInstances -= .ExpandedUseInstances
}

// surfaceIconLayout partitions an existing printable face without changing
// the physical shape footprint. Outside icon positions are projected onto the
// corresponding face edge: icon artwork never floats beyond its parent mesh.
// The returned rectangles are disjoint and inherit the real surface angle.
func surfaceIconLayout( labelSurface,  d2target.Shape,  float64,  string) (,  labelSurface) {
	 = 
	if .Icon == nil || !captionFinite(.width, .depth, .angle, .center.X, .center.Y, .center.Z) || .width <= 0 || .depth <= 0 {
		return labelSurface{}, 
	}
	if  <= 0 || !captionFinite() {
		 = .01
	}
	 := .Type == d2target.ShapeSQLTable || .Type == d2target.ShapeClass || len(.Columns)+len(.Fields)+len(.Methods) > 0
	if .Label == "" && ! {
		return , labelSurface{}
	}
	if  {
		// A table's row coordinates are the attachment contract. Its icon uses
		// the existing header; the row texture keeps the complete source face.
		 := .depth / float64(1+len(.Columns))
		if .Type == d2target.ShapeClass {
			 = max(2*.depth/float64(2+len(.Fields)+len(.Methods)), float64(.LabelHeight)*+2*label.PADDING*)
		}
		 := min(label.PADDING*, min(.width, )*.1)
		 := min(float64(d2target.DEFAULT_ICON_SIZE)*, min(.width-2*, -2*))
		 = max(0, )
		 = 
		.width, .depth = , 
		 := (.width-)/2 - 
		if strings.Contains(.IconPosition, "LEFT") {
			 = -
		}
		 := -.depth/2 + /2
		.center = nadd(.center, nv(math.Cos(.angle)*-math.Sin(.angle)*, 0, math.Sin(.angle)*+math.Cos(.angle)*))
		return , 
	}
	 := .IconPosition
	 := strings.Contains(, "LEFT") || strings.Contains(, "RIGHT") ||  == "board" ||  == "edge"
	if  == "" || strings.Contains(, "MIDDLE_CENTER") {
		// Prefer the layout's already allocated whitespace over a fixed chip
		// subdivision; short, wide modules usually reserve a left icon lane.
		 := .width - float64(.LabelWidth)*
		 := .depth - float64(.LabelHeight)*
		 =  >=  ||  == "board" ||  == "edge"
	}
	if .Type == d2target.ShapeImage {
		 = false
		 = "INSIDE_TOP_CENTER"
	}
	if  {
		// Structured rows need the full face width, including when their
		// authored header is empty. Reserve only a compact top icon strip.
		 = false
		 = "INSIDE_TOP_CENTER"
	}
	,  = , 
	 := min(.08, min(.width, .depth)*.08)
	// The compiled label metrics take priority over a decorative icon minimum.
	// A little print padding covers native glyph overhang and texture insets.
	 := float64(.LabelWidth)**1.06 + min(.04, .width*.04)
	 := float64(.LabelHeight)**1.06 + min(.04, .depth*.04)
	 := .width -  - 
	if  &&  < min(.depth*.22, .width*.12) {
		 = false
	}
	 := func(,  float64) Vec {
		return nadd(.center, nv(math.Cos(.angle)*-math.Sin(.angle)*, 0, math.Sin(.angle)*+math.Cos(.angle)*))
	}
	if  {
		 := min(.depth, min(.width*.48, ))
		if  == "board" ||  == "edge" {
			 = min(, .width*.28)
		}
		.width, .depth = , min(, .depth)
		.width = .width -  - 
		 := 1.
		if strings.Contains(, "RIGHT") {
			 = -1
		}
		.center = (-*(.width-)/2, 0)
		if strings.Contains(, "TOP") {
			.center = (-*(.width-)/2, -(.depth-.depth)/2)
		} else if strings.Contains(, "BOTTOM") {
			.center = (-*(.width-)/2, (.depth-.depth)/2)
		}
		.center = (*(+)/2, 0)
	} else {
		 := min(.width, max(.depth*.14, .depth--))
		 = min(, .depth*.72)
		if  {
			 = min(, min(.depth*.2, float64(d2target.DEFAULT_ICON_SIZE)*))
		}
		.width, .depth = min(, .width), 
		if .Type == d2target.ShapeImage {
			// A wide image gets the whole image panel, not a square badge.
			.width = .width
		}
		.depth = .depth -  - 
		 := 1.
		if strings.Contains(, "BOTTOM") {
			 = -1
		}
		.center = (0, -*(.depth-)/2)
		.center = (0, *(+)/2)
	}
	return , 
}

// Use vector clipping before rasterization so transparent and antialiased
// image corners retain correct premultiplied alpha on the physical surface.
func roundedSurfaceIconClip(, ,  float64) *d2scene.Clip {
	 = min(, min(, )/2)
	 :=  * .5522847498307936
	return &d2scene.Clip{Transform: d2scene.Identity(), Path: d2scene.Path{Commands: []d2scene.PathCommand{
		d2scene.MoveTo(, 0), d2scene.LineTo(-, 0), d2scene.CubicTo(-+, 0, , -, , ),
		d2scene.LineTo(, -), d2scene.CubicTo(, -+, -+, , -, ),
		d2scene.LineTo(, ), d2scene.CubicTo(-, , 0, -+, 0, -),
		d2scene.LineTo(0, ), d2scene.CubicTo(0, -, -, 0, , 0), d2scene.ClosePath(),
	}}}
}