package dagro

import (
	
	
	
)

const (
	defaultEdgeName = "\x00"
	graphNode       = "\x00"
	edgeKeyDelim    = "\x01"
)

// GraphOptions is the D2-used subset of Graphlib 4.0.5's Graph options.
type GraphOptions struct {
	Directed   bool
	Undirected bool
	Multigraph bool
	Compound   bool
}

// Edge uniquely identifies an edge. HasName distinguishes an unnamed edge
// from a named edge whose name happens to be empty.
type Edge struct {
	V       string
	W       string
	Name    string
	HasName bool
}

type orderedSet struct {
	order []string
	has   map[string]bool
}

func newOrderedSet() *orderedSet { return &orderedSet{has: map[string]bool{}} }

func ( *orderedSet) ( string) {
	if !.has[] {
		.has[] = true
		.order = append(.order, )
	}
}

func ( *orderedSet) ( string) {
	if !.has[] {
		return
	}
	delete(.has, )
	// Reparenting algorithm-generated nodes normally removes the node that was
	// just appended. Search backwards so that common case is constant time while
	// preserving JavaScript property order exactly.
	for  := len(.order) - 1;  >= 0; -- {
		if .order[] ==  {
			.order = append(.order[:], .order[+1:]...)
			return
		}
	}
}

func ( *orderedSet) () []string {
	 := append([]string(nil), .order...)
	return jsObjectKeyOrder()
}

type orderedCounter struct {
	order []string
	count map[string]int
}

func newOrderedCounter() *orderedCounter { return &orderedCounter{count: map[string]int{}} }

func ( *orderedCounter) ( string) {
	if .count[] == 0 {
		.order = append(.order, )
	}
	.count[]++
}

func ( *orderedCounter) ( string) {
	if .count[] > 1 {
		.count[]--
		return
	}
	delete(.count, )
	for ,  := range .order {
		if  ==  {
			.order = append(.order[:], .order[+1:]...)
			return
		}
	}
}

func ( *orderedCounter) () []string { return jsObjectKeyOrder(append([]string(nil), .order...)) }

type edgeMap struct {
	order []string
	items map[string]Edge
}

func newEdgeMap() *edgeMap { return &edgeMap{items: map[string]Edge{}} }

func newEdgeMapWithCapacity( int) *edgeMap {
	return &edgeMap{order: make([]string, 0, ), items: make(map[string]Edge, )}
}

func ( *edgeMap) ( string,  Edge) {
	if ,  := .items[]; ! {
		.order = append(.order, )
	}
	.items[] = 
}

func ( *edgeMap) ( string) {
	if ,  := .items[]; ! {
		return
	}
	delete(.items, )
	for ,  := range .order {
		if  ==  {
			.order = append(.order[:], .order[+1:]...)
			return
		}
	}
}

func ( *edgeMap) () []Edge {
	if  == nil {
		return nil
	}
	// Canonical edge IDs always contain edgeKeyDelim, so none is a JS
	// array-index key. Their property order is already their insertion order.
	 := make([]Edge, 0, len(.order))
	for ,  := range .order {
		 = append(, .items[])
	}
	return 
}

// Graph implements the D2-used Graphlib 4.0.5 operations plus the operations
// required by Dagre's verified default layout path.
type Graph struct {
	directed, multigraph, compound bool
	label                          any
	defaultNodeLabel               func(string) any
	defaultEdgeLabel               func(string, string, *string) any
	nodes                          map[string]any
	nodeOrder                      []string
	parent                         map[string]string
	children                       map[string]*orderedSet
	in, out                        map[string]*edgeMap
	preds, sucs                    map[string]*orderedCounter
	edgeObjs                       *edgeMap
	edgeLabels                     map[string]any
}

// NewGraph constructs a graph. Graphs are directed by default, matching
// graphlib; set Undirected for an undirected graph.
func ( ...GraphOptions) *Graph {
	 := GraphOptions{}
	if len() > 0 {
		 = [0]
	}
	return newGraphWithCapacity(, 0, 0)
}

func newGraphWithCapacity( GraphOptions, ,  int) *Graph {
	 := true
	if .Undirected {
		 = false
	} else if .Directed {
		 = true
	}
	 := 
	if *2 <  {
		 =  * 2
	}
	 := &Graph{
		directed: , multigraph: .Multigraph, compound: .Compound,
		defaultNodeLabel: func(string) any { return nil },
		defaultEdgeLabel: func(string, string, *string) any { return nil },
		nodes:            make(map[string]any, ), nodeOrder: make([]string, 0, ),
		parent: make(map[string]string, ), children: make(map[string]*orderedSet, +1),
		in: make(map[string]*edgeMap, ), out: make(map[string]*edgeMap, ),
		preds: make(map[string]*orderedCounter, ), sucs: make(map[string]*orderedCounter, ),
		edgeObjs: newEdgeMapWithCapacity(), edgeLabels: make(map[string]any, ),
	}
	if .compound {
		.children[graphNode] = newOrderedSet()
	}
	return 
}

func ( *Graph) () bool          { return .directed }
func ( *Graph) () bool        { return .multigraph }
func ( *Graph) () bool          { return .compound }
func ( *Graph) ( any) *Graph { .label = ; return  }
func ( *Graph) () any                { return .label }

func ( *Graph) ( any) *Graph {
	if isCallable() {
		.defaultNodeLabel = func( string) any { return callCallable(, ) }
	} else {
		.defaultNodeLabel = func(string) any { return  }
	}
	return 
}

func ( *Graph) ( any) *Graph {
	if isCallable() {
		.defaultEdgeLabel = func(,  string,  *string) any {
			return callCallable(, , , optionalEdgeName{})
		}
	} else {
		.defaultEdgeLabel = func(string, string, *string) any { return  }
	}
	return 
}

func ( *Graph) () int { return len(.nodes) }

func ( *Graph) () []string { return jsObjectKeyOrder(append([]string(nil), .nodeOrder...)) }

func ( *Graph) () []string {
	var  []string
	for ,  := range .Nodes() {
		if  := .in[];  == nil || len(.items) == 0 {
			 = append(, )
		}
	}
	return 
}

func ( *Graph) () []string {
	var  []string
	for ,  := range .Nodes() {
		if  := .out[];  == nil || len(.items) == 0 {
			 = append(, )
		}
	}
	return 
}

func ( *Graph) ( []string,  ...any) *Graph {
	for ,  := range  {
		.SetNode(, ...)
	}
	return 
}

func ( *Graph) ( string,  ...any) *Graph {
	if ,  := .nodes[];  {
		if len() > 0 {
			.nodes[] = [0]
		}
		return 
	}
	if len() > 0 {
		.nodes[] = [0]
	} else {
		.nodes[] = .defaultNodeLabel()
	}
	.nodeOrder = append(.nodeOrder, )
	if .compound {
		.parent[] = graphNode
		.children[graphNode].add()
	}
	return 
}

func ( *Graph) ( string) any     { return .nodes[] }
func ( *Graph) ( string) bool { ,  := .nodes[]; return  }

func ( *Graph) ( string) *Graph {
	if !.HasNode() {
		return 
	}
	 := append(.in[].values(), .out[].values()...)
	 := map[string]bool{}
	for ,  := range  {
		 := edgeObjToID(.directed, )
		if ![] {
			[] = true
			.RemoveEdge()
		}
	}
	if .compound {
		.removeFromParentsChildList()
		for ,  := range .Children() {
			_ = .SetParent()
		}
		delete(.parent, )
		delete(.children, )
	}
	delete(.nodes, )
	delete(.in, )
	delete(.out, )
	delete(.preds, )
	delete(.sucs, )
	for ,  := range .nodeOrder {
		if  ==  {
			.nodeOrder = append(.nodeOrder[:], .nodeOrder[+1:]...)
			break
		}
	}
	return 
}

func ( *Graph) ( string,  ...string) error {
	return .setParent(, true, ...)
}

// setParentKnownAcyclic is for internal graphs and dummy nodes whose parent
// relationship is derived from an already validated compound tree. It retains
// graphlib's insertion and child ordering, but avoids walking the ancestor
// chain for every generated parent assignment.
func ( *Graph) ( string,  ...string) error {
	return .setParent(, false, ...)
}

func ( *Graph) ( string,  bool,  ...string) error {
	if !.compound {
		return fmt.Errorf("cannot set parent in a non-compound graph")
	}
	 := graphNode
	if len() > 0 {
		 = [0]
		if  {
			for ,  := , true; ; {
				if  ==  {
					return fmt.Errorf("setting %s as parent of %s would create a cycle", , )
				}
				,  = .Parent()
			}
		}
		.SetNode()
	}
	.SetNode()
	.removeFromParentsChildList()
	.parent[] = 
	if .children[] == nil {
		.children[] = newOrderedSet()
	}
	.children[].add()
	return nil
}

func ( *Graph) ( string) {
	if ,  := .parent[];  {
		if  := .children[];  != nil {
			.remove()
		}
	}
}

func ( *Graph) ( string) (string, bool) {
	if !.compound {
		return "", false
	}
	,  := .parent[]
	if ! ||  == graphNode {
		return "", false
	}
	return , true
}

func ( *Graph) ( ...string) []string {
	 := graphNode
	if len() > 0 {
		 = [0]
	}
	if .compound {
		if  := .children[];  != nil {
			return .values()
		}
		return nil
	}
	if  == graphNode {
		return .Nodes()
	}
	if .HasNode() {
		return []string{}
	}
	return nil
}

func ( *Graph) ( string) []string {
	if  := .preds[];  != nil {
		return .keys()
	}
	return nil
}
func ( *Graph) ( string) []string {
	if  := .sucs[];  != nil {
		return .keys()
	}
	return nil
}
func ( *Graph) ( string) []string {
	,  := .preds[], .sucs[]
	 := 0
	if  != nil {
		 += len(.order)
	}
	if  != nil {
		 += len(.order)
	}
	if  == 0 {
		return nil
	}
	 := make([]string, 0, )
	if  != nil {
		 = append(, .order...)
		jsObjectKeyOrder()
	}
	// Each counter already contains unique neighbors. Only successors that
	// also occur in the predecessor counter need to be removed from the union.
	 := len()
	if  != nil {
		for ,  := range .order {
			if  == nil || .count[] == 0 {
				 = append(, )
			}
		}
		jsObjectKeyOrder([:])
	}
	return 
}
func ( *Graph) ( string) bool {
	if .directed {
		return len(.Successors()) == 0
	}
	return len(.Neighbors()) == 0
}

// FilterNodes returns a graph containing the nodes accepted by filter and the
// edges between them. A retained compound node is attached to its nearest
// retained ancestor when its immediate parent is filtered out.
func ( *Graph) ( func(string) bool) *Graph {
	 := NewGraph(GraphOptions{
		Undirected: !.directed,
		Multigraph: .multigraph,
		Compound:   .compound,
	}).SetGraph(.Graph())
	for ,  := range .Nodes() {
		if () {
			.SetNode(, .Node())
		}
	}
	for ,  := range .Edges() {
		if .HasNode(.V) && .HasNode(.W) {
			.SetEdgeObject(, .Edge())
		}
	}
	if .compound {
		for ,  := range .Nodes() {
			,  := .Parent()
			for  && !.HasNode() {
				,  = .Parent()
			}
			if  {
				if  := .SetParent(, );  != nil {
					panic()
				}
			} else if  := .SetParent();  != nil {
				panic()
			}
		}
	}
	return 
}

func ( *Graph) () int { return len(.edgeLabels) }
func ( *Graph) () []Edge  { return .edgeObjs.values() }

func ( *Graph) ( []string,  ...any) *Graph {
	for  := 1;  < len(); ++ {
		.SetEdge([-1], [], ...)
	}
	return 
}

func ( *Graph) (,  string,  ...any) *Graph {
	var  any
	 := len() > 0
	if  {
		 = [0]
	}
	var  *string
	if len() > 1 {
		 := jsConcatString([1])
		 = &
	}
	 := edgeArgsToID(.directed, , , )
	if ,  := .edgeLabels[];  {
		if  {
			.edgeLabels[] = 
		}
		return 
	}
	if  != nil && !.multigraph {
		panic("dagro: cannot set a named edge when Multigraph is false")
	}
	.SetNode()
	.SetNode()
	if  {
		.edgeLabels[] = 
	} else {
		.edgeLabels[] = .defaultEdgeLabel(, , )
	}
	 := edgeArgsToObj(.directed, , , )
	.edgeObjs.set(, )
	.ensureEdgeEndpointMaps(.V, .W)
	.preds[.W].inc(.V)
	.sucs[.V].inc(.W)
	.in[.W].set(, )
	.out[.V].set(, )
	return 
}

// SetEdgeObject is the edge-object form of graphlib's setEdge.
func ( *Graph) ( Edge,  ...any) *Graph {
	if len() > 0 {
		if .HasName {
			return .SetEdge(.V, .W, [0], .Name)
		}
		return .SetEdge(.V, .W, [0])
	}
	if .HasName {
		return .setEdge(.V, .W, nil, false, &.Name)
	}
	return .SetEdge(.V, .W)
}

func ( *Graph) (,  string,  any,  bool,  *string) *Graph {
	 := []any{}
	if  {
		 = append(, )
	}
	if  != nil {
		if ! {
			 := edgeArgsToID(.directed, , , )
			if ,  := .edgeLabels[];  {
				return 
			}
			if !.multigraph {
				panic("dagro: cannot set a named edge when Multigraph is false")
			}
			.SetNode()
			.SetNode()
			.edgeLabels[] = .defaultEdgeLabel(, , )
			 := edgeArgsToObj(.directed, , , )
			.edgeObjs.set(, )
			.ensureEdgeEndpointMaps(.V, .W)
			.preds[.W].inc(.V)
			.sucs[.V].inc(.W)
			.in[.W].set(, )
			.out[.V].set(, )
			return 
		}
		 = append(, *)
	}
	return .SetEdge(, , ...)
}

func ( *Graph) (,  string) {
	if .out[] == nil {
		.out[] = newEdgeMap()
	}
	if .sucs[] == nil {
		.sucs[] = newOrderedCounter()
	}
	if .in[] == nil {
		.in[] = newEdgeMap()
	}
	if .preds[] == nil {
		.preds[] = newOrderedCounter()
	}
}

func ( *Graph) ( string) string {
	return uniqueID()
}

func ( *Graph) ( Edge) any { return .edgeLabels[edgeObjToID(.directed, )] }

func ( *Graph) (,  string,  ...string) any {
	var  *string
	if len() > 0 {
		 = &[0]
	}
	return .edgeLabels[edgeArgsToID(.directed, , , )]
}

func ( *Graph) (,  string,  ...string) bool {
	var  *string
	if len() > 0 {
		 = &[0]
	}
	,  := .edgeLabels[edgeArgsToID(.directed, , , )]
	return 
}
func ( *Graph) ( Edge) bool {
	,  := .edgeLabels[edgeObjToID(.directed, )]
	return 
}

func ( *Graph) ( Edge) *Graph {
	 := edgeObjToID(.directed, )
	,  := .edgeObjs.items[]
	if ! {
		return 
	}
	delete(.edgeLabels, )
	.edgeObjs.remove()
	.preds[.W].dec(.V)
	.sucs[.V].dec(.W)
	.in[.W].remove()
	.out[.V].remove()
	return 
}

func ( *Graph) (,  string,  ...string) *Graph {
	var  *string
	if len() > 0 {
		 = &[0]
	}
	 := edgeArgsToID(.directed, , , )
	if ,  := .edgeObjs.items[];  {
		return .RemoveEdge()
	}
	return 
}

func ( *Graph) ( string,  ...string) []Edge {
	 := .in[]
	if  == nil {
		if .HasNode() {
			return []Edge{}
		}
		return nil
	}
	 := .values()
	if len() == 0 || [0] == "" {
		return 
	}
	 := [:0]
	for ,  := range  {
		if .V == [0] {
			 = append(, )
		}
	}
	return 
}
func ( *Graph) ( string,  ...string) []Edge {
	 := .out[]
	if  == nil {
		if .HasNode() {
			return []Edge{}
		}
		return nil
	}
	 := .values()
	if len() == 0 || [0] == "" {
		return 
	}
	 := [:0]
	for ,  := range  {
		if .W == [0] {
			 = append(, )
		}
	}
	return 
}
func ( *Graph) ( string,  ...string) []Edge {
	,  := .in[], .out[]
	if  == nil &&  == nil {
		if .HasNode() {
			return []Edge{}
		}
		return nil
	}
	 := 0
	if  != nil {
		 += len(.order)
	}
	if  != nil {
		 += len(.order)
	}
	 := make([]Edge, 0, )
	 := ""
	if len() > 0 {
		 = [0]
	}
	// Match graphlib's incoming-then-outgoing order, including duplicate
	// self-edges and its empty-string endpoint meaning "no filter".
	if  != nil {
		for ,  := range .order {
			 := .items[]
			if  == "" || .V ==  {
				 = append(, )
			}
		}
	}
	if  != nil {
		for ,  := range .order {
			 := .items[]
			if  == "" || .W ==  {
				 = append(, )
			}
		}
	}
	return 
}

func edgeArgsToID( bool, ,  string,  *string) string {
	if ! && jsStringGreater(, ) {
		,  = , 
	}
	 := defaultEdgeName
	if  != nil {
		 = *
	}
	return  + edgeKeyDelim +  + edgeKeyDelim + 
}

func edgeArgsToObj( bool, ,  string,  *string) Edge {
	if ! && jsStringGreater(, ) {
		,  = , 
	}
	 := Edge{V: , W: }
	if  != nil {
		.Name, .HasName = *, true
	}
	return 
}

func jsStringGreater(,  string) bool {
	// Layout-generated IDs and D2's numeric IDs are ASCII. Compare that common
	// prefix without allocating UTF-16 copies; retain JS code-unit ordering
	// when either string reaches a non-ASCII byte.
	 := 0
	for  < len() &&  < len() && [] < 0x80 && [] < 0x80 {
		if [] != [] {
			return [] > []
		}
		++
	}
	if  == len() ||  == len() {
		return len() > len()
	}
	,  = [:], [:]
	,  := utf16.Encode([]rune()), utf16.Encode([]rune())
	for  := 0;  < len() &&  < len(); ++ {
		if [] != [] {
			return [] > []
		}
	}
	return len() > len()
}

func edgeObjToID( bool,  Edge) string {
	var  *string
	if .HasName {
		 = &.Name
	}
	return edgeArgsToID(, .V, .W, )
}

// JavaScript Object.keys enumerates array-index keys first in numeric order.
// jsObjectKeyOrder reorders the caller-owned slice in place. Most Dagre key
// sets are already in JavaScript order, so the first pass also acts as a fast
// path that avoids parsing with strconv, sorting, and allocating helper slices.
func jsObjectKeyOrder( []string) []string {
	type  struct {
		   string
		 uint32
	}
	if len() < 2 {
		return 
	}

	 := 0
	 := true
	 := false
	var  uint32
	for ,  := range  {
		,  := jsArrayIndex()
		if ! {
			 = true
			continue
		}
		if  ||  > 0 &&  <  {
			 = false
		}
		 = 
		++
	}
	if  {
		return 
	}

	 := make([], 0, )
	 := make([]string, 0, len()-)
	for ,  := range  {
		if ,  := jsArrayIndex();  {
			 = append(, {: , : })
		} else {
			 = append(, )
		}
	}
	sort.SliceStable(, func(,  int) bool { return []. < []. })
	for ,  := range  {
		[] = .
	}
	copy([len():], )
	return 
}

// jsArrayIndex implements the array-index portion of ECMAScript property-key
// ordering: a canonical unsigned decimal string in [0, 2^32-2].
func jsArrayIndex( string) (uint32, bool) {
	if  == "0" {
		return 0, true
	}
	if len() == 0 || len() > 10 || [0] == '0' {
		return 0, false
	}
	var  uint64
	for  := 0;  < len(); ++ {
		 := []
		if  < '0' ||  > '9' {
			return 0, false
		}
		 = *10 + uint64(-'0')
		if  >= 1<<32-1 {
			return 0, false
		}
	}
	return uint32(), true
}

func preorder( *Graph,  []string) []string  { return dfs(, , false) }
func postorder( *Graph,  []string) []string { return dfs(, , true) }

func dfs( *Graph,  []string,  bool) []string {
	 := map[string]bool{}
	var  []string
	var  func(string)
	 = func( string) {
		if [] {
			return
		}
		[] = true
		if ! {
			 = append(, )
		}
		var  []string
		if .directed {
			 = .Successors()
		} else {
			 = .Neighbors()
		}
		for ,  := range  {
			()
		}
		if  {
			 = append(, )
		}
	}
	for ,  := range  {
		if !.HasNode() {
			panic(fmt.Sprintf("dagro: graph does not have node: %s", ))
		}
		()
	}
	return 
}