package placement

import (
	
	
	
	
	
	

	
	

	
)

type gapNormalizationOptions struct {
	axis      layoutAxis
	direction traversalDirection
	costTxn   *layoutgraph.Transaction
}

type gapReductionOptions struct {
	axis                   layoutAxis
	direction              traversalDirection
	attemptRecoverSymmetry bool
	costTxn                *layoutgraph.Transaction
}

func gapNormalization( context.Context,  layoutgraph.Nodes,  *layoutgraph.Transaction,  *layoutgraph.Graph,  gapNormalizationOptions) (bool, error) {
	if !.axis.valid() {
		return false, fmt.Errorf("TALA GapNormalization requires an axis")
	}
	if !.direction.valid() {
		return false, fmt.Errorf("TALA GapNormalization requires a direction")
	}
	, ,  := layoutgraph.EnsureTransactionWorkGuard(, "GapNormalizationTransactions")
	if  != nil {
		return false, 
	}
	// We gap reduce by order of most connected nodes to least connected
	// This should achieve the effect of bringing nodes into central areas of the diagram instead of isolated nodes dragging stuff to it
	 := make([]*layoutgraph.Node, len())
	copy(, )
	slices.SortFunc(, func(,  *layoutgraph.Node) int {
		if  := cmp.Compare(len(.Edges), len(.Edges));  != 0 {
			return 
		}
		return cmp.Compare(.ID, .ID)
	})

	 := false

	// whole trees can move towards other nodes but we don't want to move within trees
	for ,  := range  {
		if ,  := .NodeToTree[];  {
			continue
		}
		if .Hierarchy != nil {
			continue
		}
		,  := .CloneGeometryContext()
		if  != nil {
			return , 
		}
		.AddOp(func() error {
			, ,  := reduceGapToNeighbors(, , , gapReductionOptions{
				axis:                   .axis,
				direction:              .direction,
				attemptRecoverSymmetry: true,
				costTxn:                .costTxn,
			})
			return 
		})
		if  := .Commit();  != nil {
			if !layoutgraph.IsCandidateRejection() {
				return , 
			}
		} else {
			if  := .UpdateState();  != nil {
				return , 
			}
			 = true
		}
		.Clear()
	}

	return , nil
}

// Assumes ahead is ahead of behind when forwards is true
func isBetween( *layoutgraph.Node, ,  *layoutgraph.Node, ,  bool) bool {
	 := float64(.DeltaTo(, .TopLeft))
	if  {
		// . ┌──────┐
		// . │ node │     false
		// . └──────┘   ─┬──────
		// .             │ delta
		// . ┌────────┐  ┴      ┌───────┐
		// . │ behind │         │ ahead │
		// . └────────┘         └───────┘
		if (.TopLeft.Y + .Height) < .TopLeft.Y- {
			return false
		}
		// . ┌────────┐         ┌───────┐
		// . │ behind │         │ ahead │
		// . └────────┘  ┬      └───────┘
		// .             │ delta
		// . ┌──────┐   ─┴───────
		// . │ node │     false
		// . └──────┘
		if .TopLeft.Y > .TopLeft.Y+.Height+ {
			return false
		}

		if ! {
			,  = , 
		}
		// ┌──────┐    ┌────────┐         ┌───────┐
		// │ node │    │ behind │         │ ahead │
		// └──────┘    └────────┘         └───────┘
		// n is behind behind
		if .TopLeft.X+.Width < .TopLeft.X+.Width {
			return false
		}
		// ┌────────┐         ┌───────┐     ┌──────┐
		// │ behind │         │ ahead │     │ node │
		// └────────┘         └───────┘     └──────┘
		// n is ahead of ahead
		if .TopLeft.X > .TopLeft.X {
			return false
		}
	} else {
		if (.TopLeft.X + .Width) < .TopLeft.X- {
			return false
		}
		if .TopLeft.X > .TopLeft.X+.Width+ {
			return false
		}

		if ! {
			,  = , 
		}
		if .TopLeft.Y+.Height < .TopLeft.Y+.Height {
			return false
		}
		if .TopLeft.Y > .TopLeft.Y {
			return false
		}
	}
	return true
}

// Assumes otherNode is ahead of node when forwards is true
func nearestBetween( layoutgraph.Nodes, , ,  *layoutgraph.Node, ,  bool) *layoutgraph.Node {
	var  *layoutgraph.Node
	for ,  := range  {
		if  ==  ||  ==  {
			continue
		}
		if .OwningContainer() !=  {
			continue
		}
		if !isBetween(, , , , ) {
			continue
		}
		if  == nil {
			 = 
			continue
		}
		if  {
			if  {
				if .TopLeft.X < .TopLeft.X {
					 = 
				}
			} else {
				if (.TopLeft.X + .Width) > (.TopLeft.X + .Width) {
					 = 
				}
			}
		} else {
			if  {
				if .TopLeft.Y < .TopLeft.Y {
					 = 
				}
			} else {
				if (.TopLeft.Y + .Height) > (.TopLeft.Y + .Height) {
					 = 
				}
			}
		}
	}

	return 
}

func nearestConnectedAhead( *layoutgraph.Node, ,  bool) *layoutgraph.Node {
	var  *layoutgraph.Node

	for ,  := range .Edges {
		 := .Adjacent()
		if  {
			if  {
				// if adj's left is behind node's right its not ahead
				if .TopLeft.X < (.TopLeft.X + .Width) {
					continue
				}
				if  == nil || .TopLeft.X < .TopLeft.X {
					 = 
				}
			} else {
				// if adj's right is ahead of node's left its not behind
				if (.TopLeft.X + .Width) > .TopLeft.X {
					continue
				}
				if  == nil || (.TopLeft.X+.Width > .TopLeft.X+.Width) {
					 = 
				}
			}
		} else {
			if  {
				if .TopLeft.Y < (.TopLeft.Y + .Height) {
					continue
				}
				if  == nil || .TopLeft.Y < .TopLeft.Y {
					 = 
				}
			} else {
				if .TopLeft.Y+.Height > .TopLeft.Y {
					continue
				}
				if  == nil || (.TopLeft.Y+.Height) > (.TopLeft.Y+.Height) {
					 = 
				}
			}
		}
	}

	return 
}

// reduceGapToNeighbors attempts to reduce the gap between this node and its neighbors in front of it
// returns true if reduced, false if no changes. If true, also returns the reduced global edge length
//
// Imagine a scenario like this
// . ┌────┐
// . │ A  │
// . │    ├───┐
// . └───┬┘   │
// .     │  ┌─▼┐
// .     │  │B │
// .     │  └──┘
// .     │
// .     │
// .     │
// .     │
// .     │
// .     │
// .   ┌─▼───┐
// .   │     │
// .   │ C   │
// .   └─────┘
// IF C IS PULLING NEIGHBORS --------------
//
// C's nearest ahead is A, but it won't be able to move A + B down since it'll error
// Nonetheless, there's still a large gap, and the optimal place is to consider B to be its nearest ahead
// So we don't really want nearest ahead, we want the node connected to nearest ahead closest to C
//
// IF A IS PULLING NEIGHBORS --------------
//
// A's nearest ahead is C, but it won't be able to move to A
// So we sweep, get node B, and consider the gap between C and B as the amount it should try to close
func reduceGapToNeighbors( context.Context,  *layoutgraph.Node,  *layoutgraph.Transaction,  gapReductionOptions) (bool, float64, error) {
	if  := .Err();  != nil {
		return false, 0, 
	}
	if !.axis.valid() {
		return false, 0, fmt.Errorf("TALA gap reduction requires an axis")
	}
	if !.direction.valid() {
		return false, 0, fmt.Errorf("TALA gap reduction requires a direction")
	}
	 := .axis.isHorizontal()
	 := .direction.isForward()
	 := .attemptRecoverSymmetry
	// Needed for calculating partial symmetry
	if .Graph.CellSize == 0 {
		.Graph.ComputeCellSize()
	}

	 := nearestConnectedAhead(, , )
	if  == nil {
		return false, 0, nil
	}
	// if nearest ahead is fixed or is within a fixed node, we can't pull it closer
	for  := .Container;  != nil;  = .Container {
		if .FixedTopLeft != nil {
			return false, 0, nil
		}
	}
	 := []*layoutgraph.Node{}
	if .HerdAssignment != nil {
		// when normalizing the gap from `4` to `1`, it selects `c1`, `2`, `3`, `5`, `6`
		// as connected nodes, however, `5` and `6` shouldn't be considered because
		// when moving the connected nodes towards `4` it will break the herd alignment
		// ┌─────────────────────────────────────────────────────────────────┐
		// │    ┌─────────────┐        ┌─────────────┐     ┌─────────────┐   │
		// │    │             │        │             │     │             │   │
		// │    │             │        │             │     │             │   │
		// │    │     1       │   c1   │     2       │     │      3      │   │
		// │    │             │        │             │     │             │   │
		// │    │             │        │             │     │             │   │
		// │    └─────┬───────┘        └──────┬──────┘     └──────┬──────┘   │
		// └──────────┼───────────────────────┼───────────────────┼──────────┘
		//            │                       │                   │
		//            │                       │                   │
		//  ┌─────────┼───────────────────────┼───────────────────┼──────────┐
		//  │    ┌────▼────────┐        ┌─────▼───────┐     ┌─────▼───────┐  │
		//  │    │             │        │             │     │             │  │
		//  │    │             │        │             │     │             │  │
		//  │    │     4       │   c2   │     5       │     │     6       │  │
		//  │    │             │        │             │     │             │  │
		//  │    │             │        │             │     │             │  │
		//  │    └─────────────┘        └─────────────┘     └─────────────┘  │
		//  └────────────────────────────────────────────────────────────────┘
		for ,  := range .Graph.Containers[.Container] {
			if  ==  || .HerdAssignment == nil || .IsDescendantOf() {
				continue
			}
			if .HerdAssignment.Orientation == .HerdAssignment.Orientation {
				 = append(, )
			}
		}
	}

	 := .NearestSharedAncestor()
	 := nearestBetween(layoutgraph.Nodes(.ConnectedNodes(, .Graph)), ,
		,
		,
		,
		,
	)

	// we don't want fixed nodes preventing us from getting the actual nearest between (could be a fixed node's container)
	// but we only want to move connected nodes up until we reach a fixed node
	 = append(, .Graph.FixedNodes()...)
	 := .ConnectedNodeSet(, .Graph)

	if  != nil {
		 = 
	}

	if .costTxn != nil {
		if  := .costTxn.CapturePlacementCosts("GapNormalizationTransactions");  != nil {
			return false, 0, 
		}
	}
	,  := placementcost.EdgeLength(, .Graph, placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: false})
	if  != nil {
		return false, 0, 
	}
	 := 

	 := []*layoutgraph.Node{}
	for ,  := range .Graph.Nodes {
		if  ==  ||  ==  {
			continue
		}
		 := slices.Contains(, )
		if  {
			continue
		}
		if  {
			if .IsBlocked(, , true, ) {
				 = append(, )
			}
		} else {
			if .IsBlocked(, , true, ) {
				 = append(, )
			}
		}
	}

	var  *layoutgraph.Transaction
	// We try pulling the nearest ahead to any nodes in between
	for ,  := range append([]*layoutgraph.Node{}, ...) {
		// if nearest ahead is less nested, we only want move it to the candidate's ancestor in the same container
		// Note: we should always reach nearestAhead's Container, since it should be the outermost connected node
		for .OwningContainer() != .OwningContainer() {
			 = .OwningContainer()
			if  == nil {
				break
			}
		}
		if  == nil {
			continue
		}
		var  float64
		if  {
			if  {
				 = .TopLeft.X - (.TopLeft.X + .Width)
			} else {
				 = .TopLeft.X - (.TopLeft.X + .Width)
			}
		} else {
			if  {
				 = .TopLeft.Y - (.TopLeft.Y + .Height)
			} else {
				 = .TopLeft.Y - (.TopLeft.Y + .Height)
			}
		}

		if  <= largeGapThreshold*.Graph.CellSize {
			continue
		}

		// TODO 150 seems a bit large for ideal gap size
		 := placementcost.IdealGapSize - 
		if ! {
			 = -
		}
		if  == 0 {
			continue
		}

		var  float64
		if  == nil {
			var  error
			,  = .Graph.NewRequestTransaction(, layoutgraph.TransactionOptions{AffectContainers: true})
			if  != nil {
				return false, , 
			}
		} else {
			.Clear()
			if  := .UpdateState();  != nil {
				return false, , 
			}
		}
		.AddOp(func() error {
			for ,  := range  {
				if  {
					.Translate(, 0)
				} else {
					.Translate(0, )
				}
			}
			.Graph.SyncClusters()
			.Graph.SyncSequences()
			var  error
			,  = placementcost.EdgeLength(, .Graph, placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: false})
			if  != nil {
				return 
			}

			// Before we commit to anything, we try to "recover symmetry" by running gap reduction on the opposite side
			// E.g. if running on b, c might be brought closer and lose symmetry. But bringing a closer recovers that symmetry
			// ┌───────────────────────────────────────────────────┐
			// │                                                   │
			// │  ┌──┐                 ┌──┐                  ┌──┐  │
			// │  │a │---------------->│b │<-----------------│c │  │
			// │  └──┘                 └┬─┘                  └──┘  │
			// │                        │                          │
			// │                        │                          │
			// │                        │                          │
			// │                       ┌┴─┐                        │
			// │                       │d │                        │
			// │                       └──┘                        │
			// │                                                   │
			// └───────────────────────────────────────────────────┘
			// There are 3 scenarios:
			// 1. The gap reduction without backwards run is best
			// 2. The gap reduction plus backwards run is best
			// 3. No gap reduction is best
			// There is a fourth, which is that gap reduction backwards is best, but that's checked in outer
			if  {
				if  == nil {
					var  error
					,  = .CloneGeometryContext()
					if  != nil {
						return 
					}
				}
				.Clear()
				if  := .UpdateState();  != nil {
					return 
				}
				,  := .CloneGeometryContext()
				if  != nil {
					return 
				}
				.AddOp(func() error {
					, ,  := (, , , gapReductionOptions{
						axis:      .axis,
						direction: .direction.opposite(),
						costTxn:   .costTxn,
					})
					if  != nil {
						return 
					}

					if  && ( <  &&  < ) {
						return nil
					}
					return layoutgraph.ErrNonImprovingCandidate
				})
				if  := .Commit();  != nil {
					if !layoutgraph.IsCandidateRejection() {
						return 
					}
				} else {
					// If the backward commit was successful, it means it was optimal
					return nil
				}
			}

			if  >=  {
				return layoutgraph.ErrNonImprovingCandidate
			}

			return nil
		})
		if  := .Commit();  != nil {
			.Clear()
			// Continue searching after an invalid candidate. A non-improving
			// candidate cannot get better by searching farther forward.
			if errors.Is(, layoutgraph.ErrNonImprovingCandidate) {
				break
			}
			if !errors.Is(, layoutgraph.ErrInvalidCandidate) {
				return false, , 
			}
		} else {
			 = 
			.Clear()
			if  := .UpdateState();  != nil {
				return false, , 
			}
			break
		}
	}

	// try moving non-fixed node to the side of its container

	if .Container != nil && .Container != .Container && .FixedTopLeft == nil {
		for .OwningContainer() != .OwningContainer() {
			 := .Container.InnerBox()

			var  float64
			if  {
				if  {
					 = .TopLeft.X + .Width - (.TopLeft.X + .Width)
				} else {
					 = .TopLeft.X - .TopLeft.X
				}
			} else {
				if  {
					 = .TopLeft.Y + .Height - (.TopLeft.Y + .Height)
				} else {
					 = .TopLeft.Y - .TopLeft.Y
				}
			}

			 := .Graph.ContainerPadding(.Container, true)
			 := 
			if  {
				if  {
					 -= .Right()
				} else {
					 -= .Left()
				}
			} else {
				if  {
					 -= .Bottom()
				} else {
					 -= .Top()
				}
			}
			if ! {
				 = -
			}
			if  > largeGapThreshold*.Graph.CellSize &&  != 0 {
				if  == nil {
					var  error
					,  = .Graph.NewRequestTransaction(, layoutgraph.TransactionOptions{AffectContainers: true})
					if  != nil {
						return false, , 
					}
				} else {
					.Clear()
					if  := .UpdateState();  != nil {
						return false, , 
					}
				}
				.AddOp(func() error {
					if  {
						.MoveWithChildren(, 0)
					} else {
						.MoveWithChildren(0, )
					}
					return nil
				})
				 := false
				if  := .Commit();  != nil {
					if !layoutgraph.IsCandidateRejection() {
						return false, , 
					}
					 = true
				} else {
					,  := placementcost.EdgeLength(, .Graph, placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: false})
					if  != nil {
						.Rollback()
						return false, , 
					}

					if geo.PrecisionCompare(, , geo.PRECISION) >= 0 {
						.Rollback()
						 = true

					} else {
						 = 
					}
				}
				if  {
					.Clear()
					// Alright so if moving to the side of the container obstructs something, you can move PADDING away from the next thing in that axis
					// E.g. can't move A all the way to the side, but can move close to B whereby it's still reducing distance
					//
					// ┌───────────────────────────────────────────────┐
					// │                                               │
					// │                                               │
					// │     ┌─────┬───────────────────────────────────┼────────────►
					// │     │     │                                   │
					// │     │ A   │                    ┌──────────────┼──────────►
					// │     │     │                    │              │
					// │     └─────┘                  ┌─┴───┐          │
					// │                              │     │          │
					// │                              │ B   │          │
					// │                              │     │          │
					// │                              └─────┘          │
					// │                                               │
					// │                                               │
					// │                                               │
					// └───────────────────────────────────────────────┘
					//
					 := math.Inf(1)
					for ,  := range .Graph.Containers[.OwningContainer()] {
						if  ==  {
							continue
						}
						if  {
							if  {
								 := .TopLeft.X - placementcost.IdealGapSize
								if  > .TopLeft.X+.Width {
									if -(.TopLeft.X+.Width) <  {
										 =  - (.TopLeft.X + .Width)
									}
								}
							} else {
								 := .TopLeft.X + .Width + placementcost.IdealGapSize
								if  < .TopLeft.X {
									if .TopLeft.X- <  {
										 = .TopLeft.X - 
									}
								}
							}
						} else {
							if  {
								 := .TopLeft.Y - placementcost.IdealGapSize
								if  > .TopLeft.Y+.Height {
									if -(.TopLeft.Y+.Height) <  {
										 =  - (.TopLeft.Y + .Height)
									}
								}
							} else {
								 := .TopLeft.Y + .Height + placementcost.IdealGapSize
								if  < .TopLeft.Y {
									if .TopLeft.Y- <  {
										 = .TopLeft.Y - 
									}
								}
							}
						}
					}
					if !math.IsInf(, 1) {
						 = 
						if ! {
							 = -
						}
						.AddOp(func() error {
							if  {
								.MoveWithChildren(, 0)
							} else {
								.MoveWithChildren(0, )
							}
							return nil
						})
						if  := .Commit();  != nil {
							if !layoutgraph.IsCandidateRejection() {
								return false, , 
							}
						} else {
							,  := placementcost.EdgeLength(, .Graph, placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: false})
							if  != nil {
								.Rollback()
								return false, , 
							}
							if geo.PrecisionCompare(, , geo.PRECISION) >= 0 {
								.Rollback()
							} else {
								 = 
								.Clear()
								if  := .UpdateState();  != nil {
									return false, , 
								}
							}
						}
					}
				}
			}
			 = .OwningContainer()
			if .Container == nil {
				break
			}
		}
	}
	return  != , , nil
}