package d2isometricimg

import (
	
	
	
	
	
	

	
	
	
	
	
)

// Page combines the native PNG with annotations in final image pixels. Writers
// can resize the image and annotations together for a PDF page or PPTX slide.
type Page struct {
	PNG           []byte
	Links         []d2scene.LinkRegion
	Width, Height int
}

type nativeLink struct {
	points [4]Vec
	region d2scene.LinkRegion
}

// RenderPage renders one compiled board and returns its image and bounded
// annotation regions for D2's native PDF/PPTX writers.
func ( context.Context,  *d2target.Diagram,  *Options) (*Page, error) {
	,  := normalize()
	if  != nil {
		return nil, 
	}
	if .Format != PNG {
		return nil, fmt.Errorf("isometric page export requires PNG rendering")
	}
	if .LinkBudget == nil {
		.LinkBudget = &d2scenebuild.LinkBudget{MaxRegions: 4096, MaxStringBytes: 1 << 20}
	}
	if  == nil {
		 = context.Background()
	}
	if  := preflightPageMetadata(, , *.LinkBudget);  != nil {
		return nil, 
	}
	,  := openCapture(, , )
	if  != nil {
		return nil, 
	}
	defer .close()
	,  := .frame(0, false)
	if  != nil {
		return nil, 
	}
	,  := projectPageLinks(.ctx, .scene)
	if  != nil {
		return nil, 
	}
	return &Page{PNG: , Links: , Width: .opts.Width, Height: .opts.Height}, nil
}

func preflightPageMetadata( context.Context,  *d2target.Diagram,  d2scenebuild.LinkBudget) error {
	if  == nil {
		return fmt.Errorf("isometric page requires a diagram")
	}
	,  := 0, 0
	 := func(, ,  string) error {
		if  := .Err();  != nil {
			return 
		}
		if  != "" &&  == "" {
			return fmt.Errorf("isometric page prettyLink requires a non-empty link")
		}
		if  == "" &&  == "" {
			return nil
		}
		++
		if  > .MaxRegions {
			return fmt.Errorf("isometric page link regions exceed %d", .MaxRegions)
		}
		for ,  := range []string{, } {
			if len() > .MaxStringBytes- {
				return fmt.Errorf("isometric page link metadata exceeds %d bytes", .MaxStringBytes)
			}
			 += len()
			if  := validatePageLinkText(, );  != nil {
				return 
			}
		}
		if  := validateNativeLink();  != nil {
			return 
		}
		if  != "" &&  != "" {
			if ,  := url.ParseRequestURI();  == nil && .Host != "" {
				return fmt.Errorf("isometric page tooltip must not be a URL when link is also set")
			}
		}
		return nil
	}
	for ,  := range .Shapes {
		if  := (.Link, .Tooltip, .PrettyLink);  != nil {
			return 
		}
	}
	for ,  := range .Connections {
		if  := (.Link, .Tooltip, .PrettyLink);  != nil {
			return 
		}
		if .Link != "" || .Tooltip != "" {
			if .Label == "" && .Icon == nil {
				return fmt.Errorf("isometric page connection %q needs a label or icon for its metadata hit region", .ID)
			}
		}
	}
	return .Err()
}

func ( *meshBuilder) ( [4]Vec,  d2scene.LinkRegion) {
	if .options.links == nil || .err != nil || .URL == "" && .Target == "" && .Tooltip == "" {
		return
	}
	 := .options.links
	if len(.links) >= .MaxRegions {
		.err = fmt.Errorf("isometric page link regions exceed %d", .MaxRegions)
		return
	}
	 := len(.URL) + len(.Target) + len(.Tooltip)
	if  > .MaxStringBytes-.linkBytes {
		.err = fmt.Errorf("isometric page link metadata exceeds %d bytes", .MaxStringBytes)
		return
	}
	for ,  := range []string{.URL, .Target, .Tooltip} {
		if  := validatePageLinkText(.ctx, );  != nil {
			.err = 
			return
		}
	}
	if  := validateNativeLink(.URL);  != nil {
		.err = 
		return
	}
	for ,  := range  {
		if !captionFinite(.X, .Y, .Z) {
			.err = fmt.Errorf("isometric page link bounds are non-finite")
			return
		}
	}
	.linkBytes += 
	.links = append(.links, nativeLink{points: , region: })
}

// Match the existing native PDF/PPTX metadata contract. Reject unsupported
// characters before the XML writer could replace them in a destination.
func validatePageLinkText( context.Context,  string) error {
	for  := 0;  < len(); {
		if  := .Err();  != nil {
			return 
		}
		,  := utf8.DecodeRuneInString([:])
		if  == utf8.RuneError &&  == 1 {
			return fmt.Errorf("isometric page link metadata must be valid UTF-8")
		}
		if !( == '\t' ||  == '\n' ||  == '\r' ||  >= 0x20 &&  <= 0xd7ff ||  >= 0xe000 &&  <= 0xfffd ||  >= 0x10000 &&  <= 0x10ffff) {
			return fmt.Errorf("isometric page link metadata contains a character forbidden by XML 1.0")
		}
		 += 
	}
	return .Err()
}

func validateNativeLink( string) error {
	if textmeasure.IsDangerousLink() {
		return fmt.Errorf("isometric link uses an unsafe URL scheme")
	}
	return nil
}

func ( *meshBuilder) (,  string) bool {
	if .options.vector {
		return true // SVG preserves arbitrary source tooltip text in a title.
	}
	if  != "" &&  != "" {
		if ,  := url.ParseRequestURI();  == nil && .Host != "" {
			.err = fmt.Errorf("isometric page tooltip must not be a URL when link is also set")
			return false
		}
	}
	return true
}

func nativeLinkDestination(,  string) d2scene.LinkRegion {
	 := d2scene.LinkRegion{URL: , Tooltip: }
	,  := d2parser.ParseKey()
	if  != nil || len(.Path) == 0 || len(.Path)%2 == 0 || .Path[0].Unbox().ScalarString() != "root" {
		return 
	}
	// Semantic board targets alternate a board collection and its name. An
	// already-resolved export such as root.svg is a relative URL, not a target.
	for  := 1;  < len(.Path);  += 2 {
		switch .Path[].Unbox().ScalarString() {
		case "layers", "scenarios", "steps":
		default:
			return 
		}
	}
	.URL, .Target = "", 
	return 
}

func surfaceCorners( labelSurface) [4]Vec {
	,  := nv(math.Cos(.angle), 0, math.Sin(.angle)), nv(-math.Sin(.angle), 0, math.Cos(.angle))
	 := func(,  float64) Vec { return nadd(.center, nadd(nmul(, *.width), nmul(, *.depth))) }
	return [4]Vec{(-.5, -.5), (.5, -.5), (.5, .5), (-.5, .5)}
}

func ( *meshBuilder) (,  string,  labelSurface) {
	if .options.links == nil ||  == "" &&  == "" || .width <= 0 || .depth <= 0 {
		return
	}
	if !.validateTypedLink(, ) {
		return
	}
	.addNativeLink(surfaceCorners(), nativeLinkDestination(, ))
}

func ( *meshBuilder) (,  string,  []Triangle) {
	if .options.links == nil ||  == "" &&  == "" || len() == 0 {
		return
	}
	if !.validateTypedLink(, ) {
		return
	}
	var  projectedExtent
	.mesh()
	 := nativeCameraAxes()
	 := func(,  float64) Vec { return nadd(nmul(.right, ), nadd(nmul(.up, ), nmul(.direction, .maxZ))) }
	.addNativeLink([4]Vec{(.minX, .minY), (.maxX, .minY), (.maxX, .maxY), (.minX, .maxY)}, nativeLinkDestination(, ))
}

// Map source-document link boxes through the same meet/stretch viewport used
// for its texture, and then through the physical surface's orientation.
func ( *meshBuilder) ( *d2scene.Document,  labelSurface) {
	if .options.links == nil ||  == nil || len(.Links) == 0 || .err != nil {
		return
	}
	if !captionFinite(.ViewBox.Width, .ViewBox.Height, .width, .depth) || .ViewBox.Width <= 0 || .ViewBox.Height <= 0 || .width <= 0 || .depth <= 0 {
		.err = fmt.Errorf("isometric page document link viewport is invalid")
		return
	}
	,  := .width/.ViewBox.Width, .depth/.ViewBox.Height
	,  := 0., 0.
	if .ViewportFit == d2scene.ViewportMeet {
		// Surface aspect equals the raster viewport aspect except for rounded
		// texture dimensions; use logical pixels to retain its exact letterbox.
		,  := .LogicalWidth, .LogicalHeight
		if  <= 0 ||  <= 0 {
			,  = .width, .depth
		}
		 := min(/.ViewBox.Width, /.ViewBox.Height)
		,  = *.width/, *.depth/
		if .ViewportAlign == d2scene.ViewportAlignXMidYMid {
			,  = (.width-.ViewBox.Width*)/2, (.depth-.ViewBox.Height*)/2
		}
	}
	,  := nv(math.Cos(.angle), 0, math.Sin(.angle)), nv(-math.Sin(.angle), 0, math.Cos(.angle))
	 := func(,  float64) Vec { return nadd(.center, nadd(nmul(, -.width/2), nmul(, -.depth/2))) }
	for ,  := range .Links {
		,  := (.Box.X-.ViewBox.X)*+, (.Box.Y-.ViewBox.Y)*+
		,  := +.Box.Width*, +.Box.Height*
		, , ,  = max(0., ), max(0., ), min(.width, ), min(.depth, )
		if  <=  ||  <=  {
			continue
		}
		.addNativeLink([4]Vec{(, ), (, ), (, ), (, )}, )
		if .err != nil {
			return
		}
	}
}

func projectPageLinks( context.Context,  *nativeScene) ([]d2scene.LinkRegion, error) {
	 := make([]d2scene.LinkRegion, 0, len(.links))
	 := cameraAtResolution(.raster.camera, .width, .height)
	for ,  := range .links {
		if  := .Err();  != nil {
			return nil, 
		}
		, , ,  := math.Inf(1), math.Inf(1), math.Inf(-1), math.Inf(-1)
		for ,  := range .points {
			 := .project()
			, , ,  = min(, .x), min(, .y), max(, .x), max(, .y)
		}
		, , ,  = max(0., ), max(0., ), min(float64(.width), ), min(float64(.height), )
		if  <=  ||  <=  {
			continue
		}
		 := .region
		.Box = d2scene.Box{X: , Y: , Width:  - , Height:  - }
		 = append(, )
	}
	// Broad container/shape regions precede smaller inline links so document
	// viewers can activate the most specific overlapping annotation.
	sort.SliceStable(, func(,  int) bool {
		return [].Box.Width*[].Box.Height > [].Box.Width*[].Box.Height
	})
	return , nil
}