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

package debugger

import (
	
	
	
	

	
	

	am 
	
	
)

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

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

	 := cview.NewTreeView()
	.SetRoot(.treeRoot)
	.SetCurrentNode(.treeRoot)
	.SetSelectedBackgroundColor(tcell.GetColor(theme.Highlight2))
	.SetSelectedTextColor(tcell.GetColor(theme.White))
	.SetHighlightColor(tcell.GetColor(theme.Highlight))
	.SetScrollBarColor(tcell.GetColor(theme.Highlight2))

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

			return
		}

		.Mach.Add1(ss.StateNameSelected, Pass(&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  dbg.DbgMsg
	 := .C
	if  == nil {
		return
	}

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

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

	// default decorations plus name highlights
	 := .hUpdateTreeDefaultsHighlights(, )
	 += treeIndent
	.hSortTree()
	.hUpdateTreeRelCols(, nil, nil)
}

// returns the length of the longest row
// TODO refactor to a model, add inbound relations
func ( *Debugger) (
	 dbg.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 ! {
			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)
			return true

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

			// tag root (collapse)
		} else if .isTagRoot {
			.SetText("Tags")
			return true
		}

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

		 := .stateName
		 :=  + strings.Repeat(" ",
			max(0, -len()))
		 := theme.Inactive

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

				 = theme.Err
			} else {
				 = theme.Active
			}
		}

		// reset to defaults
		.SetText()

		 := " "
		if ,  := [];  && !.isRef && .Multi {
			 = "M"
			if  == theme.Active {
				 = theme.Active2
			}
		}

		// 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(tcell.GetColor())
				.SetText( + " " +  + "|" + )
			}

			return true
		}

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

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

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

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

		return true
	})

	return 
}

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

	for ,  := range .tree.GetRoot().GetChildren() {
		,  := .GetReference().(*nodeRef)
		if ! {
			continue
		}
		.handleExpanded(, , )
	}
}

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

	.SetExpanded(.expanded)
}

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(tcell.GetColor(theme.Inactive))
	.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.GetColor(theme.Grey))
			.SetReference(&nodeRef{
				isTag: true,
			})
			.AddChild()
		}

		.AddChild()
	}

	.SetExpanded(false)
}

// 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 types.NormalizeGroupName() ==
			types.NormalizeGroupName(.C.SelectedGroup) {
			 = 
		}
	}

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

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()
}

// UTILS

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