package d2lib

import (
	
	
	
	

	
	
)

// layoutInputSignature is an exact, length-delimited encoding of the graph
// fields consumed by D2's layout pipeline. It is captured after theme and text
// dimensions have been applied, but before layout mutates geometry.
//
// Deliberately omitted fields are render-only metadata (colors, opacity,
// tooltips, links, classes, and AST references). Text styling is represented by
// the resulting label dimensions. The two visual modifiers that affect routing
// bounds, 3d and multiple, are included explicitly.
type layoutInputSignature string

type layoutSignatureBuilder struct {
	bytes.Buffer
	scratch [8]byte
}

func newLayoutInputSignature( *d2graph.Graph,  string) (layoutInputSignature, bool, error) {
	if !supportsLayoutReuse(, ) {
		return "", false, nil
	}

	 := &layoutSignatureBuilder{}
	.writeString()
	.writeInt(.RootLevel)
	if  := .writeObject(.Root);  != nil {
		return "", false, 
	}
	.writeInt(len(.Objects))
	for ,  := range .Objects {
		if  := .writeObject();  != nil {
			return "", false, 
		}
	}
	.writeInt(len(.Edges))
	for ,  := range .Edges {
		if  := .writeEdge();  != nil {
			return "", false, 
		}
	}
	return layoutInputSignature(.String()), true, nil
}

// Sequence, grid, and constant-near layouts can synthesize or temporarily
// restructure graph topology. Geometry-only replay is intentionally disabled
// for those graph types until their generated topology can be snapshotted too.
func supportsLayoutReuse( *d2graph.Graph,  string) bool {
	// The built-in Dagre and ELK adapters consume the geometry inputs encoded
	// below. Custom layout functions receive the whole graph and may choose to
	// make render-only styling affect geometry, so they remain opt-out by
	// default until the layout API exposes an explicit reuse capability.
	if  != "dagre" &&  != "elk" {
		return false
	}
	 := make([]*d2graph.Object, 0, len(.Objects)+1)
	 = append(, .Root)
	 = append(, .Objects...)
	for ,  := range  {
		if .IsSequenceDiagram() || .IsGridDiagram() || .IsConstantNear() {
			return false
		}
	}
	return true
}

func ( *layoutSignatureBuilder) ( *d2graph.Object) error {
	.writeString(.AbsID())
	if .Parent == nil {
		.writeOptionalString(nil)
	} else {
		 := .Parent.AbsID()
		.writeOptionalString(&)
	}
	.writeInt(len(.ChildrenArray))
	for ,  := range .ChildrenArray {
		.writeString(.AbsID())
	}
	.writeInt(len(.Children))
	.writeBool(.IsContainer())
	.writeBox(.Box)
	.writeOptionalString(.LabelPosition)
	.writeOptionalString(.IconPosition)
	.writeOptionalFloat(.ContentAspectRatio)
	.writeBool(.HasLabel())
	.writeBool(.HasIcon())
	.writeBool(.Icon != nil)
	.writeBool(.Is3D())
	.writeBool(.IsMultiple())

	if  := .writeJSON(.Class);  != nil {
		return 
	}
	if  := .writeJSON(.SQLTable);  != nil {
		return 
	}
	.writeAttributes(&.Attributes)
	return nil
}

func ( *layoutSignatureBuilder) ( *d2graph.Edge) error {
	.writeString(.AbsID())
	.writeString(.Src.AbsID())
	.writeString(.Dst.AbsID())
	.writeInt(.Index)
	.writeOptionalInt(.SrcTableColumnIndex)
	.writeOptionalInt(.DstTableColumnIndex)
	.writeBool(.SrcArrow)
	.writeBool(.DstArrow)
	.writeBool(.IsCurve)
	.writePoints(.Route)
	.writeOptionalString(.LabelPosition)
	.writeOptionalFloat(.LabelPercentage)
	.writeAttributes(&.Attributes)

	.writeBool(.SrcArrowhead != nil)
	if .SrcArrowhead != nil {
		.writeAttributes(.SrcArrowhead)
	}
	.writeBool(.DstArrowhead != nil)
	if .DstArrowhead != nil {
		.writeAttributes(.DstArrowhead)
	}
	return nil
}

func ( *layoutSignatureBuilder) ( *d2graph.Attributes) {
	.writeBool(.Label.Value != "")
	.writeInt(.LabelDimensions.Width)
	.writeInt(.LabelDimensions.Height)
	.writeString(.Shape.Value)
	.writeString(.Direction.Value)
	.writeStrings(.Constraint)
	.writeString(.Language)
	.writeOptionalScalar(.WidthAttr)
	.writeOptionalScalar(.HeightAttr)
	.writeOptionalScalar(.Top)
	.writeOptionalScalar(.Left)
	.writeOptionalScalar(.GridRows)
	.writeOptionalScalar(.GridColumns)
	.writeOptionalScalar(.GridGap)
	.writeOptionalScalar(.VerticalGap)
	.writeOptionalScalar(.HorizontalGap)
	.writeOptionalScalar(.LabelPosition)
	.writeOptionalScalar(.IconPosition)

	if .NearKey == nil {
		.writeBool(false)
	} else {
		.writeBool(true)
		.writeStrings(d2graph.Key(.NearKey))
	}
}

func ( *layoutSignatureBuilder) ( any) error {
	,  := json.Marshal()
	if  != nil {
		return 
	}
	.writeBytes()
	return nil
}

func ( *layoutSignatureBuilder) ( []byte) {
	.writeUint64(uint64(len()))
	_, _ = .Write()
}

func ( *layoutSignatureBuilder) ( string) {
	.writeBytes([]byte())
}

func ( *layoutSignatureBuilder) ( []string) {
	.writeInt(len())
	for ,  := range  {
		.writeString()
	}
}

func ( *layoutSignatureBuilder) ( bool) {
	if  {
		_ = .WriteByte(1)
	} else {
		_ = .WriteByte(0)
	}
}

func ( *layoutSignatureBuilder) ( int) {
	.writeUint64(uint64())
}

func ( *layoutSignatureBuilder) ( uint64) {
	binary.LittleEndian.PutUint64(.scratch[:], )
	_, _ = .Write(.scratch[:])
}

func ( *layoutSignatureBuilder) ( float64) {
	.writeUint64(math.Float64bits())
}

func ( *layoutSignatureBuilder) ( *float64) {
	.writeBool( != nil)
	if  != nil {
		.writeFloat(*)
	}
}

func ( *layoutSignatureBuilder) ( *int) {
	.writeBool( != nil)
	if  != nil {
		.writeInt(*)
	}
}

func ( *layoutSignatureBuilder) ( *string) {
	.writeBool( != nil)
	if  != nil {
		.writeString(*)
	}
}

func ( *layoutSignatureBuilder) ( *d2graph.Scalar) {
	.writeBool( != nil)
	if  != nil {
		.writeString(.Value)
	}
}

func ( *layoutSignatureBuilder) ( *geo.Box) {
	.writeBool( != nil)
	if  == nil {
		return
	}
	.writePoint(.TopLeft)
	.writeFloat(.Width)
	.writeFloat(.Height)
}

func ( *layoutSignatureBuilder) ( *geo.Point) {
	.writeBool( != nil)
	if  != nil {
		.writeFloat(.X)
		.writeFloat(.Y)
	}
}

func ( *layoutSignatureBuilder) ( []*geo.Point) {
	.writeBool( != nil)
	if  == nil {
		return
	}
	.writeInt(len())
	for ,  := range  {
		.writePoint()
	}
}

type layoutGeometry struct {
	signature layoutInputSignature
	root      objectGeometry
	objects   map[string]objectGeometry
	edges     map[string]edgeGeometry
}

type objectGeometry struct {
	box           *geo.Box
	labelPosition *string
	iconPosition  *string
}

type edgeGeometry struct {
	route           []*geo.Point
	labelPosition   *string
	labelPercentage *float64
	isCurve         bool
}

func captureLayoutGeometry( *d2graph.Graph,  layoutInputSignature) *layoutGeometry {
	 := &layoutGeometry{
		signature: ,
		root:      captureObjectGeometry(.Root),
		objects:   make(map[string]objectGeometry, len(.Objects)),
		edges:     make(map[string]edgeGeometry, len(.Edges)),
	}
	for ,  := range .Objects {
		.objects[.AbsID()] = captureObjectGeometry()
	}
	for ,  := range .Edges {
		.edges[.AbsID()] = captureEdgeGeometry()
	}
	return 
}

func captureObjectGeometry( *d2graph.Object) objectGeometry {
	return objectGeometry{
		box:           copyBox(.Box),
		labelPosition: copyString(.LabelPosition),
		iconPosition:  copyString(.IconPosition),
	}
}

func captureEdgeGeometry( *d2graph.Edge) edgeGeometry {
	return edgeGeometry{
		route:           copyPoints(.Route),
		labelPosition:   copyString(.LabelPosition),
		labelPercentage: copyFloat(.LabelPercentage),
		isCurve:         .IsCurve,
	}
}

// apply replays only geometry and layout-selected label/icon placement. Every
// structural key must be present; otherwise the caller falls back to layout.
func ( *layoutGeometry) ( *d2graph.Graph,  layoutInputSignature) bool {
	if  == nil || .signature !=  ||
		len(.objects) != len(.Objects) || len(.edges) != len(.Edges) {
		return false
	}
	for ,  := range .Objects {
		if ,  := .objects[.AbsID()]; ! {
			return false
		}
	}
	for ,  := range .Edges {
		if ,  := .edges[.AbsID()]; ! {
			return false
		}
	}

	applyObjectGeometry(.Root, .root)
	for ,  := range .Objects {
		applyObjectGeometry(, .objects[.AbsID()])
	}
	for ,  := range .Edges {
		applyEdgeGeometry(, .edges[.AbsID()])
	}
	return true
}

func applyObjectGeometry( *d2graph.Object,  objectGeometry) {
	.Box = copyBox(.box)
	.LabelPosition = copyString(.labelPosition)
	.IconPosition = copyString(.iconPosition)
}

func applyEdgeGeometry( *d2graph.Edge,  edgeGeometry) {
	.Route = copyPoints(.route)
	.LabelPosition = copyString(.labelPosition)
	.LabelPercentage = copyFloat(.labelPercentage)
	.IsCurve = .isCurve
}

func copyBox( *geo.Box) *geo.Box {
	if  == nil {
		return nil
	}
	return geo.NewBox(copyPoint(.TopLeft), .Width, .Height)
}

func copyPoints( []*geo.Point) []*geo.Point {
	if  == nil {
		return nil
	}
	 := make([]*geo.Point, len())
	for ,  := range  {
		[] = copyPoint()
	}
	return 
}

func copyPoint( *geo.Point) *geo.Point {
	if  == nil {
		return nil
	}
	return geo.NewPoint(.X, .Y)
}

func copyString( *string) *string {
	if  == nil {
		return nil
	}
	 := *
	return &
}

func copyFloat( *float64) *float64 {
	if  == nil {
		return nil
	}
	 := *
	return &
}