package debugger

import (
	
	
	
	
	
	
	

	
	
	

	amhelp 
	am 
	ss 
)

// regexp removing [foo]
var re = regexp.MustCompile(`\[(.*?)\]`)

// TODO move
func normalizeText( string) string {
	return strings.ToLower(re.ReplaceAllString(, ""))
}

func ( *Debugger) () {
	 := .hInitFocusManager()

	// custom keys
	for ,  := range .getKeystrokes() {
		 := .Set(, )
		if  != nil {
			log.Printf("Error: binding keys %s", )
		}
	}

	.hSearchSchemaClients()
	.App.SetInputCapture(.Capture)
}

func ( *Debugger) () *cbind.Configuration {
	// focus manager
	.focusManager = cview.NewFocusManager(.App.SetFocus)
	.focusManager.SetWrapAround(true)
	 := cbind.NewConfiguration()
	.App.SetAfterFocusFunc(.newAfterFocusFn())

	 := func( func()) func( *tcell.EventKey) *tcell.EventKey {
		return func( *tcell.EventKey) *tcell.EventKey {
			defer .Mach.PanicToErr(nil)

			// keep Tab inside dialogs
			if .Mach.Any1(ss.GroupDialog...) {
				return 
			}

			// fwd to FocusManager
			()
			return nil
		}
	}

	// TODO stop accepting keys if the actions arent processed in time

	// tab
	for ,  := range cview.Keys.MovePreviousField {
		 := .Set(, (.focusManager.FocusPrevious))
		if  != nil {
			// TODO no log
			log.Printf("Error: binding keys %s", )
		}
	}

	// shift+tab
	for ,  := range cview.Keys.MoveNextField {
		 := .Set(, (.focusManager.FocusNext))
		if  != nil {
			// TODO no log
			log.Printf("Error: binding keys %s", )
		}
	}

	return 
}

// newAfterFocusFn forwards focus events to machine states
func ( *Debugger) () func( cview.Primitive) {
	return func( cview.Primitive) {
		// add, but dont dup
		if !.Mach.WillBe1(ss.AfterFocus, am.PositionLast) {
			.Mach.Add1(ss.AfterFocus, am.A{"cview.Primitive": })
		}
		// d.Mach.Log("after focus %s", p)
	}
}

// hSearchSchemaClients does search-as-you-type for a-z, -, _ in the tree and
// clientList, with a searchAsTypeWindow buffer.
// TODO split
func ( *Debugger) ( *cbind.Configuration) {
	var (
		 time.Time
		      string
		        = []string{"-", "_"}
	)

	for  := 0;  < 26; ++ {
		 = append(,
			fmt.Sprintf("%c", 'a'+))
	}

	for ,  := range  {
		 := 
		// TODO extract
		 := .Set(, func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Not(am.S{ss.ClientListFocused, ss.TreeFocused}) {
				return 
			}

			// buffer
			if .Add(searchAsTypeWindow).After(time.Now()) {
				 += 
			} else {
				 = time.Now()
				 = 
			}

			// find the first client starting with the key

			// sidebar
			if .Mach.Is1(ss.ClientListFocused) {
				 := .clientList.GetCurrentItemIndex()

				for ,  := range .clientList.GetItems() {
					if +1 <=  {
						continue
					}

					// TODO trim left and preserve position for multiple matches
					 := normalizeText(.GetMainText())
					if strings.HasPrefix(, ) {
						.clientList.SetCurrentItem()
						.hUpdateClientList()
						.draw(.clientList)
						break
					}
				}

				// TODO wrap search, extract
			} else if .Mach.Is1(ss.TreeFocused) {

				// tree
				 := false
				 := .tree.GetCurrentNode()
				// TODO optimize
				var  *cview.TreeNode
				.treeRoot.Walk(
					func(,  *cview.TreeNode,  int) bool {
						if  != nil {
							return false
						}
						if ! &&  !=  {
							return true

							// minimal match and move to next matches for speed
						} else if ! {
							 = true
							return true
						}

						 := normalizeText(.GetText())

						// check if branch is expanded
						 := 
						for  != nil {
							if !.IsExpanded() {
								return true
							}
							 = .GetParent()
						}

						// match found
						if strings.HasPrefix(, ) {
							 = 

							return false
						}

						return true
					})

				if  != nil {
					// handle StateNameSelected
					,  := .GetReference().(*nodeRef)
					if  &&  != nil && .stateName != "" {
						.Mach.Add1(ss.StateNameSelected, am.A{
							// TODO typed args
							"state": .stateName,
						})
					} else {
						.Mach.Remove1(ss.StateNameSelected, nil)
					}
					.hUpdateSchemaTree()
					.hUpdateLogReader(nil)
					.draw()
					.tree.SetCurrentNode()
				}

				// TODO wrap search
			}

			return nil
		})
		if  != nil {
			log.Printf("Error: binding keys %s", )
		}
	}
}

// TODO move
type tcellKeyFn = func(ev *tcell.EventKey) *tcell.EventKey

func ( *Debugger) () map[string]tcellKeyFn {
	// TODO add state deps to the keystrokes structure
	// TODO use tcell.KeyNames instead of strings as keys
	// TODO rate limit
	return map[string]func( *tcell.EventKey) *tcell.EventKey{
		// play/pause
		"space": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Is1(ss.Paused) {
				.Mach.Add1(ss.Playing, nil)
			} else {
				.Mach.Add1(ss.Paused, nil)
			}

			return nil
		},

		// prev tx
		"left": .hPrevTxKey(),

		// next tx
		"right": .hNextTxKey(),

		// state jumps
		"alt+h":     .hJumpBackKey,
		"alt+l":     .hJumpFwdKey,
		"alt+Left":  .hJumpBackKey,
		"alt+Right": .hJumpFwdKey,
		"alt+j": func( *tcell.EventKey) *tcell.EventKey {
			.Mach.Add1(ss.UserBackStep, nil)

			return nil
		},
		"alt+k": func( *tcell.EventKey) *tcell.EventKey {
			.Mach.Add1(ss.UserFwdStep, nil)

			return nil
		},

		// page up / down
		"alt+n": func( *tcell.EventKey) *tcell.EventKey {
			return tcell.NewEventKey(tcell.KeyPgDn, ' ', tcell.ModNone)
		},
		"alt+p": func( *tcell.EventKey) *tcell.EventKey {
			return tcell.NewEventKey(tcell.KeyPgUp, ' ', tcell.ModNone)
		},

		// expand / collapse trees
		"alt+e": func( *tcell.EventKey) *tcell.EventKey {
			.hToolExpand()

			return nil
		},

		// log reader
		"alt+o": func( *tcell.EventKey) *tcell.EventKey {
			.Mach.Toggle1(ss.LogReaderEnabled, nil)

			return nil
		},

		// tail mode
		"alt+v": func( *tcell.EventKey) *tcell.EventKey {
			.Mach.Toggle1(ss.TailMode, nil)

			return nil
		},

		// matrix view
		"alt+m": func( *tcell.EventKey) *tcell.EventKey {
			.toolMatrix()

			return nil
		},

		"alt+r": func( *tcell.EventKey) *tcell.EventKey {
			.Mach.Add1(ss.ToolRain, nil)

			return nil
		},

		// scroll to the first tx
		"home": func( *tcell.EventKey) *tcell.EventKey {
			.hToolFirstTx(nil)

			return nil
		},

		// scroll to the last tx
		"end": func( *tcell.EventKey) *tcell.EventKey {
			.hToolLastTx(nil)

			return nil
		},

		// quit the app
		"ctrl+q": func( *tcell.EventKey) *tcell.EventKey {
			.Mach.Remove1(ss.Start, nil)

			return nil
		},

		// help modal
		"?": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Not1(ss.HelpDialog) {
				.Mach.Add1(ss.HelpDialog, nil)
			} else {
				.Mach.Remove(ss.GroupDialog, nil)
			}

			return 
		},

		// focus filters bar
		"alt+f": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Not1(ss.Toolbar1Focused) {
				.focusManager.Focus(.toolbars[0])
			} else {
				.focusManager.Focus(.clientList)
			}
			.draw()

			return 
		},

		// export modal
		"alt+s": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Not1(ss.ExportDialog) {
				.Mach.Add1(ss.ExportDialog, nil)
			} else {
				.Mach.Remove(ss.GroupDialog, nil)
			}

			return 
		},

		// exit modals
		"esc": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Any1(ss.GroupDialog...) {
				.Mach.Remove(ss.GroupDialog, nil)
				return nil
			}

			.focusDefault()

			return 
		},

		// remove client (sidebar)
		"backspace": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Not1(ss.ClientListFocused) {
				return 
			}

			 := .clientList.GetCurrentItem()
			if  == nil || .Mach.Not1(ss.ClientListFocused) {
				return nil
			}

			 := .GetReference().(*sidebarRef)
			.Mach.Add1(ss.RemoveClient, am.A{"Client.id": .name})

			return nil
		},

		// scroll to LogScrolled
		// scroll sidebar
		"down": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Is(S{ss.ClientListFocused, ss.ClientListVisible}) {
				.updateClientList()
			} else if .Mach.Is1(ss.LogFocused) {
				.Mach.Add1(ss.LogUserScrolled, nil)
			}

			return 
		},
		"up": func( *tcell.EventKey) *tcell.EventKey {
			if .Mach.Is(S{ss.ClientListFocused, ss.ClientListVisible}) {
				.updateClientList()
			} else if .Mach.Is1(ss.LogFocused) {
				.Mach.Add1(ss.LogUserScrolled, nil)
			}

			return 
		},
	}
}

func ( *Debugger) () {
	// default focus element
	if .Mach.Is1(ss.ClientListVisible) {
		.Mach.Add1(ss.ClientListFocused, nil)
	} else if .Mach.Not1(ss.TimelineTxHidden) {
		.Mach.Add1(ss.TimelineTxsFocused, nil)
	} else {
		.Mach.Add1(ss.AddressFocused, nil)
	}

	.Mach.Add1(ss.UpdateFocus, nil)
}

// these UI components can be navigated with keys
var keyNavigable = am.S{
	ss.AddressFocused, ss.Toolbar1Focused,
	ss.Toolbar2Focused, ss.Toolbar3Focused,
}

func ( *Debugger) () tcellKeyFn {
	return func( *tcell.EventKey) *tcell.EventKey {
		// skip for log scrolling
		if .Mach.Is1(ss.LogFocused) {
			.Mach.Add1(ss.LogUserScrolled, nil)

			return 

			// skip for other UI components
		} else if .Mach.Any1(keyNavigable...) {
			return 
		}

		if .Mach.Not1(ss.ClientSelected) {
			return nil
		}
		if .hThrottleKey(, arrowThrottleMs) {
			// TODO fast jump scroll while holding the key
			return nil
		}

		// skip if scrolling
		if .shouldScrollCurrView() {
			return 
		}

		// scroll timelines
		 := ss.UserFwd
		if .Mach.Is1(ss.TimelineStepsFocused) {
			 = ss.UserFwdStep
		}

		// check queue throttle and add the state
		if !.Mach.IsQueuedAbove(scrollTxThrottle, am.MutationAdd, S{}, false,
			false, 0) {

			.Mach.Add1(, nil)
		}

		return nil
	}
}

func ( *Debugger) () tcellKeyFn {
	return func( *tcell.EventKey) *tcell.EventKey {
		// skip for log scrolling
		if .Mach.Is1(ss.LogFocused) {
			.Mach.Add1(ss.LogUserScrolled, nil)

			return 

			// skip for other UI components
		} else if .Mach.Any1(keyNavigable...) {
			return 
		}

		if .Mach.Not1(ss.ClientSelected) {
			return nil
		}
		if .hThrottleKey(, arrowThrottleMs) {
			// TODO fast jump scroll while holding the key
			return nil
		}

		// skip if scrolling
		if .shouldScrollCurrView() {
			return 
		}

		// scroll timelines
		 := ss.UserBack
		if .Mach.Is1(ss.TimelineStepsFocused) {
			 = ss.UserBackStep
		}

		// check queue throttle and add the state
		if !.Mach.IsQueuedAbove(scrollTxThrottle, am.MutationAdd, S{}, false,
			false, 0) {

			.Mach.Add1(, nil)
		}

		return nil
	}
}

func ( *Debugger) ( *tcell.EventKey) *tcell.EventKey {
	if .Mach.Not1(ss.ClientSelected) {
		return nil
	}
	if  != nil && .hThrottleKey(, arrowThrottleMs) {
		return nil
	}

	// TODO Start?
	 := context.TODO()
	.Mach.Remove(am.S{ss.Playing, ss.TailMode}, nil)

	if .Mach.Is1(ss.StateNameSelected) {
		 := ss.ScrollToMutTx
		// if d.Mach.Is1(ss.JumpTouch) {
		// 	state = ss.ScrollToTouchTx
		// }

		// state jump
		amhelp.Add1Block(, .Mach, , am.A{
			"state": .C.SelectedState,
			"fwd":   false,
		})
		// sidebar for errs
		.hUpdateClientList()
	} else {
		// fast jump
		amhelp.Add1Block(, .Mach, ss.Back, am.A{
			"amount": min(fastJumpAmount, .C.CursorTx1),
		})
	}

	return nil
}

func ( *Debugger) ( *tcell.EventKey) *tcell.EventKey {
	if .Mach.Not1(ss.ClientSelected) {
		return nil
	}
	if  != nil && .hThrottleKey(, arrowThrottleMs) {
		return nil
	}

	// TODO Start?
	 := context.TODO()
	.Mach.Remove(am.S{ss.Playing, ss.TailMode}, nil)

	if .Mach.Is1(ss.StateNameSelected) {
		 := ss.ScrollToMutTx
		// if d.Mach.Is1(ss.JumpTouch) {
		// 	state = ss.ScrollToTouchTx
		// }

		// state jump
		amhelp.Add1Block(, .Mach, , am.A{
			"state": .C.SelectedState,
			"fwd":   true,
		})
		// sidebar for errs
		.hUpdateClientList()
	} else {
		// fast jump
		amhelp.Add1Block(, .Mach, ss.Fwd, am.A{
			"amount": min(fastJumpAmount, len(.C.MsgTxs)-.C.CursorTx1),
		})
	}

	return nil
}

func ( *Debugger) () {
	 := .Mach.Is1
	 := .Mach.Not1
	switch {

	// default
	case (ss.TreeLogView):
		.Mach.Add1(ss.TreeMatrixView, nil)

	// small matrix
	case (ss.TreeMatrixView):
		if (ss.MatrixRain) {
			.Mach.Add1(ss.MatrixRain, nil)
		} else {
			// enlarge
			.Mach.Remove1(ss.MatrixRain, nil)
			.Mach.Add1(ss.MatrixView, nil)
		}

	// large matrix
	case (ss.MatrixView):
		if (ss.MatrixRain) {
			.Mach.Add1(ss.MatrixRain, nil)
		} else {
			// back to default
			.Mach.Add1(ss.TreeLogView, nil)
		}
	}
}

func ( *Debugger) ( *am.Event) {
	if .Mach.Not1(ss.ClientSelected) {
		return
	}
	.hSetCursor1(, am.A{
		"cursor1":    len(.C.MsgTxs),
		"filterBack": true,
	})
	.Mach.Remove(am.S{ss.TailMode, ss.Playing}, nil)
	// sidebar for errs
	.hUpdateClientList()
	.hRedrawFull(true)
}

func ( *Debugger) ( *am.Event) {
	if .Mach.Not1(ss.ClientSelected) {
		return
	}
	.hSetCursor1(, am.A{
		"cursor1": 0,
	})

	.Mach.Remove(am.S{ss.TailMode, ss.Playing}, nil)
	// sidebar for errs
	.hUpdateClientList()
	.hRedrawFull(true)
}

func ( *Debugger) () {
	// log reader tree
	if .Mach.Is1(ss.LogReaderFocused) {
		 := .logReader.GetRoot()
		 := .GetChildren()
		 := false

		for ,  := range  {
			if .IsExpanded() {
				 = true
				break
			}
			.Collapse()
		}

		// memorize
		.C.ReaderCollapsed = 

		// TODO reader looses focus
		return
	}

	// schema tree
	 := false
	 := .tree.GetRoot().GetChildren()

	for ,  := range  {
		if .IsExpanded() {
			 = true
			break
		}
		.Collapse()
	}

	for ,  := range  {
		if  {
			.Collapse()
			.GetReference().(*nodeRef).expanded = false
		} else {
			.Expand()
			.GetReference().(*nodeRef).expanded = true
		}
	}

	// TODO maybe expand recursively?
}

func ( *Debugger) () bool {
	// always scroll matrix and log views
	return .Mach.Any1(ss.MatrixFocused, ss.LogFocused)
	// TODO scroll tree when relations expanded (support H scroll)
	// d.Mach.Is(am.S{ss.TreeFocused, ss.TimelineStepsScrolled})
}

// TODO optimize usage places
func ( *Debugger) ( *tcell.EventKey,  int) bool {
	// throttle TODO atomics
	 := .lastKeystroke == .Key()
	 := time.Since(.lastKeystrokeTime)
	if  &&  < time.Duration()*time.Millisecond {
		return true
	}

	.lastKeystroke = .Key()
	.lastKeystrokeTime = time.Now()

	return false
}

func ( *Debugger) () {
	var  []cview.Primitive

	// dialogs
	if .Mach.Is1(ss.ExportDialog) {
		.focusable = []*cview.Box{.exportDialog.Box}
		 = []cview.Primitive{.exportDialog}
		.focusManager.Reset()
		.focusManager.Add(...)

		return
	}

	.focusable = []*cview.Box{
		.addressBar.Box, .clientList.Box, .treeGroups.Box, .tree.Box,
	}
	 = []cview.Primitive{
		.addressBar, .clientList, .treeGroups, .tree,
	}

	if .Mach.Not1(ss.ClientListVisible) {
		.focusable = slices.Delete(.focusable, 1, 2)
		 = slices.Delete(, 1, 2)
	}

	// matrix
	if .Mach.Any1(ss.MatrixView, ss.TreeMatrixView) {
		.focusable = append(.focusable, .matrix.Box)
		 = append(, .matrix)

		// log
	} else if .Opts.Filters.LogLevel != am.LogNothing {
		.focusable = append(.focusable, .log.Box)
		 = append(, .log)
	}

	// add log reader
	if .Mach.Is1(ss.LogReaderVisible) {
		.focusable = append(.focusable, .logReader.Box)
		 = append(, .logReader)
	}

	// add timelines
	switch .Opts.Timelines {
	case 2:
		.focusable = append(.focusable, .timelineTxs.Box, .timelineSteps.Box)
		 = append(, .timelineTxs, .timelineSteps)
	case 1:
		.focusable = append(.focusable, .timelineTxs.Box)
		 = append(, .timelineTxs)

	}

	// add toolbars
	.focusable = append(.focusable, .toolbars[0].Box, .toolbars[1].Box,
		.toolbars[2].Box)
	 = append(, .toolbars[0], .toolbars[1], .toolbars[2])

	// unblock bc of locks
	// TODO fix locks
	.focusManager.Reset()
	.focusManager.Add(...)
}