package debugger

import (
	
	
	
	
	
	

	
	
	
	

	

	am 
	ss 
)

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(colorHighlight)
	.treeGroups.SetDropDownBackgroundColor(colorHighlight)
	.treeGroups.SetDropDownSelectedTextColor(colorDefault)
	.treeGroups.SetDropDownTextColor(colorDefault)
	.treeGroups.SetSelectedFunc(func( int,  *cview.DropDownOption) {
		// TODO typed args
		.Mach.Add1(ss.SetGroup, am.A{"group": .GetText()})
	})

	// sidebar
	.initClientList()

	// 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.ColorWhite)
	.log.SetHighlightBackgroundColor(colorHighlight2)
	.log.SetScrollBarColor(colorHighlight2)
	// log click
	 := []string{"[state] ", "[state:auto] "}
	.log.SetClickedFunc(func( string) {
		// unblock
		go func() {
			// scroll to tx
			 := .C.TxIndex()
			.Mach.Add1(ss.ScrollToTx, am.A{"Client.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, am.A{"state": })
							break
						}
					}
				}
			}, nil)
		}()
	})

	// reader view
	.logReader = .initLogReader()
	.logReader.SetTitle(" Reader ")
	.logReader.SetBorder(true)
	.logReader.SetScrollBarColor(colorHighlight2)

	// matrix
	.matrix = cview.NewTable()
	.matrix.SetBorder(true)
	.matrix.SetTitle(" Matrix ")
	.matrix.SetScrollBarVisibility(cview.ScrollBarNever)
	.matrix.SetPadding(0, 0, 1, 0)
	.matrix.SetSelectedStyle(colorDefault, colorHighlight, tcell.AttrNone)

	// current tx bar
	.currTxBarLeft = cview.NewTextView()
	.currTxBarLeft.SetDynamicColors(true)
	.currTxBarLeft.SetScrollBarColor(colorHighlight2)
	.currTxBarRight = cview.NewTextView()
	.currTxBarRight.SetTextAlign(cview.AlignRight)
	.currTxBarRight.SetScrollBarColor(colorHighlight2)
	.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(colorHighlight2)
	.nextTxBarRight = cview.NewTextView()
	.nextTxBarRight.SetTextAlign(cview.AlignRight)
	.nextTxBarRight.SetScrollBarColor(colorHighlight2)
	.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()

	// TODO status bar (keystrokes, msgs), step info bar (type, from, to, data)
	.statusBar = cview.NewTextView()
	.statusBar.SetTextAlign(cview.AlignRight)
	.statusBar.SetDynamicColors(true)

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

func ( *Debugger) () {
	.timelineSteps = cview.NewProgressBar()
	.timelineSteps.SetBorder(true)
	.timelineSteps.SetFilledColor(tcell.ColorLightGray)
	// timeline click
	.timelineSteps.SetMouseCapture(func(
		 cview.MouseAction,  *tcell.EventMouse,
	) (cview.MouseAction, *tcell.EventMouse) {
		 := 
		if  == cview.MouseScrollUp ||  == cview.MouseScrollLeft {
			.Mach.Add1(ss.BackStep, am.A{"amount": 1})

			return , 
		} else if  == cview.MouseScrollDown ||  == cview.MouseScrollRight {
			.Mach.Add1(ss.FwdStep, am.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, am.A{"cursorStep1": int()})

		return , 
	})
}

func ( *Debugger) () {
	.timelineTxs = cview.NewProgressBar()
	.timelineTxs.SetBorder(true)
	.timelineTxs.SetFilledColor(tcell.ColorLightGray)
	// 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, am.A{"amount": 5})

			return , 
		} else if  == cview.MouseScrollDown ||  == cview.MouseScrollRight {
			.Mach.Add1(ss.Fwd, am.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, am.A{
			"cursorTx1":   int(),
			"trimHistory": true,
		})

		return , 
	})
}

func ( *Debugger) () {
	// TODO enum for col indexes

	.addressBar = cview.NewTable()
	.addressBar.SetSelectedStyle(colorActive,
		cview.Styles.PrimitiveBackgroundColor, tcell.AttrBold)
	.addressBar.SetCellSimple(0, 0, "◀ prev mach")
	.addressBar.SetCellSimple(0, 1, "")
	.addressBar.SetCellSimple(0, 2, " next mach▶ ")
	.addressBar.SetCellSimple(0, 3, "")
	// addr
	.addressBar.SetCellSimple(0, 4, "")
	.addressBar.SetCellSimple(0, 5, "")
	.addressBar.SetCellSimple(0, 6, " copy")
	.addressBar.SetCellSimple(0, 7, "")
	.addressBar.SetCellSimple(0, 8, " 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.SetSelectedFunc(func(,  int) {
		switch  {
		case 0: // prev mach
			if .HistoryCursor >= len(.History)-1 {
				return
			}
			.HistoryCursor++
			.hGoToMachAddress(.History[.HistoryCursor], true)

		case 2: // next mach
			if .HistoryCursor <= 0 {
				return
			}
			.HistoryCursor--
			.hGoToMachAddress(.History[.HistoryCursor], true)

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

		case 8: // paste
			if .clip == nil {
				return
			}
			,  := .clip.ReadAll(clipper.RegClipboard)
			if ,  := url.Parse(string());  == nil && .Scheme == "mach" {
				 := &types.MachAddress{MachId: .Host}
				if .Path != "" {
					.TxId = strings.TrimLeft(.Path, "/")
				}
				.hGoToMachAddress(, false)
			}
		}

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

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

	.tagsBar = cview.NewTextView()
	.tagsBar.SetTextColor(tcell.ColorGrey)
	.tagsBar.SetDynamicColors(true)
	.hUpdateAddressBar()
}

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

		.toolbars[] = cview.NewTable()
		.toolbars[].ScrollToBeginning()
		.toolbars[].SetSelectedStyle(colorActive,
			cview.Styles.PrimitiveBackgroundColor, tcell.AttrBold)
		.toolbars[].SetSelectable(true, true)
		.toolbars[].SetBorders(false)

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

			.toolbars[].SetSelectedStyle(colorActive,
				cview.Styles.PrimitiveBackgroundColor, tcell.AttrUnderline)
			.Mach.Add1(ss.ToggleTool, am.A{"ToolName": .toolbarItems[][].id})
			go func() {
				time.Sleep(time.Millisecond * 200)
				.toolbars[].SetSelectedStyle(colorActive,
					cview.Styles.PrimitiveBackgroundColor, tcell.AttrBold)
				.draw(.toolbars[])
			}()
		})
	}

	// TODO light mode button
	// TODO save filters per machine checkbox
	// TODO next error
	.toolbarItems = [][]toolbarItem{
		// row 1
		{
			{id: toolJumpPrev, label: "jump", icon: "◀ "},
			{id: toolPrevStep, label: "step", icon: "<"},
			{id: toolPrev, label: "tx", icon: "◁ "},
			{id: toolNext, label: "tx", icon: "▷ "},
			{id: toolNextStep, label: "step", icon: ">"},
			{id: toolJumpNext, label: "jump", icon: "▶ "},
			{id: toolPlay, label: "play", active: func() bool {
				return .Mach.Is1(ss.Playing)
			}},
			{id: toolFirst, label: "first", icon: "1"},
			{id: toolLast, label: "last", icon: "N"},
			{id: toolExport, label: "export", active: func() bool {
				return .Mach.Is1(ss.ExportDialog)
			}},
		},

		// row 2
		{
			{id: toolLog, label: "log", active: func() bool {
				return .Opts.Filters.LogLevel > am.LogNothing
			}, activeLabel: func() string {
				// TODO make Opts threadsafe
				return strconv.Itoa(int(.Opts.Filters.LogLevel))
			}},
			{id: toolFilterCanceledTx, label: "canceled", active: func() bool {
				return .Mach.Not1(ss.FilterCanceledTx)
			}},
			{id: toolFilterQueuedTx, label: "queued", active: func() bool {
				return .Mach.Not1(ss.FilterQueuedTx)
			}},
			{id: 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: toolFilterEmptyTx, label: "empty", active: func() bool {
				return .Mach.Not1(ss.FilterEmptyTx)
			}},
			{id: toolFilterHealth, label: "health", active: func() bool {
				return .Mach.Not1(ss.FilterHealth)
			}},
			{id: toolFilterOutGroup, label: "group", active: func() bool {
				return .Mach.Is1(ss.FilterOutGroup)
			}},
			{id: toolFilterChecks, label: "checks", active: func() bool {
				return .Mach.Not1(ss.FilterChecks)
			}},
			{id: ToolLogTimestamps, label: "times", active: func() bool {
				return .Mach.Not1(ss.LogTimestamps)
			}},
			{id: ToolFilterTraces, label: "traces", active: func() bool {
				return .Mach.Not1(ss.FilterTraces)
			}},
			// 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: toolHelp, label: "[yellow::b]help[-]", active: func() bool {
				return .Mach.Is1(ss.HelpDialog)
			}},
			{id: toolTail, label: "[::b]tail[-::-]", active: func() bool {
				return .Mach.Is1(ss.TailMode)
			}},
			{id: toolReader, label: "reader", active: func() bool {
				return .Mach.Is1(ss.LogReaderVisible)
			}},
			{
				id:    toolExpand,
				label: "expand", active: func() bool {
					 := .treeRoot.GetChildren()
					return len() > 0 && [0].IsExpanded()
				},
			},
			{id: toolTimelines, label: "timelines", active: func() bool {
				return .Opts.Timelines > 0
			}, activeLabel: func() string {
				// TODO make Opts threadsafe
				return strconv.Itoa(.Opts.Timelines)
			}},
			// TODO make it an anchor for file://...svg
			{id: toolDiagrams, label: "diagrams", active: func() bool {
				return .Opts.OutputDiagrams > 0
			}, activeLabel: func() string {
				// TODO make Opts threadsafe
				return strconv.Itoa(.Opts.OutputDiagrams)
			}},
			{id: toolRain, label: "rain", active: func() bool {
				return .Mach.Is1(ss.MatrixRain)
			}},
			{id: toolMatrix, label: "matrix", active: func() bool {
				return .Mach.Any1(ss.MatrixView, ss.TreeMatrixView)
			}},
		},
	}

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

		if  == "Save" &&  != "" {
			.GetButton(0).SetLabel("Saving...")
			.Draw(.App.GetScreen())
			.hExportData(, )
			.Mach.Remove1(ss.ExportDialog, nil)
			.GetButton(0).SetLabel("Save")
		} else if  == "Cancel" {
			.Mach.Remove1(ss.ExportDialog, nil)
		}
	})

	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(colorHighlight)
	.SetScrollBarColor(colorHighlight2)
	.SetTitle(" Legend ")
	.SetDynamicColors(true)
	.SetPadding(1, 1, 1, 1)
	.SetText(fmt.Sprintf(dedent.Dedent(strings.Trim(`
		[::b]### [::u]client list legend[::-]
		[::b]T:123[-]        total network time
	
		[::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
	`, "\n ")), colorActive, colorActive2, colorInactive))

	// 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)

	 := 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(colorHighlight)
	.helpDialogRight.SetScrollBarColor(colorHighlight2)
	.helpDialogRight.SetTitle(" Keystrokes ")
	.helpDialogRight.SetDynamicColors(true)
	.helpDialogRight.SetPadding(1, 1, 1, 1)

	.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[::-]           struct / last tx
		[::b]alt s[::-]              export data
		[::b]backspace[::-]          remove machine
		[::b]esc[::-]                focus mach list
		[::b]ctrl q[::-]             quit
		[::b]?[::-]                  show help
	
		[::b]### [::u]toolbar legend[::-]
		TODO
	
		[::b]### [::u]machine list legend[::-]
		T:123              total received machine time
		[%s]client-id[-]          connected
		[grey]client-id[-]          disconnected
		[red]client-id[-]          current error
		[orangered]client-id[-]          recent error
		[::u]client-id[::-]          selected machine
		|123               transitions till now
		|123+              more transitions left
		S|                 Start active
		R|                 Ready active
	
		[::b]### [::u]about am-dbg[::-]
		%-15s    version
		%-15s    server DBG addr
		%-15s    server HTTP addr
		%-15s    mem usage
	`, "\n ")), colorActive, .Opts.Version, .Opts.AddrRpc,
		.Opts.AddrHttp, 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(colorHighlight)

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

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

	.LayoutRoot = 
	.hUpdateBorderColor()
}

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

	.mainGrid.Clear()
	.mainGrid.SetRows(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)

		.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)

		.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(.statusBar, , 0, 1, len(), 0, 0, false)

	.hUpdateFocusable()
	// TODO UpdateFocus?
}

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.Ctx().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.Add1(ss.NarrowLayout, nil)
	} else if !.Opts.ViewNarrow {
		// remove if not forced
		.Mach.Remove1(ss.NarrowLayout, nil)
	}
}

func ( *Debugger) () {
	 := .Opts.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 .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 .helpDialog:
		fallthrough
	case .helpDialog.Box:
		 = .helpDialog.Box
		 = ss.DialogFocused

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

	return , 
}