package debugger

import (
	
	
	
	
	
	
	

	
	
	
	

	am 
	
)

const (
	DialogExport = "export"
	DialogHelp   = "help"
)

func ( *Debugger) () {
	.helpDialog = .initHelpDialog()
	.exportDialog = .initExportDialog()

	// tree view
	.tree = .hInitSchemaTree()
	.tree.SetTitle(" Schema ")
	.tree.SetBorder(true)

	// groups dropdown
	.treeGroups = cview.NewDropDown()
	.treeGroups.SetLabel(" Group ")
	.treeGroups.SetFieldBackgroundColor(tcell.GetColor(theme.Highlight))
	.treeGroups.SetDropDownBackgroundColor(tcell.GetColor(theme.Highlight))
	.treeGroups.SetDropDownSelectedTextColor(cview.Styles.PrimaryTextColor)
	.treeGroups.SetDropDownTextColor(cview.Styles.PrimaryTextColor)
	.treeGroups.SetSelectedFunc(func( int,  *cview.DropDownOption) {
		if !.treeGroupSkip {
			//
			.Mach.Add(
				am.S{ss.SetGroup, ss.UpdateFocus, ss.TreeGroupsFocused},
				Pass(&A{Group: .GetText()}),
			)
		}

		.treeGroupSkip = false
	})

	// sidebar
	.hInitClientList()

	// log view
	.log = cview.NewTextView()
	.log.SetBorder(true)
	.log.SetRegions(true)
	.log.SetTextAlign(cview.AlignLeft)
	// wrapping causes perf issues in cview/reindexBuffer
	// TODO add to toolbar as [ ]wrap
	.log.SetWrap(false)
	.log.SetDynamicColors(true)
	.log.SetTitle(" Log ")
	.log.SetHighlightForegroundColor(tcell.GetColor(theme.White))
	.log.SetHighlightBackgroundColor(tcell.GetColor(theme.Highlight2))
	.log.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
	// log click
	 := []string{"[state] ", "[state:auto] "}
	.log.SetClickedFunc(func( string) {
		// unblock
		go func() {
			// scroll to tx
			 := .C.TxIndex()
			.Mach.Add1(ss.ScrollToTx, Pass(&A{
				TxId: ,
			}))

			// select the clicked state
			.Mach.Eval("log.SetClickedFunc", func() {
				defer .Mach.PanicToErr(nil)

				 := .C.MsgTxs[].LogEntries
				for ,  := range  {
					for ,  := range  {
						if strings.HasPrefix(.Text, ) {
							 := strings.Split(.Text[len()+1:], " ")[0]
							.Mach.Add1(ss.StateNameSelected, Pass(&A{
								State: ,
							}))
							break
						}
					}
				}
			}, nil)
		}()
	})

	// TODO hacky
	.overlay = cview.NewTextView()
	.overlay.SetDynamicColors(true)
	.overlay.SetBorder(true)
	.overlay.SetClickedFunc(func( string) {
		.Mach.Remove1(ss.Overlay, nil)
	})

	// reader view
	.logReader = .initLogReader()
	.logReader.SetTitle(" Reader ")
	.logReader.SetBorder(true)
	.logReader.SetScrollBarColor(tcell.GetColor(theme.Highlight2))

	// matrix
	.matrix = cview.NewTable()
	.matrix.SetBorder(true)
	.matrix.SetTitle(" Matrix ")
	.matrix.SetScrollBarVisibility(cview.ScrollBarNever)
	.matrix.SetPadding(0, 0, 1, 0)
	.matrix.SetSelectedStyle(cview.Styles.PrimaryTextColor,
		tcell.GetColor(theme.Highlight), tcell.AttrNone)

	// current tx bar
	.currTxBarLeft = cview.NewTextView()
	.currTxBarLeft.SetDynamicColors(true)
	.currTxBarLeft.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
	.currTxBarRight = cview.NewTextView()
	.currTxBarRight.SetTextAlign(cview.AlignRight)
	.currTxBarRight.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
	.currTxBarRight.SetDynamicColors(true)
	.currTxBarRight.SetClickedFunc(func( string) {
		.Mach.Add1(ss.TimelineTxsFocused, nil)
	})
	.currTxBarLeft.SetClickedFunc(func( string) {
		.Mach.Add1(ss.TimelineTxsFocused, nil)
	})

	// next tx bar
	.nextTxBarLeft = cview.NewTextView()
	.nextTxBarLeft.SetDynamicColors(true)
	.nextTxBarLeft.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
	.nextTxBarRight = cview.NewTextView()
	.nextTxBarRight.SetTextAlign(cview.AlignRight)
	.nextTxBarRight.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
	.nextTxBarRight.SetDynamicColors(true)
	.nextTxBarRight.SetClickedFunc(func( string) {
		.Mach.Add1(ss.TimelineStepsFocused, nil)
	})
	.nextTxBarLeft.SetClickedFunc(func( string) {
		.Mach.Add1(ss.TimelineStepsFocused, nil)
	})

	// timeline tx
	.hInitTimelineTx()

	// timeline steps
	.hInitTimelineSteps()

	// address bar
	.hInitAddressBar()

	// toolbar
	.hInitToolbar()

	.statusBarLeft = cview.NewTextView()
	.statusBarLeft.SetTextAlign(cview.AlignLeft)
	.statusBarLeft.SetDynamicColors(true)
	.statusBarRight = cview.NewTextView()
	.statusBarRight.SetTextAlign(cview.AlignRight)
	.statusBarRight.SetDynamicColors(true)

	// update models
	.hUpdateTimelines()
	.hUpdateTxBars()
	.hUpdateStatusBar()
	.Mach.Add1(ss.UpdateFocus, nil)
}

func ( *Debugger) () {
	.timelineSteps = cview.NewProgressBar()
	.timelineSteps.SetBorder(true)
	// d.timelineSteps.SetFilledColor(tcell.GetColor(theme.Highlight))
	// timeline click
	.timelineSteps.SetMouseCapture(func(
		 cview.MouseAction,  *tcell.EventMouse,
	) (cview.MouseAction, *tcell.EventMouse) {
		 := 
		if  == cview.MouseScrollUp ||  == cview.MouseScrollLeft {
			.Mach.Add1(ss.BackStep, Pass(&A{
				Amount: 1,
			}))

			return , 
		} else if  == cview.MouseScrollDown ||  == cview.MouseScrollRight {
			.Mach.Add1(ss.FwdStep, Pass(&A{
				Amount: 1,
			}))

			return , 
		} else if  != cview.MouseLeftClick {
			// TODO support wheel scrolling
			return , 
		}

		, , ,  := .timelineSteps.GetRect()
		,  := .Position()
		 := float64() / float64()
		 := math.Round(float64(.timelineSteps.GetMax()) * )
		.Mach.Add1(ss.ScrollToStep, Pass(&A{
			CursorStep1: int(),
		}))

		return , 
	})
}

func ( *Debugger) () {
	.timelineTxs = cview.NewProgressBar()
	.timelineTxs.SetBorder(true)
	// d.timelineTxs.SetFilledColor(tcell.GetColor(theme.Highlight))
	// support mouse TODO double click needed with progressive rendering
	.timelineTxs.SetMouseCapture(func(
		 cview.MouseAction,  *tcell.EventMouse,
	) (cview.MouseAction, *tcell.EventMouse) {
		 := 
		 := .C
		if  == nil {
			return , 
		}

		if  == cview.MouseScrollUp ||  == cview.MouseScrollLeft {
			.Mach.Add1(ss.Back, Pass(&A{
				Amount: 5,
			}))

			return , 
		} else if  == cview.MouseScrollDown ||  == cview.MouseScrollRight {
			.Mach.Add1(ss.Fwd, Pass(&A{
				Amount: 5,
			}))

			return , 
		} else if  != cview.MouseLeftClick {
			return , 
		}

		, , ,  := .timelineTxs.GetRect()
		,  := .Position()
		 := float64() / float64()
		// TODO race: eval / lock / state
		 := math.Round(float64(len(.MsgTxs)) * )
		.Mach.Add1(ss.ScrollToTx, Pass(&A{
			CursorTx1:   int(),
			TrimHistory: true,
		}))

		return , 
	})
}

// TODO enum for col indexes
var (
	colPrevMach = 0
	colPrev     = 2
	colNext     = 4
	colNextMach = 6
	colAddr     = 8
	colCopy     = 10
	colPaste    = 12
)

func ( *Debugger) () {
	.addressBar = cview.NewTable()
	.addressBar.SetSelectedStyle(tcell.GetColor(theme.Active),
		cview.Styles.PrimitiveBackgroundColor, tcell.AttrBold)
	.addressBar.SetCellSimple(0, colPrevMach, "◀ mach")
	.addressBar.SetCellSimple(0, 1, " ")
	.addressBar.SetCellSimple(0, colPrev, "◀")
	.addressBar.SetCellSimple(0, 3, " ")
	.addressBar.SetCellSimple(0, colNext, "▶")
	.addressBar.SetCellSimple(0, 5, " ")
	.addressBar.SetCellSimple(0, colNextMach, " mach▶")
	.addressBar.SetCellSimple(0, 7, "")
	.addressBar.SetCellSimple(0, colAddr, "") // addr
	.addressBar.SetCellSimple(0, 9, " ")
	.addressBar.SetCellSimple(0, colCopy, " copy")
	.addressBar.SetCellSimple(0, 11, " ")
	.addressBar.SetCellSimple(0, colPaste, " paste")
	.addressBar.SetSelectable(true, true)
	.addressBar.GetCell(0, 1).SetSelectable(false)
	.addressBar.GetCell(0, 3).SetSelectable(false)
	.addressBar.GetCell(0, 5).SetSelectable(false)
	.addressBar.GetCell(0, 7).SetSelectable(false)
	.addressBar.GetCell(0, 9).SetSelectable(false)
	.addressBar.GetCell(0, 11).SetSelectable(false)
	.addressBar.SetSelectedFunc(func(,  int) {
		switch  {
		case colPrevMach:
			if .HistoryCursor >= len(.History)-1 {
				return
			}
			// scan until machId changes
			for  := .HistoryCursor;  < len(.History)-1; ++ {
				if .History[].MachId != .History[+1].MachId {
					.HistoryCursor =  + 1
					break
				}
			}
			 := .History[.HistoryCursor]
			// d.params.Print("go:\n" + addr.String() + "\n")
			.GoToMachAddress(, true)
		case colPrev:
			if .HistoryCursor >= len(.History)-1 {
				return
			}
			.HistoryCursor++
			 := .History[.HistoryCursor]
			// d.params.Print("go:\n" + addr.String() + "\n")
			.GoToMachAddress(, true)

		case colNext:
			if .HistoryCursor <= 0 {
				return
			}
			.HistoryCursor--
			 := .History[.HistoryCursor]
			// d.params.Print("go:\n" + addr.String() + "\n")
			.GoToMachAddress(, true)
		case colNextMach:
			if .HistoryCursor <= 0 {
				return
			}
			// scan until machId changes
			for  := .HistoryCursor;  > 0; -- {
				if .History[].MachId != .History[-1].MachId {
					.HistoryCursor =  - 1
					break
				}
			}
			 := .History[.HistoryCursor]
			// d.params.Print("go:\n" + addr.String() + "\n")
			.GoToMachAddress(, true)

		case colCopy:
			if .clip == nil {
				return
			}
			 := .hGetMachAddress().String()
			_ = .clip.WriteAll(clipper.RegClipboard, []byte())

		case colPaste:
			if .clip == nil {
				return
			}
			,  := .clip.ReadAll(clipper.RegClipboard)
			if ,  := types.ParseMachUrl(string());  == nil {
				.GoToMachAddress(, false)
			}
		}

		// TODO state for button down
		.addressBar.SetSelectedStyle(tcell.GetColor(theme.Active),
			cview.Styles.PrimitiveBackgroundColor, tcell.AttrUnderline)
		go func() {
			time.Sleep(time.Millisecond * 200)
			.addressBar.SetSelectedStyle(tcell.GetColor(theme.Active),
				cview.Styles.PrimitiveBackgroundColor, tcell.AttrBold)
			.draw(.addressBar)
		}()
	})

	 := .addressBar.GetCell(0, colAddr)
	.SetExpansion(1)
	.SetSelectable(false)
	.SetAlign(cview.AlignCenter)

	.tagsBar = cview.NewTextView()
	.tagsBar.SetTextColor(tcell.GetColor(theme.Grey))
	.tagsBar.SetDynamicColors(true)
	.hUpdateAddressBar()
}

func ( *Debugger) () {
	for  := range .toolbars {

		.toolbars[] = cview.NewTable()
		.toolbars[].ScrollToBeginning()
		.toolbars[].SetSelectedStyle(tcell.GetColor(theme.Active),
			cview.Styles.PrimitiveBackgroundColor, tcell.AttrBold)
		.toolbars[].SetSelectable(true, true)
		// TODO remove empty space
		.toolbars[].SetBorders(false)

		// click effect
		.toolbars[].SetSelectedFunc(func(,  int) {
			if  >= len(.toolbarItems[]) ||  == -1 {
				return
			}

			.toolbars[].SetSelectedStyle(tcell.GetColor(theme.Active),
				cview.Styles.PrimitiveBackgroundColor, tcell.AttrUnderline)
			 := .toolbarItems[][]
			.Mach.Add1(ss.ToggleTool, Pass(&A{
				ToolName: .id,
			}))
			go func() {
				// prevent toolbar stealing focus from dialogs
				if .skipFocus {
					.Mach.Add1(ss.UpdateFocus, nil)
					.hUpdateToolbar()
				}

				// unclick
				time.Sleep(time.Millisecond * 200)
				.toolbars[].SetSelectedStyle(tcell.GetColor(theme.Active),
					cview.Styles.PrimitiveBackgroundColor, tcell.AttrBold)
				.draw(.toolbars[])
			}()
		})

		// key navi wrap around TODO move to keyboard
		.toolbars[].SetInputCapture(func( *tcell.EventKey) *tcell.EventKey {
			,  := .toolbars[].GetSelection()

			// left / right
			if .Key() == tcell.KeyRight &&  == len(.toolbarItems[])-1 {
				.toolbars[].Select(, 0)
				return nil
			}
			if .Key() == tcell.KeyLeft &&  == 0 {
				.toolbars[].Select(, len(.toolbarItems[])-1)
				return nil
			}

			// up / down to other toolbars
			,  := -1, ""
			if .Key() == tcell.KeyDown &&  < 3 {
				 =  + 1
				 = []string{
					ss.Toolbar2Focused, ss.Toolbar3Focused, ss.Toolbar4Focused,
				}[]
			} else if .Key() == tcell.KeyUp &&  > 0 {
				 =  - 1
				 = []string{
					ss.Toolbar1Focused, ss.Toolbar2Focused, ss.Toolbar3Focused,
				}[-1]
			}
			if  != -1 {
				.toolbars[].Select(
					0, min(, .toolbars[].GetColumnCount()-1),
				)
				.Mach.Add1(, nil)
				.Mach.Add1(ss.UpdateFocus, nil)
				.hUpdateToolbar()
				return nil
			}

			// fwd
			return 
		})
	}

	// TODO light mode button
	// TODO save filters per machine checkbox
	// TODO next error
	.toolbarItems = [4][]toolbarItem{
		// row 1
		{
			{id: types.ToolPrevClient, label: "", icon: "▲"},
			{id: types.ToolNextClient, label: "", icon: "▼"},
			{id: types.ToolJumpPrev, label: "jump", icon: "◀ "},
			{id: types.ToolPrevStep, label: "step", icon: "<"},
			{id: types.ToolPrev, label: "tx", icon: "◁ "},
			{id: types.ToolNext, label: "tx", icon: "▷ "},
			{id: types.ToolNextStep, label: "step", icon: ">"},
			{id: types.ToolJumpNext, label: "jump", icon: "▶ "},
			{id: types.ToolPlay, label: "play", active: func() bool {
				return .Mach.Is1(ss.Playing)
			}},
			{id: types.ToolFirst, label: "first", icon: "1"},
			{id: types.ToolLast, label: "last", icon: "N"},
		},

		// row 2
		{
			{id: types.ToolLog, label: "log", active: func() bool {
				return .params.Filters.LogLevel > am.LogNothing
			}, activeLabel: func() string {
				return strconv.Itoa(int(.params.Filters.LogLevel))
			}},
			{id: types.ToolFilterCanceledTx, label: "canceled", active: func() bool {
				return .Mach.Not1(ss.FilterCanceledTx)
			}},
			{id: types.ToolFilterQueuedTx, label: "queued", active: func() bool {
				return .Mach.Not1(ss.FilterQueuedTx)
			}},
			{id: types.ToolFilterAutoTx, label: "auto", active: func() bool {
				return .Mach.Is1(ss.FilterAutoCanceledTx) ||
					.Mach.Not1(ss.FilterAutoTx)
			}, activeLabel: func() string {
				switch .Mach.Switch(am.S{ss.FilterAutoTx, ss.FilterAutoCanceledTx}) {
				case ss.FilterAutoTx:
					return " "
				case ss.FilterAutoCanceledTx:
					return "*"
				default:
					return "X"
				}
			}},
			{id: types.ToolFilterEmptyTx, label: "empty", active: func() bool {
				return .Mach.Not1(ss.FilterEmptyTx)
			}},
			{id: types.ToolFilterHealth, label: "health", active: func() bool {
				return .Mach.Not1(ss.FilterHealth)
			}},
			{id: types.ToolFilterOutGroup, label: "group", active: func() bool {
				return .Mach.Is1(ss.FilterOutGroup)
			}},
			{id: types.ToolFilterDisconn, label: "disconn", active: func() bool {
				return .Mach.Not1(ss.FilterDisconn)
			}},
			{id: types.ToolFilterChecks, label: "checks", active: func() bool {
				return .Mach.Not1(ss.FilterChecks)
			}},
			{id: types.ToolLogTimestamps, label: "times", active: func() bool {
				return .Mach.Not1(ss.LogTimestamps)
			}},
			{id: types.ToolFilterRpcMachs, label: "rpc", active: func() bool {
				return .Mach.Not1(ss.FilterRpcMachs)
			}},
			// TODO values t / c / m
			//  touch / called / mutation (change)
			//  eg "[call]jump"
			// {id: ToolFilterTraces, label: "jump", active: func() bool {
			// 	return d.Mach.Not1(ss.FilterJumpTouch)
			// }},
		},

		// row 3
		{
			{
				id: types.ToolExport, label: "export", skipFocus: true,
				active: func() bool { return .Mach.Is1(ss.ExportDialog) },
			},
			{
				id:    types.ToolExpand,
				label: "expand", active: func() bool {
					 := .treeRoot.GetChildren()
					return len() > 0 && [0].IsExpanded()
				},
			},
			{id: types.ToolFilterTraces, label: "traces", active: func() bool {
				return .Mach.Not1(ss.FilterTraces)
			}},
			{id: types.ToolDiagrams, label: "diagrams", active: func() bool {
				return .params.OutputDiagrams.Value > 0
			}, activeLabel: func() string {
				return strconv.Itoa(.params.OutputDiagrams.Value)
			}},
			{id: types.ToolDiagramsTx, label: "diag-tx", active: func() bool {
				return .params.OutputDiagTx != types.ParamsOutDiagTxNone
			}, activeLabel: func() string {
				switch .params.OutputDiagTx {
				default:
					return " "
				case types.ParamsOutDiagTxCalled:
					return "C"
				case types.ParamsOutDiagTxMutated:
					return "M"
				case types.ParamsOutDiagTxTouched:
					return "T"
				case types.ParamsOutDiagTxRelations:
					return "R"
				}
			}},
			{id: types.ToolDiagramsGroup, label: "diag-group", active: func() bool {
				return .params.OutputDiagGroup != types.ParamsOutDiagGroupNone
			}, activeLabel: func() string {
				switch .params.OutputDiagGroup {
				default:
					return " "
				case types.ParamsOutDiagGroupHide:
					return "H"
				case types.ParamsOutDiagGroupSkip:
					return "S"
				}
			}},
			{id: types.ToolDiagramsSteps, label: "diag-steps", active: func() bool {
				return .params.OutputTx
			}},
			{id: types.ToolCallLog, label: "call-log", active: func() bool {
				return .params.OutputCallLog
			}},
			{id: types.ToolOutputLog, label: "out-log", active: func() bool {
				return .params.OutputLog
			}},
		},

		// row 4
		{
			{id: types.ToolHelp, label: "[yellow::b]help[-]", active: func() bool {
				return .Mach.Is1(ss.HelpDialog)
			}},
			{id: types.ToolTail, label: "[yellow::b]tail[-::-]", active: func() bool {
				return .Mach.Is1(ss.TailMode)
			}},
			{id: types.ToolReader, label: "reader", active: func() bool {
				return .Mach.Is1(ss.LogReaderVisible)
			}},
			{id: types.ToolTimelines, label: "timelines", active: func() bool {
				return .params.ViewTimelines.Value > 0
			}, activeLabel: func() string {
				return strconv.Itoa(.params.ViewTimelines.Value)
			}},
			{id: types.ToolLogWrap, label: "wrap", active: func() bool {
				return .params.ViewLogWrap
			}},
			{id: types.ToolNarrowLayout, label: "narrow", active: func() bool {
				return .Mach.Is1(ss.NarrowLayout)
			}},
			{id: types.ToolRain, label: "rain", active: func() bool {
				return .Mach.Is1(ss.MatrixRain)
			}},
			{id: types.ToolMatrix, label: "matrix", active: func() bool {
				return .Mach.Any1(ss.MatrixView, ss.TreeMatrixView)
			}},
		},
	}

	if .params.AddrHttp != "" {
		.toolbarItems[3] = slices.Insert(
			.toolbarItems[3], 3,
			toolbarItem{id: types.ToolWeb, label: "web", active: func() bool {
				return false
			}},
		)
	}

	.hUpdateToolbar()
}

func ( *Debugger) () *cview.Modal {
	 := cview.NewModal()
	 := .GetForm()
	.AddInputField("Filename", "am-dbg-dump", 20, nil, nil)
	.AddCheckBox("Single Frame", "", false, func( bool) {})

	.SetText("Export to a file")
	// exportDialog.AddButtons([]string{"Save"})
	.AddButtons([]string{"Save", "Cancel"})
	.SetDoneFunc(func( int,  string) {
		,  := .GetFormItem(0).(*cview.InputField)
		if ! {
			.Mach.Log("Error: export dialog field not found")
			return
		}
		,  := .GetFormItem(1).(*cview.CheckBox)
		if ! {
			.Mach.Log("Error: export dialog single frame checkbox not found")
		}

		// form data
		 := .GetText()
		 := .IsChecked()

		defer .Mach.Remove(am.S{ss.ExportDialog, ss.DialogFocused}, nil)
		if  == "Save" &&  != "" {
			.GetButton(0).SetLabel("Saving...")
			// form.Draw(d.App.GetScreen())
			.draw()
			.hExportData(, )
			.GetButton(0).SetLabel("Save")
		}
	})

	return 
}

// TODO better approach to modals
// TODO modal titles
// TODO state color meanings
// TODO page up/down on tx timeline
func ( *Debugger) () *cview.Flex {
	 := cview.NewTextView()
	.SetBackgroundColor(tcell.GetColor(theme.Highlight))
	.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
	.SetTitle(" Legend ")
	.SetDynamicColors(true)
	.SetPadding(1, 1, 1, 1)
	.SetText(fmt.Sprintf(dedent.Dedent(strings.Trim(`
		[::b]### [::u]schema legend[::-]
		[%s::b]state[-]        active state
		[red::b]state[-]        active state (error)
		[%s::b]state[-]        active multi state
		[%s::b]state[-]        inactive state
		[::b]M|[::-]           multi state
		[::b]|5[::-]           tick value
		[::b]*[::-]            executed handler
		[::b]+[::-]            to be activated
		[::b]-[::-]            to be de-activated
		[::b]bold[::-]         touched state
		[::b]underline[::-]    called state
		[::b]![::-]            state canceled
		[::b]|[::-]            rel link
		[green::b]|[-::-]            rel link start
		[red::b]|[-::-]            rel link end
	
		[::b]### [::u]matrix rain legend[::-]
		[::b]1[::-]            state active
		[::b]2[::-]            state active and touched
		[::b]-[::-]            state touched
		[::b]|[::-]            state de-activated
		[::b]c[::-]            state canceled
		underline    state called
	
		[::b]### [::u]dashboard keystrokes[::-]
		[::b]alt arrow[::-]    navigate to tiles
		[::b]alt -+[::-]       change size of a tile
	
		[::b]### [::u]matrix legend[::-]
		[::b]underline[::-]    called state
		[::b]1st row[::-]      called states
		             col == state index
		[::b]2nd row[::-]      state tick changes
		             col == state index
		[::b]>=3 row[::-]      state relations
		             cartesian product
		             col == source state index
		             row == target state index
	
		[::b]### [::u]log legend[::-]
		[:green] [:-]            Executed
		[:yellow] [:-]            Queued
		[:red] [:-]            Canceled
		%s               queued regular transition
		%s               queued auto transition
		%s               state diff after a reg transition
		%s               state diff after an auto transition
		%s               canceled transition (any)
	`, "\n ")), theme.Active, theme.Active2, theme.Inactive,
		cview.Escape("[queue]"), cview.Escape("[aqueu]"),
		cview.Escape("[state]"), cview.Escape("[auto_]"),
		cview.Escape("[cance]")))

	// render the right side separately
	.hUpdateHelpDialog()

	 := cview.NewGrid()
	.SetTitle(" asyncmachine-go debugger ")
	.SetColumns(0, 0)
	.SetRows(0)
	.AddItem(, 0, 0, 1, 1, 0, 0, false)
	.AddItem(.helpDialogRight, 0, 1, 1, 1, 0, 0, false)
	.helpDialogLeft = 

	 := cview.NewBox()
	.SetBackgroundTransparent(true)
	 := cview.NewBox()
	.SetBackgroundTransparent(true)
	 := cview.NewBox()
	.SetBackgroundTransparent(true)
	 := cview.NewBox()
	.SetBackgroundTransparent(true)

	 := cview.NewFlex()
	.AddItem(, 0, 1, false)
	.AddItem(, 0, 4, false)
	.AddItem(, 0, 1, false)

	 := cview.NewFlex()
	.SetDirection(cview.FlexRow)
	.AddItem(, 0, 1, false)
	.AddItem(, 0, 4, false)
	.AddItem(, 0, 1, false)

	 := []*cview.Box{, , , }

	// close on click TODO loops when clicked on toolbar help
	.SetMouseCapture(func(
		 cview.MouseAction,  *tcell.EventMouse,
	) (cview.MouseAction, *tcell.EventMouse) {
		if  == cview.MouseLeftClick {
			,  := .Position()
			for ,  := range  {
				if .InRect(, ) {
					go .Mach.Remove1(ss.HelpDialog, nil)
					return cview.MouseLeftClick, 
				}
			}
		}

		return , 
	})

	return 
}

func ( *Debugger) () {
	 := int(AllocMem() / 1024 / 1024)
	if .helpDialogRight == nil {
		.helpDialogRight = cview.NewTextView()
	}
	.helpDialogRight.SetBackgroundColor(tcell.GetColor(theme.Highlight))
	.helpDialogRight.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
	.helpDialogRight.SetTitle(" Keystrokes ")
	.helpDialogRight.SetDynamicColors(true)
	.helpDialogRight.SetPadding(1, 1, 1, 1)

	// TODO link[#FF5FAF]  to color 205
	.helpDialogRight.SetText(fmt.Sprintf(dedent.Dedent(strings.Trim(`
		[::b]### [::u]keystrokes[::-]
		[::b]tab[::-]                change focus
		[::b]shift tab[::-]          change focus
		[::b]space[::-]              play/pause
		[::b]left/right[::-]         prev/next tx
		[::b]left/right[::-]         scroll log
		[::b]alt left/right[::-]     fast jump
		[::b]alt j/k[::-]            prev/next step
		[::b]alt h/l[::-]            fast jump
		[::b]alt h/l[::-]            state jump (when selected)
		[::b]up/down[::-]            scroll / navigate
		[::b]j/k[::-]                scroll / navigate
		[::b]alt j/k[::-]            page up/down
		[::b]alt e[::-]              expand/collapse tree
		[::b]enter[::-]              expand/collapse node
		[::b]alt v[::-]              tail mode
		[::b]alt r[::-]              rain view
		[::b]alt m[::-]              matrix views
		[::b]alt o[::-]              log reader
		[::b]home/end[::-]           schema / last tx
		[::b]alt s[::-]              export data
		[::b]backspace[::-]          remove client
		[::b]alt-d[::-]              remove client
		[::b]esc[::-]                focus mach list
		[::b]ctrl q[::-]             quit
		[::b]?[::-]                  show help
	
		[::b]### [::u]machine list legend[::-]
		[::b]T:t123[::-]              total received graph time
		[%s::b]client-id[-::-]          connected
		[grey::b]client-id[-::-]          disconnected
		[red::b]client-id[-::-]          current error
		[#FF5FAF::b]client-id[-::-]          recent error
		[::bu]client-id[::-]          selected machine
		[::b]|123[::-]               transitions till now
		[::b]|123+[::-]              more transitions left
		[::b]S|[::-]                 Start active
		[::b]R|[::-]                 Ready active
	
		[::b]### [::u]toolbar legend[::-]
		[::b]auto *[::-]        skip canceled auto mutations
		[::b]auto x[::-]        skip all auto mutations
		[::b]times[::-]         show timestamps in the log
		[::b]health[::-]        include Healthcheck and Heartbeat
		[::b]group[::-]         show transition only from the 
		              selected group
		[::b]disconn[::-]        show disconnected clients
		[::b]checks[::-]        include Can* mutations
		[::b]rpc[::-]           show RPC machines
		[::b]expand[::-]        expand tree in the currently
		              focused tile
		[::b]traces[::-]        show stack traced in the log
		[::b]diagrams N[::-]    render a diagram with N level
		              of detail
		[::b]diag-tx[::-]       highlight transition-related states
		              (called, mutated, touched, relations)
		[::b]diag-group[::-]    skip or hide states outside of the
		              currently selected group
		[::b]diag-seq[::-]      generate transtion sequence diagrams
		[::b]call-log[::-]      generate call-log/mach/*.go handler
		              call files (for new msgs only)
		[::b]out-log[::-]       dump log buffer into log.md
		[::b]rain[::-]          transition per line with 1 char
		              per state
		[::b]matrix[::-]        transition vectors
	
		[::b]### [::u]status bar legend[::-]
		[::b]Graph:t123[::-]        current graph time
	
		[::b]### [::u]about am-dbg[::-]
		%-15s    version
		%-15s    DBG server addr
		%-15s    HTTP server addr
		%-15s    SSH server addr
		%-15s    mem usage
	`, "\n ")), theme.Active, .params.Version, .params.AddrRpc,
		.params.AddrHttp, .params.AddrSsh, strconv.Itoa()+"mb"))
}

func ( *Debugger) () {
	// tree schema
	.treeLayout = cview.NewFlex()
	.treeLayout.SetDirection(cview.FlexRow)
	.treeLayout.AddItem(.treeGroups, 1, 1, false)
	.treeLayout.AddItem(.tree, 0, 1, false)

	// transition rows
	.currTxBar = cview.NewFlex()
	.currTxBar.AddItem(.currTxBarLeft, 0, 1, false)
	.currTxBar.AddItem(.currTxBarRight, 0, 1, false)

	.nextTxBar = cview.NewFlex()
	.nextTxBar.AddItem(.nextTxBarLeft, 0, 1, false)
	.nextTxBar.AddItem(.nextTxBarRight, 0, 1, false)

	// content grid
	.schemaLogGrid = cview.NewGrid()
	.schemaLogGrid.SetRows(-1)
	// TODO use hUpdateSchemaLogGrid()
	.schemaLogGrid.SetColumns( /*tree*/ -1, -1 /*log*/, -1, -1, -1, -1)
	.schemaLogGrid.AddItem(.treeLayout, 0, 0, 1, 2, 0, 0, false)
	.schemaLogGrid.AddItem(.log, 0, 2, 1, 4, 0, 0, false)

	.treeMatrixGrid = cview.NewGrid()
	.treeMatrixGrid.SetRows(-1)
	.treeMatrixGrid.SetColumns( /*tree*/ -1, -1 /*log*/, -1, -1, -1, -1)
	.treeMatrixGrid.AddItem(.treeLayout, 0, 0, 1, 2, 0, 0, false)
	.treeMatrixGrid.AddItem(.matrix, 0, 2, 1, 4, 0, 0, false)

	// content panels
	.contentPanels = cview.NewPanels()
	.contentPanels.AddPanel("tree-log", .schemaLogGrid, true, true)
	.contentPanels.AddPanel("tree-matrix", .treeMatrixGrid, true, false)
	.contentPanels.AddPanel("matrix", .matrix, true, false)
	.contentPanels.SetBackgroundColor(tcell.GetColor(theme.Highlight))

	// main grid
	.mainGrid = cview.NewGrid()

	 := cview.NewPanels()
	.AddPanel(DialogExport, .exportDialog, false, true)
	.AddPanel(DialogHelp, .helpDialog, true, true)
	.AddPanel("overlay", .overlay, false, true)
	.AddPanel("main", .mainGrid, true, true)

	.LayoutRoot = 

	.hUpdateLayout()
	.hUpdateBorderColor()
}

func ( *Debugger) () {
	if .mainGrid == nil {
		return
	}

	.mainGrid.Clear()
	.mainGrid.SetRows(1, 1, -1, 1, 1, 1, 1, 1)

	// columns
	var  []int
	if .Mach.Is1(ss.NarrowLayout) {
		 = []int{ /*content*/ -1, -1, -1, -1, -1, -1, -1, -1}
	} else {
		 = []int{
			/*client list*/ -1,
			/*content*/ -1, -1, -1, -1, -1, -1, -1, -1,
		}
	}
	.mainGridCols = 
	.mainGrid.SetColumns(...)

	// row 0
	.mainGrid.AddItem(.addressBar, 0, 0, 1, len(), 0, 0, false)

	// row 1
	.mainGrid.AddItem(.tagsBar, 1, 0, 1, len(), 0, 0, false)

	// row 2
	if .Mach.Not1(ss.NarrowLayout) {
		.mainGrid.AddItem(.clientList, 2, 0, 1, 2, 0, 0, true)

		// row 2 mid, right
		.mainGrid.AddItem(.contentPanels, 2, 2, 1, 7, 0, 0, false)
	} else {
		// row 2 mid, right
		.mainGrid.AddItem(.contentPanels, 2, 0, 1, len(), 0, 0, false)
	}

	 := 3

	// timelines
	if .Mach.Not1(ss.TimelineTxHidden) {
		.mainGrid.SetRows(1, 1, -1, 2, 3, 1, 1, 1, 1, 1)

		.mainGrid.AddItem(.currTxBar, , 0, 1, len(), 0, 0, false)
		++
		.mainGrid.AddItem(.timelineTxs, , 0, 1, len(), 0, 0, false)
		++
	}
	if .Mach.Not1(ss.TimelineStepsHidden) {
		.mainGrid.SetRows(1, 1, -1, 2, 3, 2, 3, 1, 1, 1, 1, 1)

		.mainGrid.AddItem(.nextTxBar, , 0, 1, len(), 0, 0, false)
		++
		.mainGrid.AddItem(.timelineSteps, , 0, 1, len(), 0, 0, false)
		++
	}

	// toolbars and status
	.mainGrid.AddItem(.toolbars[0], , 0, 1, len(), 0, 0, false)
	++
	.mainGrid.AddItem(.toolbars[1], , 0, 1, len(), 0, 0, false)
	++
	.mainGrid.AddItem(.toolbars[2], , 0, 1, len(), 0, 0, false)
	++
	.mainGrid.AddItem(.toolbars[3], , 0, 1, len(), 0, 0, false)
	++
	.mainGrid.AddItem(.statusBarLeft, , 0, 1,
		len()-len()/3, 0, 0, false)
	.mainGrid.AddItem(.statusBarRight, , len()-len()/3, 1,
		len()/3, 0, 0, false)

	.hUpdateFocusableList()
}

func ( *Debugger) () {
	.hUpdateViews(true)
	.Mach.Add1(ss.UpdateFocus, nil)
	.draw()
}

// hRedrawFull updates the common components of the UI, except:
// - client list,
// - toolbars
// - matrices
// Then, it schedules a redraw.
func ( *Debugger) ( bool) {
	.hUpdateViews()
	.hUpdateTimelines()
	.hUpdateTxBars()
	.hUpdateStatusBar()
	.hUpdateBorderColor()
	.hUpdateAddressBar()
	.draw()
}

func ( *Debugger) ( ...cview.Primitive) {
	// TODO detect visibility of requested components??? or in cview
	if !.repaintScheduled.CompareAndSwap(false, true) {
		return
	}

	go func() {
		select {
		case <-.Mach.Context().Done():
			return

		// debounce every 16msec
		case <-time.After(16 * time.Millisecond):
		}

		// TODO re-draw only changed components
		// TODO re-draw only c.Box.Draw() when simply changing focus
		// TODO maybe: use QueueUpdate and call GetRoot.Draw() in Eval? bench and
		//  compare?
		.App.QueueUpdateDraw(func() {
			// run and dispose a registered callback
			if .redrawCallback != nil {
				.redrawCallback()
				.redrawCallback = nil
			}
			.repaintScheduled.Store(false)
		}, ...)
	}()
}

func ( *Debugger) () {
	, , ,  := .LayoutRoot.GetRect()

	if  < 100 || .Mach.Is1(ss.UserNarrowLayout) {
		.Mach.Add1(ss.NarrowLayout, nil)
	} else if !.params.ViewNarrow {
		// remove if not forced
		.Mach.Remove1(ss.NarrowLayout, nil)
	}
}

func ( *Debugger) () {
	 := .params.Filters.LogLevel

	.schemaLogGrid.RemoveItem(.log)
	.schemaLogGrid.RemoveItem(.logReader)
	.schemaLogGrid.RemoveItem(.matrix)
	.schemaLogGrid.SetColumns( /*tree*/ -1, -1 /*log*/, -1, -1, -1, -1)

	 :=  > am.LogNothing
	 := .Mach.Is1(ss.LogReaderVisible)
	 := .Mach.Any1(ss.TimelineStepsScrolled, ss.TimelineStepsFocused)
	 := .Mach.Is1(ss.TreeMatrixView)

	// TODO flexbox...
	switch {

	// log

	case  &&  && :
		.schemaLogGrid.UpdateItem(.treeLayout, 0, 0, 1, 3, 0, 0, false)
		.schemaLogGrid.AddItem(.log, 0, 3, 1, 2, 0, 0, false)
		.schemaLogGrid.AddItem(.logReader, 0, 5, 1, 1, 0, 0, false)

	case  && :
		.schemaLogGrid.UpdateItem(.treeLayout, 0, 0, 1, 2, 0, 0, false)
		.schemaLogGrid.AddItem(.log, 0, 2, 1, 2, 0, 0, false)
		.schemaLogGrid.AddItem(.logReader, 0, 4, 1, 2, 0, 0, false)

	case  && !:
		.schemaLogGrid.UpdateItem(.treeLayout, 0, 0, 1, 2, 0, 0, false)
		.schemaLogGrid.AddItem(.log, 0, 2, 1, 4, 0, 0, false)

	case ! && :
		.schemaLogGrid.UpdateItem(.treeLayout, 0, 0, 1, 3, 0, 0, false)
		.schemaLogGrid.AddItem(.logReader, 0, 3, 1, 3, 0, 0, false)

	// matrix

	case  && :
		.schemaLogGrid.UpdateItem(.treeLayout, 0, 0, 1, 3, 0, 0, false)
		.schemaLogGrid.AddItem(.matrix, 0, 3, 1, 3, 0, 0, false)

	case :
		.schemaLogGrid.UpdateItem(.treeLayout, 0, 0, 1, 2, 0, 0, false)
		.schemaLogGrid.AddItem(.matrix, 0, 3, 1, 4, 0, 0, false)

	// none

	case ! && ! && !:
		.schemaLogGrid.UpdateItem(.treeLayout, 0, 0, 1, 6, 0, 0, false)
	}
}

func ( *Debugger) ( any) (*cview.Box, string) {
	var  *cview.Box
	var  string

	switch  {

	case .treeGroups:
		fallthrough
	case .treeGroups.Box:
		 = .treeGroups.Box
		 = ss.TreeGroupsFocused

	case .tree:
		fallthrough
	case .tree.Box:
		 = .tree.Box
		 = ss.TreeFocused

	case .log:
		fallthrough
	case .log.Box:
		 = .log.Box
		 = ss.LogFocused

	case .logReader:
		fallthrough
	case .logReader.Box:
		 = .logReader.Box
		 = ss.LogReaderFocused

	case .timelineTxs:
		fallthrough
	case .timelineTxs.Box:
		 = .timelineTxs.Box
		 = ss.TimelineTxsFocused

	case .timelineSteps:
		fallthrough
	case .timelineSteps.Box:
		 = .timelineSteps.Box
		 = ss.TimelineStepsFocused

	case .toolbars[0]:
		fallthrough
	case .toolbars[0].Box:
		 = .toolbars[0].Box
		 = ss.Toolbar1Focused

	case .toolbars[1]:
		fallthrough
	case .toolbars[1].Box:
		 = .toolbars[1].Box
		 = ss.Toolbar2Focused

	case .toolbars[2]:
		fallthrough
	case .toolbars[2].Box:
		 = .toolbars[2].Box
		 = ss.Toolbar3Focused

	case .toolbars[3]:
		fallthrough
	case .toolbars[3].Box:
		 = .toolbars[3].Box
		 = ss.Toolbar4Focused

	case .addressBar:
		fallthrough
	case .addressBar.Box:
		 = .addressBar.Box
		 = ss.AddressFocused

	case .clientList:
		fallthrough
	case .clientList.Box:
		 = .clientList.Box
		 = ss.ClientListFocused

	case .matrix:
		fallthrough
	case .matrix.Box:
		 = .matrix.Box
		 = ss.MatrixFocused

	// DIALOGS

	case .helpDialogLeft:
		fallthrough
	case .helpDialogLeft.Box:
		 = .helpDialogLeft.Box
		 = ss.DialogFocused

	case .helpDialogRight:
		fallthrough
	case .helpDialogRight.Box:
		 = .helpDialogRight.Box
		 = ss.DialogFocused

	case .exportDialog:
		fallthrough
	case .exportDialog.Box:
		 = .exportDialog.Box
		 = ss.DialogFocused
	}

	return , 
}

var (
	trimTailWhitespaceRe = regexp.MustCompile(` +\n`)
	foldNewlinesRe       = regexp.MustCompile(`\n{3,}`)
)

// treeToText will get a full render of a tree as text
func treeToText( *cview.TreeView) string {
	 := tcell.NewSimulationScreen("UTF-8")
	.SetSize(1000, 1000)
	 := cview.NewBox()
	.SetRect(0, 0, 1000, 1000)
	 := .Box
	.Box = 
	.Draw()
	 := foldNewlinesRe.ReplaceAllString(
		trimTailWhitespaceRe.ReplaceAllString(
			cview.TextFromScreen(), "\n",
		), "\n",
	)
	.Box = 
	return 
}

// treeToText will get a full render of a tree as text
func textViewToText( *cview.TextView) string {
	 := tcell.NewSimulationScreen("UTF-8")
	.SetSize(1000, 1000)
	 := cview.NewBox()
	.SetRect(0, 0, 1000, 1000)
	 := .Box
	.Box = 
	.Draw()
	 := foldNewlinesRe.ReplaceAllString(
		trimTailWhitespaceRe.ReplaceAllString(
			cview.TextFromScreen(), "\n",
		), "\n",
	)
	.Box = 
	return 
}