package d2talalayout

import (
	
	
	

	
	
	

	
)

// d2Patch is a fully validated update to a D2 graph. apply performs only
// non-failing assignments, preserving the graph, object, and edge identities
// held by callers.
type d2Patch struct {
	graph        *d2graph.Graph
	objects      []d2ObjectPatch
	edges        []d2EdgePatch
	destinations []d2EdgeDestinationPatch
	replaceEdges bool
	finalEdges   []*d2graph.Edge
}

type d2ObjectPatch struct {
	object  *d2graph.Object
	box     *geo.Box
	topLeft *geo.Point
	width   float64
	height  float64

	label *d2ObjectLabelPatch
	icon  *string
	font  *d2FontSizePatch
}

type d2ObjectLabelPatch struct {
	position   *string
	dimensions d2target.TextDimensions
}

type d2FontSizePatch struct {
	existing    *d2graph.Scalar
	replacement *d2graph.Scalar
	value       string
}

type d2EdgePatch struct {
	edge *d2graph.Edge

	label *d2EdgeLabelPatch
	route []*geo.Point
	curve bool
}

type d2EdgeDestinationPatch struct {
	edge        *d2graph.Edge
	destination *d2graph.Object
}

type d2EdgeLabelPatch struct {
	position   *string
	percentage *float64
	dimensions *d2target.TextDimensions
}

func preferContextError( context.Context,  string,  error) error {
	if  := .Err();  != nil {
		return fmt.Errorf("tala %s stopped: %w", , )
	}
	return 
}

// applySeedResult applies one validated result through a non-failing patch.
// Cancellation before the commit leaves graph unchanged.
func applySeedResult( context.Context,  *d2graph.Graph,  seedResult) error {
	if  := .Err();  != nil {
		return fmt.Errorf("TALA layout canceled before apply: %w", )
	}
	if .graph == nil {
		return fmt.Errorf("TALA evaluated seed is empty")
	}
	if .bindings.objectIDs == nil || .bindings.edgeIDs == nil || .bindings.edgeDestinations == nil {
		return fmt.Errorf("TALA evaluated seed has no D2 bindings")
	}
	,  := buildLayoutPatch(, , .graph, .bindings, .sequenceEdges)
	if  != nil {
		return 
	}
	if  := .Err();  != nil {
		return fmt.Errorf("TALA layout canceled before apply: %w", )
	}
	return commitLayoutPatch(, )
}

// commitLayoutPatch is the layout commit point. A caller that deliberately
// salvages a completed result after a deadline must supply a live context to
// applySeedResult.
func commitLayoutPatch( context.Context,  d2Patch) error {
	if  := .Err();  != nil {
		return fmt.Errorf("tala layout stopped before commit: %w", )
	}
	.apply()
	return nil
}

// commitRoutePatch is the routing commit point. Routing does not salvage work
// after either form of context cancellation.
func commitRoutePatch( context.Context,  d2Patch) error {
	if  := .Err();  != nil {
		return fmt.Errorf("tala edge routing canceled before commit: %w", )
	}
	.apply()
	return nil
}

func ( d2Patch) () {
	for ,  := range .objects {
		.box.TopLeft = .topLeft
		.box.Width = .width
		.box.Height = .height
		if .label != nil {
			.object.LabelPosition = .label.position
			.object.LabelDimensions = .label.dimensions
		}
		if .icon != nil {
			.object.IconPosition = .icon
		}
		if .font != nil {
			if .font.existing != nil {
				.font.existing.Value = .font.value
			} else {
				.object.Style.FontSize = .font.replacement
			}
		}
	}
	for ,  := range .edges {
		if .label != nil {
			.edge.LabelPosition = .label.position
			.edge.LabelPercentage = .label.percentage
			if .label.dimensions != nil {
				.edge.LabelDimensions = *.label.dimensions
			}
		}
		.edge.Route = .route
		.edge.IsCurve = .curve
	}
	for ,  := range .destinations {
		.edge.Dst = .destination
	}
	if .replaceEdges {
		.graph.Edges = .finalEdges
	}
}

func buildLayoutPatch( context.Context,  *d2graph.Graph,  *layoutgraph.Graph,  translation,  map[layoutgraph.EntityID]struct{}) (d2Patch, error) {
	 := d2Patch{
		graph:        ,
		replaceEdges: true,
		objects:      make([]d2ObjectPatch, 0, len(.Objects)),
		edges:        make([]d2EdgePatch, 0, len(.Edges)),
	}
	.destinations = buildDestinationPatches(, , nil)

	 := make(map[layoutgraph.EntityID]*layoutgraph.Node, len(.Nodes))
	for ,  := range .Nodes {
		if  := .Err();  != nil {
			return d2Patch{}, 
		}
		if  == nil {
			return d2Patch{}, fmt.Errorf("tala result contains a nil node")
		}
		if .TopLeft == nil {
			return d2Patch{}, fmt.Errorf("node %s reconnected with no position", .DebugID())
		}
		if ,  := [.ID];  {
			return d2Patch{}, fmt.Errorf("tala result contains duplicate node ID %d", .ID)
		}
		[.ID] = 
	}
	for ,  := range .Objects {
		if  := .Err();  != nil {
			return d2Patch{}, 
		}
		,  := .objectIDs[]
		if ! {
			return d2Patch{}, fmt.Errorf("could not reassociate graph object %q", .AbsID())
		}
		,  := []
		if ! {
			return d2Patch{}, fmt.Errorf("could not reassociate graph node %d, object ID: %s", , .AbsID())
		}
		,  := buildObjectPatch(, , )
		if  != nil {
			return d2Patch{}, 
		}
		.objects = append(.objects, )
	}

	 := make(map[layoutgraph.EntityID]*layoutgraph.Edge, len(.Edges))
	for ,  := range .Edges {
		if  := .Err();  != nil {
			return d2Patch{}, 
		}
		if  == nil {
			return d2Patch{}, fmt.Errorf("tala result contains a nil edge")
		}
		if ,  := [.ID];  {
			return d2Patch{}, fmt.Errorf("tala result contains duplicate edge ID %d", .ID)
		}
		[.ID] = 
	}
	for ,  := range .Edges {
		if  := .Err();  != nil {
			return d2Patch{}, 
		}
		,  := .edgeIDs[]
		if ! {
			return d2Patch{}, fmt.Errorf("could not reassociate D2 edge %q", .AbsID())
		}
		if ,  := [];  {
			continue
		}
		,  := []
		if ! {
			return d2Patch{}, fmt.Errorf("could not reassociate graph edge %d", )
		}
		,  := buildEdgePatch(, , , true)
		if  != nil {
			return d2Patch{}, 
		}
		.edges = append(.edges, )
		.finalEdges = append(.finalEdges, )
	}

	return , nil
}

func buildRoutePatch( context.Context,  *d2graph.Graph,  translation,  []*layoutgraph.Edge) (d2Patch, error) {
	 := d2Patch{
		graph:        ,
		edges:        make([]d2EdgePatch, 0, len()),
		replaceEdges: true,
	}
	 := make(map[layoutgraph.EntityID]*layoutgraph.Edge, len())
	 := func( []*layoutgraph.Edge) error {
		for ,  := range  {
			if  := .Err();  != nil {
				return 
			}
			if  == nil {
				return fmt.Errorf("tala route result contains a nil edge")
			}
			if ,  := [.ID];  {
				return fmt.Errorf("tala route result contains duplicate edge ID %d", .ID)
			}
			[.ID] = 
		}
		return nil
	}
	if  := ();  != nil {
		return d2Patch{}, 
	}
	 := make(map[layoutgraph.EntityID]struct{}, len())
	for  := range  {
		[] = struct{}{}
	}
	.destinations = buildDestinationPatches(, , )

	 := 0
	for ,  := range .Edges {
		if  := .Err();  != nil {
			return d2Patch{}, 
		}
		.finalEdges = append(.finalEdges, )
		,  := .edgeIDs[]
		if ! {
			return d2Patch{}, fmt.Errorf("could not reassociate D2 edge %q", .AbsID())
		}
		,  := []
		if ! {
			continue
		}
		,  := buildEdgePatch(, , , false)
		if  != nil {
			return d2Patch{}, 
		}
		.edges = append(.edges, )
		++
	}
	if  != len() {
		return d2Patch{}, fmt.Errorf("could not reassociate %d routed edges", len()-)
	}
	return , nil
}

func buildDestinationPatches(
	 *d2graph.Graph,
	 translation,
	 map[layoutgraph.EntityID]struct{},
) []d2EdgeDestinationPatch {
	 := make([]d2EdgeDestinationPatch, 0, len(.edgeDestinations))
	for ,  := range .Edges {
		if  != nil {
			,  := .edgeIDs[]
			if ! {
				continue
			}
			if ,  := []; ! {
				continue
			}
		}
		,  := .edgeDestinations[]
		if ! {
			continue
		}
		 = append(, d2EdgeDestinationPatch{
			edge:        ,
			destination: ,
		})
	}
	return 
}

func buildObjectPatch( context.Context,  *d2graph.Object,  *layoutgraph.Node) (d2ObjectPatch, error) {
	if  := .Err();  != nil {
		return d2ObjectPatch{}, 
	}
	if .Box == nil {
		return d2ObjectPatch{}, fmt.Errorf("D2 object %q has no box", .AbsID())
	}
	if .TopLeft == nil {
		return d2ObjectPatch{}, fmt.Errorf("node %s reconnected with no position", .DebugID())
	}
	if  := validateResultNode();  != nil {
		return d2ObjectPatch{}, 
	}
	 := d2ObjectPatch{
		object:  ,
		box:     .Box,
		topLeft: .TopLeft.Copy(),
		width:   .Width,
		height:  .Height,
	}
	if .Label != nil {
		.label = &d2ObjectLabelPatch{
			position: new(.Label.Position.String()),
			dimensions: d2target.TextDimensions{
				Width:  int(.Label.Width),
				Height: int(.Label.Height),
			},
		}
	}
	if .Icon != nil {
		.icon = new(.Icon.Position.String())
	}
	if .FontSize != nil {
		 := &d2FontSizePatch{
			existing: .Style.FontSize,
			value:    strconv.Itoa(*.FontSize),
		}
		if .existing == nil {
			.replacement = &d2graph.Scalar{Value: .value}
		}
		.font = 
	}
	return , nil
}

func buildEdgePatch( context.Context,  *d2graph.Edge,  *layoutgraph.Edge,  bool) (d2EdgePatch, error) {
	if  := validateResultEdge(, );  != nil {
		return d2EdgePatch{}, 
	}
	 := d2EdgePatch{
		edge:  ,
		route: make([]*geo.Point, len(.Points)),
		curve: .IsCurve,
	}
	if .Label != nil {
		 := .Label.Position
		 := .LabelPercentage
		if .SrcArrow && !.DstArrow {
			 = .Mirrored()
			 = 1 - 
		}
		.label = &d2EdgeLabelPatch{
			position:   new(.String()),
			percentage: new(),
		}
		if  {
			.label.dimensions = &d2target.TextDimensions{
				Width:  int(.Label.Width),
				Height: int(.Label.Height),
			}
		}
	}
	for  := range .Points {
		if %256 == 0 {
			if  := .Err();  != nil {
				return d2EdgePatch{}, 
			}
		}
		if .SrcArrow && !.DstArrow {
			.route[len(.Points)--1] = .Points[].Copy()
		} else {
			.route[] = .Points[].Copy()
		}
	}
	return , nil
}