package dagro

import (
	
	
	
)

// The horizontal coordinate assignment below ports Dagre 3.1.1's
// Brandes-Kopf implementation in lib/position/bk.ts.

type positionConflicts map[string]map[string]bool

type positionAlignment struct {
	root  map[string]string
	align map[string]string
}

type positionAlignments map[string]map[string]float64

// findType1Conflicts finds crossings between non-inner and inner segments.
func findType1Conflicts( *Graph,  [][]string) positionConflicts {
	 := positionConflicts{}

	// Dagre uses reduce without an initial value, so the first layer is the
	// accumulator and scanning begins with the second layer.
	for  := 1;  < len(); ++ {
		 := [-1]
		 := []
		 := 0
		 := 0
		 := len()
		 := ""
		if len() != 0 {
			 = [len()-1]
		}

		for ,  := range  {
			,  := findOtherInnerSegmentNode(, )
			// JavaScript tests the returned node ID for truthiness.
			 =  &&  != ""
			 := 
			if  {
				 = integer(asAttrs(.Node()), "order")
			}

			if  ||  ==  {
				for ,  := range [ : +1] {
					for ,  := range .Predecessors() {
						 := asAttrs(.Node())
						 := integer(, "order")
						if ( <  ||  < ) &&
							!(positionTruthy(["dummy"]) && positionTruthy(asAttrs(.Node())["dummy"])) {
							addConflict(, , )
						}
					}
				}
				 =  + 1
				 = 
			}
		}
	}

	return 
}

// findType2Conflicts finds crossings between inner segments, favoring border
// segments. The scan placement intentionally mirrors Dagre, including
// its repeated tail scan inside the south-layer loop.
func findType2Conflicts( *Graph,  [][]string) positionConflicts {
	 := positionConflicts{}

	 := func( []string, ,  int, ,  float64) {
		for  := ;  < ; ++ {
			 := []
			if !positionTruthy(asAttrs(.Node())["dummy"]) {
				continue
			}
			for ,  := range .Predecessors() {
				 := asAttrs(.Node())
				 := num(, "order")
				if positionTruthy(["dummy"]) &&
					( <  ||  > ) {
					addConflict(, , )
				}
			}
		}
	}

	for  := 1;  < len(); ++ {
		 := [-1]
		 := []
		 := float64(-1)
		 := float64(-1)
		 := 0

		for ,  := range  {
			if stringValue(asAttrs(.Node()), "dummy") == "border" {
				 := .Predecessors()
				if len() != 0 {
					 = num(asAttrs(.Node([0])), "order")
					(, , , , )
					 = 
					 = 
				}
			}
			(, , len(), , float64(len()))
		}
	}

	return 
}

func findOtherInnerSegmentNode( *Graph,  string) (string, bool) {
	if !positionTruthy(asAttrs(.Node())["dummy"]) {
		return "", false
	}
	for ,  := range .Predecessors() {
		if positionTruthy(asAttrs(.Node())["dummy"]) {
			return , true
		}
	}
	return "", false
}

func addConflict( positionConflicts, ,  string) {
	if  >  {
		,  = , 
	}
	if [] == nil {
		[] = map[string]bool{}
	}
	[][] = true
}

func hasConflict( positionConflicts, ,  string) bool {
	if  >  {
		,  = , 
	}
	return [] != nil && [][]
}

// verticalAlignment aligns nodes with median neighbors where doing so does not
// introduce a conflict or split an already-created block.
func verticalAlignment(
	 *Graph,
	 [][]string,
	 positionConflicts,
	 func(string) []string,
) positionAlignment {
	 := map[string]string{}
	 := map[string]string{}
	 := map[string]int{}

	for ,  := range  {
		for ,  := range  {
			[] = 
			[] = 
			[] = 
		}
	}

	for ,  := range  {
		 := -1
		for ,  := range  {
			 := append([]string(nil), ()...)
			if len() == 0 {
				continue
			}
			// Lodash sortBy is stable. Preserve neighbor order for equal positions.
			sort.SliceStable(, func(,  int) bool {
				return [[]] < [[]]
			})
			 := float64(len()-1) / 2
			for ,  := int(math.Floor()), int(math.Ceil());  <= ; ++ {
				 := []
				,  := []
				if ! {
					// In JavaScript, comparing an index with undefined is false. A
					// missing Go map entry is zero, so an unchecked lookup can instead
					// create an alignment rooted at the empty string and panic later.
					continue
				}
				if [] ==  &&  <  && !hasConflict(, , ) {
					[] = 
					[] = []
					[] = []
					 = 
				}
			}
		}
	}

	return positionAlignment{root: , align: }
}

func horizontalCompaction(
	 *Graph,
	 [][]string,
	,  map[string]string,
	 ...bool,
) map[string]float64 {
	 := len() != 0 && [0]
	 := map[string]float64{}
	 := buildBlockGraph(, , , )
	 := "borderRight"
	if  {
		 = "borderLeft"
	}

	 := func( func(string),  func(string) []string) {
		 := .Nodes()
		 := map[string]bool{}
		for len() != 0 {
			 := [len()-1]
			 = [:len()-1]
			if [] {
				()
			} else {
				[] = true
				 = append(, )
				 = append(, ()...)
			}
		}
	}

	(func( string) {
		 := float64(0)
		for ,  := range .InEdges() {
			 = mathMaxJS(, [.V]+number(.Edge()))
		}
		[] = 
	}, .Predecessors)

	(func( string) {
		 := math.Inf(1)
		for ,  := range .OutEdges() {
			 = mathMinJS(, [.W]-number(.Edge()))
		}
		 := asAttrs(.Node())
		if !math.IsInf(, 1) && stringValue(, "borderType") !=  {
			[] = mathMaxJS([], )
		}
	}, .Successors)

	for ,  := range  {
		[] = [[]]
	}

	return 
}

func buildBlockGraph( *Graph,  [][]string,  map[string]string,  bool) *Graph {
	 := NewGraph()
	 := asAttrs(.Graph())
	 := num(, "nodesep")
	 := num(, "edgesep")

	for ,  := range  {
		 := ""
		for ,  := range  {
			 := []
			.SetNode()
			// This is intentionally a string truthiness check, matching `if (u)`.
			if  != "" {
				 := []
				 := float64(0)
				if  := .EdgeByArgs(, );  != nil {
					 = number()
					if !jsTruthyNumber() {
						 = 0
					}
				}
				.SetEdge(, , mathMaxJS(positionSep(, , , , , ), ))
			}
			 = 
		}
	}

	return 
}

func findSmallestWidthAlignment( *Graph,  positionAlignments) map[string]float64 {
	var  map[string]float64
	 := math.Inf(1)
	for ,  := range []string{"ul", "ur", "dl", "dr"} {
		 := []
		 := math.Inf(-1)
		 := math.Inf(1)
		for ,  := range  {
			 := num(asAttrs(.Node()), "width") / 2
			 = mathMaxJS(, +)
			 = mathMinJS(, -)
		}
		 :=  - 
		// minBy retains the first value on ties.
		if  == nil ||  <  {
			 = 
			 = 
		}
	}
	return 
}

func alignCoordinates( positionAlignments,  map[string]float64) {
	,  := coordinateExtremes()

	for ,  := range []string{"u", "d"} {
		for ,  := range []string{"l", "r"} {
			 :=  + 
			 := []
			,  := coordinateExtremes()
			 :=  - 
			if  == "l" {
				 =  - 
			}
			// JavaScript treats both zero and NaN as false here.
			if  == 0 || math.IsNaN() {
				continue
			}
			 := make(map[string]float64, len())
			for ,  := range  {
				[] =  + 
			}
			[] = 
		}
	}
}

func balance( positionAlignments,  string) map[string]float64 {
	 := make(map[string]float64, len(["ul"]))
	for  := range ["ul"] {
		if  != "" {
			[] = [strings.ToLower()][]
			continue
		}
		 := []float64{["ul"][], ["ur"][], ["dl"][], ["dr"][]}
		sort.Float64s()
		[] = ([1] + [2]) / 2
	}
	return 
}

func positionX( *Graph) map[string]float64 {
	 := buildLayerMatrix()
	 := shallowMergePositionConflicts(
		findType1Conflicts(, ),
		findType2Conflicts(, ),
	)

	 := positionAlignments{}
	for ,  := range []string{"u", "d"} {
		 := clonePositionLayering()
		if  == "d" {
			reverseStrings2DOuter()
		}
		for ,  := range []string{"l", "r"} {
			if  == "r" {
				for  := range  {
					reverseStrings([])
				}
			}

			 := .Predecessors
			if  == "d" {
				 = .Successors
			}
			 := verticalAlignment(, , , )
			 := horizontalCompaction(, , .root, .align,  == "r")
			if  == "r" {
				for ,  := range  {
					[] = -
				}
			}
			[+] = 
		}
	}

	 := findSmallestWidthAlignment(, )
	alignCoordinates(, )
	return balance(, stringValue(asAttrs(.Graph()), "align"))
}

func shallowMergePositionConflicts(,  positionConflicts) positionConflicts {
	 := make(positionConflicts, len()+len())
	for ,  := range  {
		[] = 
	}
	for ,  := range  {
		// Object.assign in modern Dagre replaces the entire nested conflict
		// set when the type-2 result has the same top-level node key.
		[] = 
	}
	return 
}

func positionSep( *Graph, ,  string, ,  float64,  bool) float64 {
	 := asAttrs(.Node())
	 := asAttrs(.Node())
	 := float64(0)
	 := float64(0)

	 += num(, "width") / 2
	if has(, "labelpos") {
		switch strings.ToLower(stringValue(, "labelpos")) {
		case "l":
			 = -num(, "width") / 2
		case "r":
			 = num(, "width") / 2
		}
	}
	if jsTruthyNumber() {
		if  {
			 += 
		} else {
			 -= 
		}
	}
	 = 0

	if positionTruthy(["dummy"]) {
		 +=  / 2
	} else {
		 +=  / 2
	}
	if positionTruthy(["dummy"]) {
		 +=  / 2
	} else {
		 +=  / 2
	}

	 += num(, "width") / 2
	if has(, "labelpos") {
		switch strings.ToLower(stringValue(, "labelpos")) {
		case "l":
			 = num(, "width") / 2
		case "r":
			 = -num(, "width") / 2
		}
	}
	if jsTruthyNumber() {
		if  {
			 += 
		} else {
			 -= 
		}
	}

	return 
}

func coordinateExtremes( map[string]float64) (,  float64) {
	,  = math.NaN(), math.NaN()
	for ,  := range  {
		 = lodashMinJS(, )
		 = lodashMaxJS(, )
	}
	return , 
}

func clonePositionLayering( [][]string) [][]string {
	 := make([][]string, len())
	for  := range  {
		[] = append([]string(nil), []...)
	}
	return 
}

func reverseStrings( []string) {
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
}

func reverseStrings2DOuter( [][]string) {
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
}

func positionTruthy( any) bool {
	switch value := .(type) {
	case nil:
		return false
	case bool:
		return 
	case string:
		return  != ""
	case float64:
		return  != 0 && !math.IsNaN()
	case float32:
		return  != 0 && !float32IsNaN()
	case int:
		return  != 0
	case int8:
		return  != 0
	case int16:
		return  != 0
	case int32:
		return  != 0
	case int64:
		return  != 0
	case uint:
		return  != 0
	case uint8:
		return  != 0
	case uint16:
		return  != 0
	case uint32:
		return  != 0
	case uint64:
		return  != 0
	default:
		// JavaScript objects, including empty arrays and objects, are truthy.
		return true
	}
}

func float32IsNaN( float32) bool { return  !=  }