// TODO extract the tree logic to a separate struct, re-write

package debugger

import (
	
	
	
	
	

	
	

	am 
	
	ss 
)

type nodeRef struct {
	// TODO type
	// type nodeType
	// node is a state (reference or top level)
	stateName string
	// node is a state reference, not a top level state
	// eg Bar in case of: Foo -> Remove -> Bar
	// TODO name collision with nodeRef
	isRef bool
	// node is a relation (Remove, Add, Require, After)
	isRel bool
	// relation type (if isRel)
	rel am.Relation
	// top level state name (for both rels and refs)
	parentState string
	// node touched by a transition step
	touched bool
	// expanded by the user
	expanded bool
	// node is a state property (Auto, Multi)
	isProp    bool
	propLabel string
	isTagRoot bool
	isTag     bool
	// TODO
	// isBreakLine bool
}

const treeIndent = 3

// TODO tree model
var trailingDots = regexp.MustCompile(`\.+$`)

func ( *Debugger) () *cview.TreeView {
	.treeRoot = cview.NewTreeNode("States")
	// d.treeRoot.SetColor(tcell.ColorRed)

	 := cview.NewTreeView()
	.SetRoot(.treeRoot)
	.SetCurrentNode(.treeRoot)
	.SetSelectedBackgroundColor(colorHighlight2)
	.SetSelectedTextColor(tcell.ColorWhite)
	.SetHighlightColor(colorHighlight)
	.SetScrollBarColor(colorHighlight2)

	// focus change within the tree
	.SetChangedFunc(func( *cview.TreeNode) {
		,  := .GetReference().(*nodeRef)
		if ! || .stateName == "" {
			.Mach.Remove1(ss.StateNameSelected, nil)
			.lastSelectedState = ""

			return
		}

		.Mach.Add1(ss.StateNameSelected, am.A{
			"state": .stateName,
		})
		.hUpdateLogReader(nil)
		.hUpdateMatrix()
	})

	// click
	.SetSelectedFunc(func( *cview.TreeNode) {
		,  := .GetReference().(*nodeRef)
		if ! {
			// TODO err
			return
		}

		// jump to referenced state
		if .isRef && .stateName != "" {
			 := normalizeText(.stateName)
			for ,  := range .treeRoot.GetChildren() {
				if  == normalizeText(strings.Split(.GetText(), " ")[0]) {
					.tree.SetCurrentNode()

					// highlight the selected node
					.SetHighlighted(true)

					return
				}
			}
		}

		// expand on 2nd click
		 := .tree.GetCurrentNode()
		if  ==  {
			.expanded = !.IsExpanded()
			.SetExpanded(.expanded)
		}
	})

	return 
}

func ( *Debugger) () {
	// TODO refac to updateSchema (state)

	var  telemetry.DbgMsg
	 := .C
	if  == nil {
		return
	}

	 := 0
	if .CursorTx1 == 0 {
		 = .MsgStruct
	} else {
		 = .CursorTx1 - 1
		 = .MsgTxs[]
	}

	.tree.SetTitle(.P.Sprintf(" Schema:%v ", len(.MsgStruct.StatesIndex)))

	var  []*am.Step
	 := .hNextTx()
	if .CursorTx1 < len(.MsgTxs) && .CursorStep1 > 0 {
		 = .Steps
	}

	// default decorations plus name highlights
	 := .hUpdateTreeDefaultsHighlights(, )

	// decorate steps, take the longest row from either defaults or steps
	 = max(, .hUpdateTreeTxSteps(, ))
	 += treeIndent
	.hSortTree()
	.hUpdateTreeRelCols(, , )
}

// returns the length of the longest row
// TODO refactor
func ( *Debugger) (
	 telemetry.DbgMsg,  int,
) int {
	 := .C
	if  == nil {
		return 0
	}

	 := 0
	 := .MsgStruct.StatesIndex

	// TODO group index
	for ,  := range  {
		 = max(, len())
	}
	 := .MsgStruct.States
	 := 0

	.tree.GetRoot().Walk(func(
		,  *cview.TreeNode,  int,
	) bool {
		// skip the root
		if  == nil {
			return true
		}
		,  := .GetReference().(*nodeRef)
		if ! {
			// get node text length
			 = maxNodeLen(, , )
			return true
		}

		.touched = false
		// node.SetBold(false)
		.SetUnderline(false)

		// relation state
		if .isRel {
			.SetText(capitalizeFirst(.rel.String()))
			return true

			// auto / multi prop
		} else if .isProp {
			.SetText(.propLabel)
			// get node text length
			 = maxNodeLen(, , )
			return true

			// tag name (ignore)
		} else if .isTag {
			return true

			// tag root (collapse)
		} else if .isTagRoot {
			.SetText("Tags")
			// get node text length
			 = maxNodeLen(, , )
			return true
		}

		// inherit
		if  == .tree.GetRoot() || !.GetHighlighted() {
			.SetHighlighted(false)
		}

		 := .stateName
		 :=  + strings.Repeat(" ", -len())
		 := colorInactive

		if .Is(, am.S{}) {
			if  == am.StateException ||
				strings.HasPrefix(, am.PrefixErr) {

				 = colorErr
			} else {
				 = colorActive
			}
		}

		// reset to defaults
		.SetText()

		 := " "
		if ,  := [];  && !.isRef && .Multi {
			 = "M"
			if  == colorActive {
				 = colorActive2
			}
		}

		// reset to defaults
		if  != .SelectedState {
			if !.isRef {
				// un-highlight all descendants
				for ,  := range .GetChildren() {
					.SetHighlighted(false)
					for ,  := range .GetChildren() {
						.SetHighlighted(false)
					}
				}

				 := .P.Sprintf("%d", .Clock(,
					))
				.SetColor()
				.SetText( + " " +  + "|" + )
			}

			// get node text length
			 = maxNodeLen(, , )

			return true
		}

		// reference
		if  != .tree.GetCurrentNode() {
			.SetHighlighted(true)
			// log.Println("highlight", stateName)
		}
		if .isRef {

			// get node text length
			 = maxNodeLen(, , )
			return true
		}

		// top-level state
		 := strconv.FormatUint(.Clock(,
			), 10)
		.SetColor()
		.SetText( + " " +  + "|" + )

		// get node text length
		 = maxNodeLen(, , )

		if  == .tree.GetCurrentNode() {
			return true
		}

		// highlight all descendants
		for ,  := range .GetChildren() {
			.SetHighlighted(true)
			for ,  := range .GetChildren() {
				.SetHighlighted(true)
			}
		}

		return true
	})

	return 
}

func ( *Debugger) (
	 []*am.Step,  *telemetry.DbgMsgTx,
) int {
	 := .C
	if  == nil {
		return 0
	}

	 := 0

	// walk the tree only when scrolling steps
	if .CursorStep1 < 1 {
		for ,  := range .tree.GetRoot().GetChildren() {
			,  := .GetReference().(*nodeRef)
			if ! {
				continue
			}
			.handleExpanded(, , )
		}
		return 0
	}

	// get max length
	.tree.GetRoot().Walk(func(
		,  *cview.TreeNode,  int,
	) bool {
		if  != nil {
			 = maxNodeLen(, , )
		}
		return true
	})

	// current max len with step tags
	 := 

	.tree.GetRoot().Walk(func(
		,  *cview.TreeNode,  int,
	) bool {
		// skip the root
		if  == nil {
			return true
		}

		,  := .GetReference().(*nodeRef)
		if ! {
			return true
		}

		 := .MsgStruct.StatesIndex
		if .stateName != "" {

			// STATE NAME NODES
			 := .stateName
			for  := range  {

				if .CursorStep1 ==  {
					break
				}
				 := []
				 := ""
				 := .VisibleLength()
				// TODO unified color
				if +1- > 0 {
					 = strings.Repeat(" ", +1-) + "[white]"
					// debug
					// log.Printf("node: %s, textMargin: %d, depth: %d, visibleLen: %d",
					//	node.GetText(), len(textMargin), depth, visibleLen)
				}

				switch .Type {
				case am.StepRemoveNotActive:
					if .GetToState() ==  && !.isRef {
						nodeSetBold()
						.touched = true
					}

				case am.StepRemove:
					if .GetToState() ==  && !.isRef {
						.SetText(.GetText() +  + "[::b]-[::-]")
						nodeSetBold()
						.touched = true
					}

				case am.StepRelation:

					if .GetFromState() ==  && !.isRef {

						nodeSetBold()
						.touched = true
					} else if .GetToState() ==  && !.isRef {

						nodeSetBold()
						.touched = true
					} else if .isRef && .GetToState() ==  &&
						.parentState == .GetFromState() {

						nodeSetBold()
						.touched = true
					}

				case am.StepHandler:
					if .isRef {
						continue
					}
					// states handler executed
					if .GetFromState() ==  ||
						.GetToState() ==  {

						// canceled
						if !.Accepted &&  == len()-1 {
							.SetText(.GetText() +  + "[red::b]*[-::-]")
						} else {
							.SetText(.GetText() +  + "[::b]*[::-]")
						}
						nodeSetBold()
						.touched = true
					}

				case am.StepSet:
					if .GetToState() ==  && !.isRef {
						.SetText(.GetText() +  + "[::b]+[::-]")
						nodeSetBold()
						.touched = true
					}

				case am.StepRequested:
					if .GetToState() ==  && !.isRef {
						 := .GetText()
						 := strings.Index(, " ")
						.SetText("[::bu]" + [:] + "[::-]" + [:])
						.touched = true
					}

				case am.StepCancel:
					if .GetToState() ==  && !.isRef {

						// canceled
						if !.Accepted &&  == len()-1 {
							.SetText(.GetText() +  + "[red::b]![-::-]")
						} else {
							.SetText(.GetText() +  + "[::b]![::-]")
						}
						nodeSetBold()
						.touched = true
					}
				}
			}

			.handleExpanded(, , )
		} else if .isRel {
			// RELATION NODES
			for  := range  {

				if .CursorStep1 ==  {
					break
				}

				 := []
				if .Type != am.StepRelation {
					continue
				}

				if .RelType == .rel &&
					.parentState == .GetFromState() {

					nodeSetBold()
					.touched = true
				}
			}
		} else if .isTagRoot {
			.Collapse()
		}

		 = maxNodeLen(, , )

		return true
	})

	return 
}

var reTreeStateColorFix = regexp.MustCompile(`\[white\](M?\|\d+)(\.*)`)

func ( *Debugger) (
	 int,  []*am.Step,  telemetry.DbgMsg,
) {
	 := .C
	if  == nil {
		return
	}

	// walk the tree only when scrolling steps
	if .CursorStep1 < 1 {
		return
	}

	var  []RelCol
	var  bool

	.tree.GetRoot().Walk(func(
		,  *cview.TreeNode,  int,
	) bool {
		// skip the root
		if  == nil || !parentExpanded() {
			return true
		}

		,  := .GetReference().(*nodeRef)
		if ! {
			// TODO shouldnt happen
			return true
		}

		var  []string
		// debug
		// d.Mach.Log(".")

		if .stateName != "" {

			// STATE NAME NODES
			 := .stateName
			for  := range  {

				if .CursorStep1 ==  {
					break
				}
				 := []

				if .Type != am.StepRelation {
					continue
				}

				 := .MsgStruct.StatesIndex
				 := .GetToState() ==  && !.isRef
				 := .isRef && .GetToState() ==  &&
					.parentState == .GetFromState()

				if  ||  {

					 := getRelColName(, )
					,  = handleTreeCol(strconv.Itoa(), , )

					if  {
						// debug
						// d.Mach.Log("close %s", colName)
						 = append(, )
					}
					// } else {
					// debug
					// d.Mach.Log("open %s", colName)
					// }
				}
			}
		}

		// check if its some start/end
		 := false
		 := false
		for ,  := range  {
			 = .isRef && .stateName != "" &&
				.name == getRelColNameFromRef()
			if  {
				break
			}

			 = !.isRef && .stateName != "" &&
				strings.HasSuffix(.name, .stateName)
			if  {
				break
			}
		}

		// draw columns
		// TODO REWRITE TO A MODEL
		 :=  - *treeIndent + treeIndent
		 := ""
		 :=  - .VisibleLength()
		if  ||  {
			 := ""
			 := false
			 := false
			 := false
			 := false

			for ,  := range .GetText() {
				if  == ' ' && ! {
					 = true
					 += " "
					continue
				}

				if  == ' ' {
					if ! {
						 = true
						 += "[grey]."
					} else if ! {
						 = true
						 += "[grey]."
					} else {
						 += "."
					}
				} else if  && ! {
					 = true
					 += "[white]" + string()
				} else {
					// copy existing rune
					 += string()
				}
			}

			.SetText( + "[grey]")
			 = strings.Repeat(".", max(0, ))
		} else {
			 = strings.Repeat(" ", max(0, ))
		}

		if len() > 0 {
			 += "[grey]"
		}

		// draw columns
		 := 0
		for ,  := range  {

			 := false
			for ,  := range  {
				if  == .name {
					 = true
				}
			}

			 := .isRef && .stateName != "" &&
				.name == getRelColNameFromRef()
			 := !.isRef && .stateName != "" &&
				strings.HasSuffix(.name, "-"+.stateName)

			if !.closed ||  {
				// debug
				// d.Mach.Log("%v | %s | %s", ref.isRef, ref.stateName, col.name)
				// if ref.isRef {
				// 	d.Mach.Log("getRelColNameFromRef: %s", getRelColNameFromRef(ref))
				// }

				if  {
					 += "[green::b]|[grey::-]"
				} else if  {
					 += "[red::b]|[grey::-]"
				} else {
					 += "|"
				}
				++

			} else if  ||  {
				// link column
				 += "."
			} else {
				// empty column
				 += " "
			}
		}
		// debug
		// d.Mach.Log("cols: %d [%d] | s-idx: %d | len: %d", len(relCols),
		// active, nodeColStartIdx, nodeVisibleLen(node))

		// d.Mach.Log("%s", nodeCols)
		 := trailingDots.ReplaceAllString(, "")
		 := .GetText()
		// regexp

		// TODO avoid monkey patching
		if .stateName != "" && !.isRef {
			if !.Is(.C.MsgStruct.StatesIndex, am.S{.stateName}) {
				 = reTreeStateColorFix.ReplaceAllString(,
					"["+colorInactive.String()+"]$1[grey]$2")
			} else {
				 = reTreeStateColorFix.ReplaceAllString(,
					"["+colorActive.String()+"]$1[grey]$2")
			}
		}
		.SetText( + )

		// debug
		// d.Mach.Log("%s%s", strings.Repeat("---", depth), node.GetText())

		return true
	})
}

func ( *Debugger) (
	 *cview.TreeNode,  *nodeRef,  *Client,
) {
	// TODO ref lock? copy?
	if .isRef || .stateName == "" {
		return
	}

	// expand when touched or expanded by the user
	 := .CursorStep1 > 0 || .Mach.Is1(ss.TimelineStepsFocused)
	.SetExpanded(false)
	if (.expanded && !) || (.touched && ) {
		.SetExpanded(true)
	}
}

func ( *Debugger) () {
	 := .C
	 := .MsgStruct
	.treeRoot.ClearChildren()

	// pick states
	 := .StatesIndex
	if .SelectedGroup != "" {
		 = .MsgSchemaParsed.Groups[.SelectedGroup]
	}
	.schemaTreeStates = 

	// build
	for ,  := range  {
		// if !bl {
		// 	// TODO enable breaklines
		// 	bl = d.addBreakLine(name, i)
		// }
		.hAddState()
	}
	.treeRoot.CollapseAll()
	.treeRoot.Expand()
}

func ( *Debugger) ( string) {
	if .tree == nil {
		return
	}
	.tree.GetRoot().Walk(func(,  *cview.TreeNode,  int) bool {
		if  == nil {
			return true
		}
		 := .GetReference().(*nodeRef)
		if .stateName ==  &&  == 1 {
			.tree.SetCurrentNode()
			return false
		}

		return true
	})
}

// TODO enable breaklines with model-based rendering
// var pkgStates = am.SAdd(ssam.BasicStates.Names(),
//  ssam.ConnPoolStates.Names(), ssam.ConnectedStates.Names(),
// 	ssam.DisposedStates.Names())
// func (d *Debugger) addBreakLine(name string, idx int) bool {
// 	// TODO requires TreeNode#SetHidden(true)
// 	//  hide in steps view
// 	c := d.C
// 	if c == nil {
// 		return false
// 	}
//
// 	// check if this and all next are in pkg/states
// 	names := c.MsgStruct.StatesIndex[idx:len(c.MsgStruct.StatesIndex)]
// 	for _, name2 := range names {
// 		if !slices.Contains(pkgStates, name2) {
// 			return false
// 		}
// 	}
//
// 	stateNode := cview.NewTreeNode("pkg/states")
// 	stateNode.SetSelectable(false)
// 	stateNode.SetReference(&nodeRef{
// 		stateName: name,
// 		// isBreakLine: true,
// 	})
// 	d.treeRoot.AddChild(stateNode)
// 	stateNode.SetColor(tcell.ColorDarkGrey)
//
// 	return true
// }

func ( *Debugger) ( string) {
	 := .C
	if  == nil {
		return
	}
	 := .MsgStruct.States[]

	// labels
	 := ""
	if .Auto {
		 += "auto"
	}

	 := " "
	if .Multi {
		if  != "" {
			 += " "
		}
		 += "multi"
		 = "M"
	}

	 := cview.NewTreeNode( + " " +  + "|0")
	.SetSelectable(true)
	.SetReference(&nodeRef{stateName: })
	.SetColor(colorInactive)
	.treeRoot.AddChild()

	if  != "" {
		 := cview.NewTreeNode()
		.SetReference(&nodeRef{
			isProp:    true,
			propLabel: ,
		})
		.AddChild()
	}

	// relations
	addRelation(, , am.RelationAdd, .Add, .schemaTreeStates)
	addRelation(, , am.RelationRequire, .Require,
		.schemaTreeStates)
	addRelation(, , am.RelationRemove, .Remove,
		.schemaTreeStates)
	addRelation(, , am.RelationAfter, .After,
		.schemaTreeStates)

	// tags
	if len(.Tags) > 0 {
		 := cview.NewTreeNode("Tags")
		.SetSelectable(true)
		.SetReference(&nodeRef{
			isTagRoot: true,
		})

		for ,  := range .Tags {
			 := cview.NewTreeNode("#" + )
			.SetColor(tcell.ColorGrey)
			.SetReference(&nodeRef{
				isTag: true,
			})
			.AddChild()
		}

		.AddChild()
	}
}

// hSortTree requires hUpdateSchemaTree called before
func ( *Debugger) () {
	// sort state names in the tree with touched ones first
	 := .treeRoot.GetChildren()
	slices.SortStableFunc(, func(,  *cview.TreeNode) int {
		// sort by touched
		 := .GetReference().(*nodeRef)
		 := .GetReference().(*nodeRef)

		if .touched && !.touched {
			return -1
		} else if !.touched && .touched {
			return 1
		}

		// sort by machine order
		 := slices.Index(.C.MsgStruct.StatesIndex, .stateName)
		 := slices.Index(.C.MsgStruct.StatesIndex, .stateName)

		if  <  {
			return -1
		} else {
			return 1
		}
	})

	.treeRoot.SetChildren()
}

func ( *Debugger) () {
	var  int
	var  []*cview.DropDownOption
	for ,  := range .C.MsgSchemaParsed.GroupsOrder {
		 := len(.C.MsgSchemaParsed.Groups[])
		 := "all"
		if  != "all" {
			 = fmt.Sprintf("%s:%d", , )
		}
		 = append(, cview.NewDropDownOption())
		if  == .C.SelectedGroup {
			 = 
		}
	}

	.treeGroups.ClearOptions()
	.treeGroups.AddOptions(...)
	// TODO not great
	go .treeGroups.SetCurrentOption()
}

func parentExpanded( *cview.TreeNode) bool {
	for  = .GetParent();  != nil;  = .GetParent() {
		if !.IsExpanded() {
			return false
		}
	}
	return true
}

func handleTreeCol(,  string,  []RelCol) ([]RelCol, bool) {
	 := false
	for ,  := range  {
		if .name ==  && .source !=  {
			// close a column
			[].closed = true
			 = true
		}
	}

	if  {
		return , true
	}

	// create a new column
	 = append(, RelCol{
		colIndex: len(),
		name:     ,
		source:   ,
	})

	return , false
}

func getRelColName( am.S,  *am.Step) string {
	return .GetFromState() + "-" +
		.RelType.String() + "-" + .GetToState()
}

func getRelColNameFromRef( *nodeRef) string {
	return .parentState + "-" + .rel.String() + "-" + .stateName
}

func addRelation(
	 *cview.TreeNode,  string,  am.Relation,
	 []string,  am.S,
) {
	if len() <= 0 {
		return
	}
	 := cview.NewTreeNode(capitalizeFirst(.String()))
	.SetSelectable(true)
	.SetReference(&nodeRef{
		isRel:       true,
		rel:         ,
		parentState: ,
	})

	for  := range  {
		// TODO option, avoid empty
		// if !slices.Contains(statesWhitelist, relations[i]) {
		// 	continue
		// }

		 := []
		 := cview.NewTreeNode()
		.SetReference(&nodeRef{
			isRef:       true,
			rel:         ,
			stateName:   ,
			parentState: ,
		})
		.AddChild()
	}

	.AddChild()
}

type RelCol struct {
	name string
	// columns has been closed
	closed bool
	// column index
	colIndex int
	source   string
}

func capitalizeFirst( string) string {
	if len() == 0 {
		return 
	}
	return strings.ToUpper(string([0])) + [1:]
}

func maxNodeLen( *cview.TreeNode,  int,  int) int {
	return max(, .VisibleLength()+(-1)*3)
}

func nodeSetBold( *cview.TreeNode) {
	 := .GetText()
	if strings.Contains(, "[::b]") {
		return
	}
	 := strings.Index(, " ")
	if  < 0 {
		.SetText("[::b]" +  + "[::-]")
		return
	}
	.SetText("[::b]" + [:] + "[::-]" + [:])
}