package debugger

import (
	
	
	
	
	
	
	

	
	

	

	
	amhelp 
	am 
	
	ss 
)

// ///// ///// /////

// ///// HANDLERS (LOG)

// ///// ///// /////

func ( *Debugger) ( *am.Event) bool {
	// TODO typed args
	,  := .Args["logRebuildEnd"].(int)
	return  && .C != nil
}

func ( *Debugger) ( *am.Event) {
	// TODO typed args
	// TODO handle logRebuildEnd by observing ClientMsg state clock, dont req
	 := .Args["logRebuildEnd"].(int)
	 := .C.CursorTx1
	 := .Opts.Filters.LogLevel
	 := .Mach.NewStateCtx(ss.BuildingLog)
	var  string
	// TODO compare memorized maxVisible (support resizing)
	, , ,  := .log.GetRect()

	// skip when within the range
	// TODO optimize: count lines from log per level and same params
	 := float64( * 1)
	if .lastResize == .logLastResize && .C.logRenderedLevel ==  &&
		.logRenderedClient == .C.MsgStruct.ID &&
		.C.logRenderedFilters.Equal(.filtersFromStates()) &&
		.C.logRenderedTimestamps == .Mach.Is1(ss.LogTimestamps) &&
		math.Abs(float64(.C.logRenderedCursor1)-float64()) <  {

		// d.Mach.Log("skipping...")
		.Mach.EvAdd1(, ss.LogBuilt, nil)
		return
	}

	// d.Mach.Log("building... at %d for %d", d.lastResize, maxVisible)
	// collect TODO config for benchmarks
	.logRebuildEnd = 
	.logLastResize = .lastResize
	// TODO traverse back and forth separately and collect log lines, skip empty
	//  lines, limit only visible lines
	 := max(0, -1-*4)
	 := min(, +*5)
	for  := ;  <  && .Err() == nil; ++ {
		,  := .hGetLogEntryTxt()
		if  == "" {
			continue
		}
		if  >  && ! {
			 = "\n" + 
		}

		 += 
	}

	// unblock
	go func() {
		if .Err() != nil {
			return // expired
		}

		// cview is safe
		.log.Clear()
		,  := .log.Write([]byte())
		// d.Mach.Log("writting %d", len(buf))
		// d.Mach.Log(buf)
		if .Err() != nil {
			return // expired
		}
		if  != nil {
			.Mach.EvAddErr(, , nil)
			return
		}

		// TODO handle in LogBuiltState
		.Mach.Eval("BuildingLog", func() {
			.C.logRenderedCursor1 = 
			.C.logRenderedLevel = 
			.logRenderedClient = .C.MsgStruct.ID
			.C.logRenderedFilters = .filtersFromStates()
			.C.logRenderedTimestamps = .Mach.Is1(ss.LogTimestamps)
			.logAppends = 0
		}, )

		.Mach.EvAdd1(, ss.LogBuilt, nil)
	}()
}

func ( *Debugger) ( *am.Event) {
	.handleLogScroll()
}

func ( *Debugger) ( *am.Event) {
	// accept and no-op when in progress
	if .Mach.Is1(ss.UpdatingLog) {
		return
	}

	// unblock
	go func() {
		.Mach.EvRemove1(, ss.UpdateLogScheduled, nil)
		// start updating
		.Mach.EvAdd1(, ss.UpdatingLog, nil)
	}()
}

// TODO enter which checks that came from UpdateLogScheduled

// UpdatingLogState decorates the rendered log, and rebuilds when needed.
func ( *Debugger) ( *am.Event) {
	 := .Mach.NewStateCtx(ss.UpdatingLog)
	// rebuild if needed
	.Mach.EvAdd1(, ss.BuildingLog, am.A{"logRebuildEnd": len(.C.MsgTxs)})

	// unblock
	go func() {
		 := amhelp.Add1Async(, .Mach, ss.LogBuilt, ss.BuildingLog, am.A{
			"logRebuildEnd": len(.C.MsgTxs),
		})
		if am.Canceled ==  {
			return
		}

		.Mach.EvAdd1(, ss.LogUpdated, nil)
	}()
}

func ( *Debugger) ( *am.Event) {
	 := .hCurrentTx()
	 := .C

	// go again?
	defer func() {
		if .Mach.Is1(ss.UpdateLogScheduled) {
			.Mach.EvRemove1(, ss.UpdateLogScheduled, nil)
		}
	}()

	.hUpdateLogReader()

	if .MsgStruct != nil {
		 := " Log:" + .Opts.Filters.LogLevel.String() + " "
		if  != nil && .CursorTx1 != 0 {
			 := strconv.Itoa(int(.MsgTxsParsed[.CursorTx1-1].TimeSum))
			 += "[gray]([-]t" +  + "[gray])[-] "
		}
		.log.SetTitle()
	}

	// highlight the next tx if scrolling by steps
	 := .Mach.Is1(ss.TimelineStepsScrolled)
	if  {
		 = .hNextTx()
	}
	if  == nil {
		.log.Highlight("")
		if  {
			.log.ScrollToEnd()
		} else {
			.log.ScrollToBeginning()
		}

		return
	}

	// highlight this tx or the prev if empty
	 := .hPrevTx()
	if len(.LogEntries) == 0 &&  != nil {
		for  := .C.CursorTx1 - 1;  > 0; -- {
			if len(.LogEntries) > 0 {
				 = 
				break
			}
			 = .C.MsgTxs[-1]
		}

		.log.Highlight(.ID)
	} else {
		.log.Highlight(.ID)
	}

	// scroll, but only if not manually scrolled
	if .Mach.Not1(ss.LogUserScrolled) {
		.log.ScrollToHighlight()
	}
}

// ///// ///// /////

// ///// METHODS (LOG)

// ///// ///// /////

func ( *Debugger) () {
	if .Mach.Is1(ss.LogUserScrolled) {
		// TODO restore scroll
		return
	}

	// row-scroll only TODO not working
	.log.ScrollToHighlight()
	,  := .log.GetScrollOffset()
	.log.ScrollTo(, 0)
}

func ( *Debugger) ( *Client,  *telemetry.DbgMsgTx,  int) {
	 := make([]*am.LogEntry, 0)

	 := .MsgTxs[]
	 := .MsgTxsParsed[]
	var  []*types.LogReaderEntryPtr
	if  > 0 {
		// copy from previous msg
		 = slices.Clone(.MsgTxsParsed[-1].ReaderEntries)
	}

	// synthetic log for queued, canceled, and empty
	// TODO full synth log imitating LogChanges
	if .IsQueued || !.Accepted || .TimeDiff == 0 {

		 := .MsgStruct.StatesIndex
		 := &am.LogEntry{Level: am.LogChanges}

		// state list
		switch .Type {
		case am.MutationAdd:
			.Text = "+" + strings.Join(.CalledStateNames(), " +")
		case am.MutationRemove:
			.Text = "-" + strings.Join(.CalledStateNames(), " -")
		case am.MutationSet:
			// TODO both + and -
		}

		// result prefix
		switch {
		case .IsQueued:
			.Text = "[queue] " + .Text
		case !.Accepted:
			.Text = "[cance] " + .Text
		case .TimeDiff == 0:
			.Text = "[empty] " + .Text
		}

		// log args
		.Text += am.MutationFormatArgs(.Args)

		// append and set TODO keep synth log somewhere else than Export
		.LogEntries = []*am.LogEntry{}
	}

	// pre-tx log entries
	for ,  := range .PreLogEntries {
		 = .parseMsgReader(, , , )
		if  := .hParseMsgLogEntry(, , );  != nil {
			 = append(, )
		}
	}

	// tx log entries
	for ,  := range .LogEntries {
		 = .parseMsgReader(, , , )
		if  := .hParseMsgLogEntry(, , );  != nil {
			 = append(, )
		}
	}

	// store the parsed log
	.LogMsgs = append(.LogMsgs, )
	.MsgTxsParsed[].ReaderEntries = 
}

func ( *Debugger) (
	 *Client,  *telemetry.DbgMsgTx,  *am.LogEntry,
) *am.LogEntry {
	 := .Level

	 := fmtLogEntry(.Text, .CalledStateNames(.MsgStruct.StatesIndex),
		.MsgStruct.States)

	return &am.LogEntry{Level: , Text: }
}

func ( *Debugger) ( int) error {
	if .hIsTxSkipped(.C, ) {
		return nil
	}

	,  := .hGetLogEntryTxt()
	if  == "" {
		return nil
	}

	if ! {
		 = "\n" + 
	}

	,  := .log.Write([]byte())
	if  != nil {
		return 
	}

	// scroll, but only if not manually scrolled
	if .Mach.Not1(ss.LogUserScrolled) {
		.log.ScrollToHighlight()
	}

	// rebuild if needed
	.logAppends++
	if .logAppends > 100 {
		.Mach.Add1(ss.BuildingLog, am.A{"logRebuildEnd": })
		.logAppends = 0
	}

	return nil
}

// hGetLogEntryTxt prepares a log entry for UI rendering
// index: 1-based
func ( *Debugger) ( int) ( string,  bool) {
	 = true

	if  < 0 ||  >= len(.C.MsgTxs) ||  >= len(.C.MsgTxsParsed) ||
		 >= len(.C.LogMsgs) {

		.Mach.AddErr(fmt.Errorf("invalid log index %d", ), nil)
		return "", true
	}

	 := .C
	 := ""
	 := .MsgTxs[]
	 := .MsgTxsParsed[]
	 := .LogMsgs[]

	// confirm visibility
	if .hIsTxSkipped(, ) {
		return "", true
	}

	for ,  := range  {
		 := .Text
		 := .Level
		if  == "" ||  > .Opts.Filters.LogLevel {
			continue
		}

		// stack traces removal
		 := strings.HasPrefix(, `[yellow][error[]`)
		 := strings.HasPrefix(, `[yellow][breakpoint[]`)
		if ( || ) && strings.Contains(, "\n") &&
			.Mach.Is1(ss.FilterTraces) {

			 += strings.Split(, "\n")[0] + "\n"
			continue
		}

		 += 
	}

	if  != "" {
		 = false

		if .Opts.Filters.LogLevel == am.LogExternal {
			 = strings.ReplaceAll(, "[yellow][extern[][white] [darkgrey]", "")
		}

		// prefix if showing canceled or queued (gutter)
		if (.Mach.Not1(ss.FilterCanceledTx) || .Mach.Not1(ss.FilterQueuedTx)) &&
			.Opts.Filters.LogLevel >= am.LogChanges {

			 := ""
			switch {
			case .IsQueued:
				 = "[-:yellow] [-:-]"
			case !.Accepted:
				 = "[-:red] [-:-]"
			case .TimeDiff == 0:
				 = "[-:grey] [-:-]"
			default:
				 = "[-:green] [-:-]"
			}

			// prefix each line
			 = strings.TrimRight(, "\n")
			 =  + strings.Join(strings.Split(, "\n"), "\n"+) + "\n"

			// executed dot TODO add gutter to cview textview, then enable
			// if tx.IsQueued {
			//
			// 	var executed *telemetry.DbgMsgTx
			//
			// 	// look into the future TODO links to the wrong one
			// 	for iii := index; iii < len(c.MsgTxs); iii++ {
			// 		check := c.MsgTxs[iii]
			//
			// 		if check.IsQueued {
			// 			continue
			// 		}
			//
			// 		if check.QueueTick == tx.MutQueueTick ||
			// 			(check.MutQueueToken > 0 && check.MutQueueToken ==
			// 			tx.MutQueueToken) {
			//
			// 			executed = check
			// 			break
			// 		}
			// 	}
			//
			// 	if executed == nil {
			// 		before, after, _ := strings.Cut(ret, " ")
			// 		ret = before + "." + after
			// 	}
			// }
		}

		if .Mach.Not1(ss.LogTimestamps) &&  > 0 {
			 := .Time
			 := .MsgTxs[-1]
			if .Time.Second() != .Second() ||
				.Sub(*.Time) > time.Second {

				// grouping labels (per second)
				 += `[grey]` + .Format(timeFormat) + "[-]\n"
			}
		}

		 = strings.TrimRight(, "\n")
	}

	// create a highlight region (even for empty txs)
	// TODO should always be in the beginning to not H scroll
	 := .ID
	 = `["` +  + `"]` +  + `[""]`

	return , 
}

var logPrefixState = regexp.MustCompile(
	`^\[yellow\]\[(state|queue|cance|empty)\[\]\[white\] .+\)\n$`)

var logPrefixExtern = regexp.MustCompile(
	`^\[yellow\]\[exter.+\n$`)

var (
	filenamePattern = regexp.MustCompile(`/[a-z_]+\.go:\d+ \+(?i)`)
	methodPattern   = regexp.MustCompile(`/[^/]+?$`)
)

// TODO split
func fmtLogEntry(
	 string,  []string,  am.Schema,
) string {
	// TODO catch panics

	if  == "" {
		return 
	}

	 := "[][white]"

	 := ""
	// format each line
	for ,  := range strings.Split(, "\n") {
		// color 1st brackets in the 1st line only
		if  == 0 {
			 = strings.Replace(strings.Replace(,
				"]", , 1),
				"[", "[yellow][", 1)
			 := strings.Index(, ) + len()
			,  := [:], [:]
			// escape the rest
			 +=  + cview.Escape() + "\n"
		} else {
			 += cview.Escape() + "\n"
		}
	}

	// highlight log args
	 = logPrefixState.ReplaceAllStringFunc(, func( string) string {
		 := strings.Split(strings.TrimRight(, ")\n"), "(")
		 := ""
		for ,  := range strings.Split([1], " ") {
			 := strings.Split(, "=")
			if len() == 1 {
				 += [0] + " "
			} else {
				 += "[grey]" + [0] + "=[" + colorInactive.String() + "]" + [1] +
					"[:] "
			}
		}

		return [0] +  + "\n"
	})

	// fade out externs
	 = logPrefixExtern.ReplaceAllStringFunc(, func( string) string {
		, ,  := strings.Cut(, " ")

		return  + " [darkgrey]" + 
	})

	// highlight state names (in the msg body)
	 := strings.Index(, )
	 := [0 : +len()]

	// style state names, start from the longest ones
	// TODO compile as regexp and limit to words only
	 := maps.Keys()
	slices.Sort()
	slices.Reverse()
	for ,  := range  {
		// start after the prefix
		 := [+len():]

		// underline called state names
		if slices.Contains(, ) {
			 = strings.ReplaceAll(, " "+, " [::bu]"++"[::-]")
			 = strings.ReplaceAll(, "+"+, "+[::bu]"++"[::-]")
			 = strings.ReplaceAll(, "-"+, "-[darkgrey::u]"++"[::-]")
			 = strings.ReplaceAll(, ","+, ",[::bu]"++"[::-]")
			 =  + strings.ReplaceAll(, "("+, "([::b]"++"[::-]")
		} else {
			// style all state names
			 = strings.ReplaceAll(, " "+, " [::b]"++"[::-]")
			 = strings.ReplaceAll(, "+"+, "+[::b]"++"[::-]")
			 = strings.ReplaceAll(, "-"+, "-[darkgrey]"++"[::-]")
			 = strings.ReplaceAll(, ","+, ",[::b]"++"[::-]")
			 =  + strings.ReplaceAll(, "("+, "([::b]"++"[::-]")
		}
	}

	 = strings.Trim(, " \n	")

	// highlight stack traces
	 := strings.HasPrefix(, `[yellow][error[]`)
	 := strings.HasPrefix(, `[yellow][breakpoint[]`)
	if ( || ) && strings.Contains(, "\n") {

		// highlight
		 := strings.Split(, "\n")
		 := [0:1]
		 := false
		for ,  := range  {
			if  == 0 ||  {
				 = false
				continue
			}
			// filter out machine lines
			if strings.Contains(, "machine.(*Machine).handlerLoop(") {
				 = [0:max(0, len()-4)]
				 = true
				continue
			}

			// method line
			if %2 == 1 {
				 = append(, "[grey]"+
					methodPattern.ReplaceAllStringFunc(,
						func( string) string {
							return "[white]" +  + "[grey]"
						}))
			} else {
				// file line
				 = append(, "[grey]"+
					filenamePattern.ReplaceAllStringFunc(,
						func( string) string {
							 := strings.Split(, " ")
							 := "[white]" + [0]
							if len() > 1 {
								 += " [grey]" + [1]
							}

							return 
						}))
			}
		}

		 = strings.Join(, "\n")
	}

	return  + "\n"
}

// ///// ///// /////

// ///// LOG READER

// ///// ///// /////

type logReaderTreeRef struct {
	// refs
	stateNames am.S
	// TODO embed MachAddress, support queue ticks
	machId   string
	txId     string
	machTime uint64

	// position
	entry *types.LogReaderEntryPtr
	addr  *types.MachAddress

	isQueueRoot bool
}

var removeStyleBracketsRe = regexp.MustCompile(`\[[^\]]*\]`)

func ( *Debugger) () *cview.TreeView {
	 := cview.NewTreeNode("Extracted")
	.SetIndent(0)
	.SetReference(&logReaderTreeRef{})

	 := cview.NewTreeView()
	.SetRoot()
	.SetCurrentNode()
	.SetSelectedBackgroundColor(colorHighlight2)
	.SetSelectedTextColor(colorDefault)
	.SetHighlightColor(colorHighlight)
	.SetTopLevel(1)

	// focus change
	.SetChangedFunc(func( *cview.TreeNode) {
		,  := .GetReference().(*logReaderTreeRef)
		if ! ||  == nil {
			return
		}

		.Mach.Eval("initLogReader", func() {
			if .C != nil {
				// TODO needed? works?
				.C.SelectedReaderEntry = .entry
			}
		}, nil)

		// state name
		if len(.stateNames) < 1 {
			.Mach.Remove1(ss.StateNameSelected, nil)
		} else {
			.Mach.Add1(ss.StateNameSelected, am.A{
				"state": .stateNames[0],
			})
		}
	})

	// click / enter
	.SetSelectedFunc(func( *cview.TreeNode) {
		// TODO support extMachTime
		,  := .GetReference().(*logReaderTreeRef)
		if ! ||  == nil {
			return
		}

		// expand and memorize
		.SetExpanded(!.IsExpanded())
		 := .GetParent() == .GetRoot()
		if  || .isQueueRoot {
			 := strings.Split(.GetText(), " ")[0]
			 = removeStyleBracketsRe.ReplaceAllString(, "")
			.readerExpanded[] = .IsExpanded()
		}

		// mach URL
		 := .addr
		if  == nil {
			 = &types.MachAddress{
				MachId:   .machId,
				TxId:     .txId,
				MachTime: .machTime,
			}
		}

		// click effect
		 := .Mach.Tick(ss.UpdateLogReader)
		 := .GetText()
		.SetText("[" + colorActive.String() + "::u]" +
			removeStyleBracketsRe.ReplaceAllString(, ""))
		.draw(.logReader)

		go func() {
			time.Sleep(time.Millisecond * 200)
			.hGoToMachAddress(, false)

			// restore in case no redir
			time.Sleep(time.Millisecond * 50)
			if  != .Mach.Tick(ss.UpdateLogReader) {
				return // expired
			}
			.SetText()
			.draw(.logReader)
		}()
	})

	return 
}

func ( *Debugger) ( *am.Event) {
	// TODO! SPLIT this 700 LoC func
	// TODO migrate to a state handler
	if .Mach.Not1(ss.LogReaderVisible) {
		return
	}

	// TODO move to UpdateLogReaderState
	.Mach.EvAdd1(, ss.UpdateLogReader, nil)
	.Mach.EvRemove1(, ss.UpdateLogReader, nil)

	 := .logReader.GetRoot()
	.ClearChildren()

	 := .C
	if  == nil || .CursorTx1 < 1 {
		return
	}

	 := .MsgTxs[.CursorTx1-1]
	 := .MsgTxsParsed[.CursorTx1-1]
	 := .MsgStruct.StatesIndex

	var (
		// parents
		       *cview.TreeNode
		      *cview.TreeNode
		   *cview.TreeNode
		  *cview.TreeNode
		  *cview.TreeNode
		 *cview.TreeNode
		    *cview.TreeNode
		   *cview.TreeNode
		  *cview.TreeNode
		      *cview.TreeNode
		    *cview.TreeNode
		     *cview.TreeNode
		     *cview.TreeNode
		  *cview.TreeNode
	)

	 := .C.SelectedState

	// parsed log entries
	for ,  := range .ReaderEntries {
		,  := .LogReader[.TxId]
		var  *cview.TreeNode
		// gc
		if ! || .EntryIdx >= len() {
			 = cview.NewTreeNode("err:GCed entry")
			.SetIndent(1)
		} else {

			 := [.EntryIdx]
			 := &logReaderTreeRef{
				stateNames: .IndexesToStates(.States),
				machId:     .Mach,
				entry:      ,
			}

			// create nodes and parents
			switch .Kind {

			case types.LogReaderCtx:
				if  == nil {
					 = cview.NewTreeNode("StateCtx")
				}
				 := amhelp.IndexesToStates(, .States)
				 = cview.NewTreeNode(.P.Sprintf("[::b]%s[::-] [grey]t%d[-]",
					utils.J(), .CreatedAt))
				.AddChild()
				if slices.Contains(, ) {
					.SetHighlighted(true)
				}

			case types.LogReaderWhen:
				if  == nil {
					 = cview.NewTreeNode("When")
				}
				 := amhelp.IndexesToStates(, .States)
				 = cview.NewTreeNode(.P.Sprintf("[::b]%s[::-] [grey]t%d[-]",
					utils.J(), .CreatedAt))
				.AddChild()
				if slices.Contains(, ) {
					.SetHighlighted(true)
				}

			case types.LogReaderWhenNot:
				if  == nil {
					 = cview.NewTreeNode("WhenNot")
				}
				 := amhelp.IndexesToStates(, .States)
				 = cview.NewTreeNode(.P.Sprintf("[::b]%s[::-] [grey]t%d[-]",
					utils.J(), .CreatedAt))
				.AddChild()
				if slices.Contains(, ) {
					.SetHighlighted(true)
				}

			case types.LogReaderWhenTime:
				if  == nil {
					 = cview.NewTreeNode("WhenTime")
				}
				 := ""
				 := false
				for ,  := range .States {
					 += fmt.Sprintf("[::b]%s[::-]:%d ", [],
						.Ticks[])
					if [] ==  {
						 = true
					}
				}
				 = cview.NewTreeNode(.P.Sprintf("%s[grey]t%d[-]", ,
					.CreatedAt))
				.AddChild()
				if  {
					.SetHighlighted(true)
				}

			case types.LogReaderWhenArgs:
				if  == nil {
					 = cview.NewTreeNode("WhenArgs")
				}
				 := amhelp.IndexesToStates(, .States)
				 = cview.NewTreeNode(.P.Sprintf("[::b]%s[::-] [grey]t%d[-]",
					utils.J(), .CreatedAt))
				// TODO node with key = val for each arg
				 := cview.NewTreeNode(.P.Sprintf("%s", .Args))
				.SetIndent(1)
				.SetReference(&logReaderTreeRef{entry: })
				.AddChild()
				.AddChild()
				if slices.Contains(, ) {
					.SetHighlighted(true)
				}

			case types.LogReaderWhenQueue:
				if  == nil {
					 = cview.NewTreeNode("WhenQueue")
				}
				 = cview.NewTreeNode(.P.Sprintf("%v", .QueueTick))
				.AddChild()

			case types.LogReaderPipeIn:
				 := amhelp.IndexesToStates(, .States)

				// state name redirs to the moment of piping
				.machId = .Id
				.machTime = .CreatedAt

				// find the piped state parent or create a new one
				if  == nil {
					 = cview.NewTreeNode("Pipe-in")
				} else {
					for ,  := range .GetChildren() {
						if .GetText() == [0] {
							 = 
							break
						}
					}
				}

				// convert entry.createdAt to human time
				 := .TxByMachTime(.CreatedAt)
				 := *.Tx().Time

				if  == nil {
					 = cview.NewTreeNode([0])
					.SetBold(true)
					.AddChild()
				}
				 := cview.NewTreeNode(.P.Sprintf("%-6s | [grey]t%d[-] %s",
					capitalizeFirst(.Pipe.String()), .CreatedAt, .Mach))
				.SetIndent(1)
				.SetReference(&logReaderTreeRef{
					stateNames: .IndexesToStates(.States),
					entry:      ,
					machId:     .Mach,
					addr: &types.MachAddress{
						MachId:    .Mach,
						HumanTime: ,
					},
				})
				.AddChild()
				if slices.Contains(, ) {
					.SetHighlighted(true)
					.SetHighlighted(true)
				}

			case types.LogReaderPipeOut:
				 := amhelp.IndexesToStates(, .States)

				// state name redirs to the moment of piping
				.machId = .Id
				.machTime = .CreatedAt

				// find the piped state parent or create a new one
				if  == nil {
					 = cview.NewTreeNode("Pipe-out")
				} else {
					for ,  := range .GetChildren() {
						if .GetText() == [0] {
							 = 
							break
						}
					}
				}

				// parent
				if  == nil {
					 = cview.NewTreeNode([0])
					.SetBold(true)
					.AddChild()
				}

				// convert entry.createdAt to human time
				 := .TxByMachTime(.CreatedAt)
				 := *.Tx().Time

				// pipe-out node
				 := cview.NewTreeNode(.P.Sprintf("%-6s | [grey]t%d[-] %s",
					capitalizeFirst(.Pipe.String()), .CreatedAt, .Mach))
				.SetIndent(1)
				.SetReference(&logReaderTreeRef{
					stateNames: .IndexesToStates(.States),
					entry:      ,
					machId:     .Mach,
					addr: &types.MachAddress{
						MachId:    .Mach,
						HumanTime: ,
					},
				})
				.AddChild()
				if slices.Contains(, ) {
					.SetHighlighted(true)
					.SetHighlighted(true)
				}

			default:
				continue
			}

			.SetIndent(1)
			.SetSelectable(true)
			.SetReference()

			 := .SelectedReaderEntry
			if  != nil && .TxId == .TxId && .EntryIdx == .EntryIdx {
				.logReader.SetCurrentNode()
			}
		}
	}

	// event source
	 = cview.NewTreeNode("Source")
	 := false
	for ,  := range .LogEntries {
		if !strings.HasPrefix(.Text, "[source] ") {
			continue
		}

		 = true
		 := strings.Split(.Text[len("[source] "):], "/")
		,  := strconv.ParseUint([2], 10, 64)

		// source machine
		 := cview.NewTreeNode("self")
		.SetIndent(1)
		if [0] != .Id {
			 = cview.NewTreeNode(.P.Sprintf("%s [grey]t%v[-]", [0],
				[2]))
			.SetReference(&logReaderTreeRef{
				machId:   [0],
				txId:     [1],
				machTime: ,
			})
		}
		.AddChild()

		// source tx
		if ,  := .hGetClientTx([0], [1]);  != nil {
			 := .CalledStateNames(.MsgStruct.StatesIndex)
			 := capitalizeFirst(.Type.String()) + " [::b]" +
				utils.J()
			 := cview.NewTreeNode()
			.SetIndent(1)
			.SetReference(&logReaderTreeRef{
				stateNames: ,
				machId:     [0],
				txId:       [1],
				machTime:   ,
			})
			.AddChild()

			// highlight
			if slices.Contains(, ) {
				if  != nil {
					.SetHighlighted(true)
				}
				.SetHighlighted(true)
			}
		}

		// tags (only for external machines)
		if  := .hGetClient([0]);  != nil &&
			[0] != .Id {

			if  := .MsgStruct.Tags; len() > 0 {
				 := cview.NewTreeNode("[grey]#" + strings.Join(, " #"))
				.SetIndent(1)
				.AddChild()
			}
		}
	}

	if ! {
		 := cview.NewTreeNode("mach unknown")
		.SetIndent(1)
		.AddChild()
		 = cview.NewTreeNode("tx unknown")
		.SetIndent(1)
		.AddChild()
	}

	 = cview.NewTreeNode("Queue")
	{
		 := cview.NewTreeNode(.P.Sprintf("current     [::b]q%v",
			.QueueTick))
		.SetIndent(1)
		.AddChild()

		// queued tx
		if .IsQueued {
			var  *telemetry.DbgMsgTx

			// DEBUG
			// // look into the future TODO links to the wrong one
			// for iii := c.CursorTx1; iii < len(c.MsgTxs); iii++ {
			// 	check := c.MsgTxs[iii]
			// 	txCalled := tx.CalledStateNames(statesIndex)
			// 	checkCalled := check.CalledStateNames(statesIndex)
			// 	if !check.IsQueued && am.StatesEqual(txCalled, checkCalled) {
			// 		d.Mach.Log("exec tx.MQT: %d, check.QT: %d",
			// 			tx.MutQueueTick, check.QueueTick)
			// 		d.Mach.Log("exec tx.Called: %s, check.Called: %s",
			// 			txCalled,
			// 			checkCalled)
			// 	}
			// }

			// look into the future TODO links to the wrong one
			for  := .CursorTx1;  < len(.MsgTxs); ++ {
				 := .MsgTxs[]

				if .IsQueued {
					continue
				}

				if .QueueTick == .MutQueueTick ||
					(.MutQueueToken > 0 && .MutQueueToken == .MutQueueToken) {

					 = 
					break
				}
			}
			 := "???"
			var  *logReaderTreeRef
			if  != nil {
				 = .P.Sprintf("t%v", .TimeSum())
				 = &logReaderTreeRef{
					stateNames: .CalledStateNames(),
					machId:     .Id,
					txId:       .ID,
				}
			}

			 := cview.NewTreeNode("executed at [::b]" + )
			.SetReference()
			.SetIndent(1)
			.AddChild()
			 := cview.NewTreeNode(fmt.Sprintf("queued   at [::b]t%d",
				.TimeSum()))
			.SetIndent(1)
			.AddChild()
			 := "..."
			if .MutQueueTick > 0 {
				 = .P.Sprintf("q%v", .MutQueueTick)
			}
			 := cview.NewTreeNode("queued  for [::b]" + )
			.SetIndent(1)
			.SetReference()
			.AddChild()

			// executed tx
		} else {
			var  *telemetry.DbgMsgTx

			// look into the past
			for  := .CursorTx1 - 2;  >= 0; -- {
				 := .MsgTxs[]

				if !.IsQueued {
					continue
				}

				// DEBUG
				// txCalled := tx.CalledStateNames(statesIndex)
				// checkCalled := check.CalledStateNames(statesIndex)
				// if check.IsQueued && am.StatesEqual(txCalled, checkCalled) {
				// 	d.Mach.Log("queued tx.QT: %d, check.MQT: %d",
				// 		tx.QueueTick, check.MutQueueTick)
				// 	d.Mach.Log("queued tx.Called: %s, check.Called: %s",
				// 		txCalled,
				// 		checkCalled)
				// }

				// TODO assert state names?
				if .MutQueueTick == .QueueTick ||
					(.MutQueueToken > 0 && .MutQueueToken == .MutQueueToken) {

					 = 
					break
				}
			}
			 := "???"
			var  *logReaderTreeRef
			if  != nil {
				 = .P.Sprintf("t%v", .TimeSum())
				 = &logReaderTreeRef{
					stateNames: .CalledStateNames(),
					machId:     .Id,
					txId:       .ID,
				}
			}

			 := cview.NewTreeNode("executed at [::b]...")
			.SetIndent(1)
			.AddChild()
			 := cview.NewTreeNode("queued   at [::b]" + )
			.SetReference()
			.SetIndent(1)
			.AddChild()
			 := cview.NewTreeNode("queued  for [::b]...")
			.SetIndent(1)
			.AddChild()
		}

		// list of queued txs
		 := .P.Sprintf("length [::b]%v[::-]", .Queue)
		if .IsQueued {
			 += " (inferred)"
		}
		 := cview.NewTreeNode()
		.SetIndent(1)
		.SetReference(&logReaderTreeRef{
			isQueueRoot: true,
		})
		if ,  := .readerExpanded["length"];  {
			.SetExpanded()
		}
		.AddChild()

		// list the queue

		// list of executed tokens
		 := []uint64{.MutQueueToken}
		// queued txs may be missing, depending on the start of the log
		 := []*cview.TreeNode{}
		// toShow2 is the priority queue
		 := []*cview.TreeNode{}
		 := 0
		 := 0

		// collect the reported queue amount
		for  := max(0, .CursorTx1-1);  >= 0; -- {
			 := .Tx()
			if  == nil {
				.Mach.AddErr(fmt.Errorf("tx missing: %d", ), nil)
				break
			}

			// end when queue collected
			if  >= .Queue {
				break
			}

			// check all executed, memorize tokens
			if !.IsQueued && .MutQueueToken > 0 {
				 = append(, .MutQueueToken)
			}

			if !.IsQueued {
				continue
			}

			// skip executed prepended (besides self)
			if .ID != .ID && .MutQueueToken > 0 &&
				slices.Contains(, .MutQueueToken) {

				continue
			}

			// skip checks TODO
			// if past.IsCheck && d.Mach.Is1(ss.FilterChecks) {
			// 	continue
			// }

			// skip self via queue tick
			if .QueueTick == .MutQueueTick {
				continue
			}

			// skip executed by QueueTick
			if .MutQueueTick > 0 && .MutQueueTick <= .QueueTick {
				continue
			}

			// hard limit
			++
			if  > 5000 {
				break
			}

			 := .MutString()

			 := [0:1]
			 := [1:]
			if .MutQueueTick > 0 {
				 =  + " " + .P.Sprintf("[gray]q%v", .MutQueueTick)
			}
			switch  {
			case "+":
				 = " +"
			case "-":
				 = "- "
			}
			 := cview.NewTreeNode("[::b]" +  + "[::-]" + )
			.SetIndent(2)

			// link
			.SetReference(&logReaderTreeRef{
				machId:     .Id,
				txId:       .ID,
				stateNames: .CalledStateNames(),
			})

			// priority queue
			if .MutQueueToken > 0 {
				 = slices.Concat([]*cview.TreeNode{}, )
			} else {
				 = append(, )
			}

			 = len() + len()

			// limit
			if  > 50 {
				break
			}
		}

		slices.Reverse()
		slices.Reverse()
		for ,  := range  {
			.AddChild()
		}
		for ,  := range  {
			.AddChild()
		}
		// overflow
		if len()+len() > 50 ||  > 5000 {
			// TODO counter of remaining ones?
			 := cview.NewTreeNode("...")
			.SetIndent(2)
			.AddChild()
		}
	}

	// forked events
	 = cview.NewTreeNode("Forked")
	.SetExpanded(false)
	for ,  := range .Forks {

		 := .hGetClient(.MachId)
		 := .TxIndex(.TxId)
		 := false

		 := .P.Sprintf("%s#%s", .MachId, .TxId)
		// internal tx
		if .MachId == .Id {
			 = .P.Sprintf("#%s", .TxId)
		}

		if  != -1 {
			 := .Tx()
			 := .CalledStateNames(
				.MsgStruct.StatesIndex)
			 = capitalizeFirst(.Type.String()) + " [::b]" +
				utils.J()
			if slices.Contains(, ) {
				 = true
			}
		}

		 := cview.NewTreeNode()
		.SetIndent(1)
		.SetReference(&logReaderTreeRef{
			machId: .MachId,
			txId:   .TxId,
		})
		.AddChild()

		// highlight
		if  {
			.SetHighlighted(true)
		}

		// details (only for external machines)
		if .MachId != .Id &&  != nil {
			// label
			 := .P.Sprintf("%s#%s", .MachId,
				.TxId)
			if  != -1 {
				 := .Tx()
				 = .P.Sprintf("%s [grey]t%v[-]", .MachId,
					.TimeSum())
			}

			 := cview.NewTreeNode()
			.SetIndent(1)
			.SetReference(&logReaderTreeRef{
				machId: .MachId,
				txId:   .TxId,
			})
			.AddChild()
			if  {
				.SetHighlighted(true)
			}

			// ID and tags (only for external machines)

			if  := .MsgStruct.Tags; len() > 0 {
				 := cview.NewTreeNode("[grey]#" + strings.Join(, " #"))
				.SetIndent(1)
				.AddChild()
				if  {
					.SetHighlighted(true)
				}
			}
		}
	}

	// sibling events
	 = cview.NewTreeNode("Siblings")
	.SetExpanded(false)
	// get source tx
	for ,  := range .LogEntries {
		if !strings.HasPrefix(.Text, "[source] ") {
			continue
		}

		 := strings.Split(.Text[len("[source] "):], "/")

		// source tx info
		,  := .hGetClientTx([0], [1])
		if  == nil {
			break
		}
		 := .TxParsed(.TxIndex(.ID))

		for ,  := range .Forks {
			if .MachId == .Id && .TxId == .ID {
				continue
			}

			 := .hGetClient(.MachId)
			 := .TxIndex(.TxId)
			 := false

			 := .P.Sprintf("%s#%s", .MachId, .TxId)
			// internal tx
			if .MachId == .Id {
				 = .P.Sprintf("#%s", .TxId)
			}

			if  != -1 {
				 := .Tx()
				 := .CalledStateNames(
					.MsgStruct.StatesIndex)
				 = capitalizeFirst(.Type.String()) + " [::b]" +
					utils.J()
				if slices.Contains(, ) {
					 = true
				}
			}

			 := cview.NewTreeNode()
			.SetIndent(1)
			.SetReference(&logReaderTreeRef{
				machId: .MachId,
				txId:   .TxId,
			})
			.AddChild()

			// highlight
			if  {
				.SetHighlighted(true)
			}

			// details (only for external machines)
			if .MachId != .Id &&  != nil {
				// label
				 := .P.Sprintf("%s#%s", .MachId,
					.TxId)
				if  != -1 {
					 := .Tx()
					 = .P.Sprintf("%s [grey]t%v[-]", .MachId,
						.TimeSum())
				}

				 := cview.NewTreeNode()
				.SetIndent(1)
				.SetReference(&logReaderTreeRef{
					machId: .MachId,
					txId:   .TxId,
				})
				.AddChild()
				if  {
					.SetHighlighted(true)
				}

				// ID and tags (only for external machines)

				if  := .MsgStruct.Tags; len() > 0 {
					 := cview.NewTreeNode("[grey]#" + strings.Join(, " #"))
					.SetIndent(1)
					.AddChild()
					if  {
						.SetHighlighted(true)
					}
				}
			}
		}
	}

	// executed handlers for the curr tx
	// TODO read from tx.Steps
	 = cview.NewTreeNode("Executed")
	.SetExpanded(false)
	for ,  := range .LogEntries {
		if !strings.HasPrefix(.Text, "[handler:") {
			continue
		}
		// get name from after first "]"
		 := strings.Index(.Text, "]")
		if  == -1 {
			continue
		}
		 := .Text[len("[handler:"):]
		 := .Text[+2:]
		 := cview.NewTreeNode(.P.Sprintf("%s[grey]:%s[-]", ,
			))
		.SetIndent(1)
		.AddChild()
		if  != "" && strings.HasPrefix(, ) {
			.SetHighlighted(true)
		}
	}

	// args for the curr tx
	 = cview.NewTreeNode("Arguments")
	.SetExpanded(false)
	{
		 := []string{}
		for ,  := range .Args {
			 = append(, .P.Sprintf(
				"[::b]%s[::-] [%s]%s", , colorInactive, ))
		}
		slices.Sort()
		for ,  := range  {
			 := cview.NewTreeNode()
			.SetIndent(1)
			.AddChild()
		}
	}

	// TODO extract
	 := func( *cview.TreeNode) {
		 := .GetText()
		if ,  := .readerExpanded[];  {
			.SetExpanded()
		}

		 := len(.GetChildren())
		if  > 0 &&  !=  &&  !=  {
			.SetText(fmt.Sprintf("[%s]%s[-] (%d)", colorActive,
				.GetText(), ))
		} else {
			.SetText(fmt.Sprintf("[%s]%s[-]", colorActive, .GetText()))
		}
		.SetIndent(0)
		.SetReference(&logReaderTreeRef{})

		// append and collapse
		.AddChild()
		if .C.ReaderCollapsed {
			.SetExpanded(false)
		}
	}

	// append parents
	if  != nil {
		()
		// need bc the root is hidden
		.logReader.SetCurrentNode()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}
	if  != nil {
		()
	}

	// expand all nodes
	go .App.QueueUpdateDraw(func() {
		.CollapseAll()
		.Expand()
	})
}

func ( *Debugger) (
	 *Client,  *am.LogEntry,  []*types.LogReaderEntryPtr,
	 *telemetry.DbgMsgTx,
) []*types.LogReaderEntryPtr {
	// TODO get data from SemLogger
	// TODO add errs to machine (not log)

	// NEW

	if strings.HasPrefix(.Text, "[source] ") {

		 := strings.Split(.Text[len("[source] "):], "/")

		if  := .hGetClient([0]);  != nil {
			 := .TxIndex([1])
			if  != -1 {
				 := .TxParsed()
				.Forks = append(.Forks, types.MachAddress{
					MachId: .Id,
					TxId:   .ID,
				})
			}
		}
	} else if strings.HasPrefix(.Text, "[when:new] ") {

		// [when:new] Foo,Bar
		 := strings.Split(.Text[len("[when:new] "):], " ")

		 := .AddReaderEntry(.ID, &types.LogReaderEntry{
			Kind:      types.LogReaderWhen,
			States:    .StatesToIndexes(),
			CreatedAt: .MTimeSum,
		})
		 = append(, &types.LogReaderEntryPtr{
			TxId:     .ID,
			EntryIdx: ,
		})

	} else if strings.HasPrefix(.Text, "[whenNot:new] ") {

		// [when:new] Foo,Bar
		 := strings.Split(.Text[len("[whenNot:new] "):], " ")

		 := .AddReaderEntry(.ID, &types.LogReaderEntry{
			Kind:      types.LogReaderWhenNot,
			States:    .StatesToIndexes(),
			CreatedAt: .MTimeSum,
		})
		 = append(, &types.LogReaderEntryPtr{
			TxId:     .ID,
			EntryIdx: ,
		})

	} else if strings.HasPrefix(.Text, "[whenTime:new] ") {

		// [whenTime:new] Foo,Bar [1 2]
		 := strings.Split(.Text[len("[whenTime:new] "):], " ")
		 := strings.Split([0], ",")
		 := strings.Split([1], ",")

		 := .AddReaderEntry(.ID, &types.LogReaderEntry{
			Kind:      types.LogReaderWhenTime,
			States:    .StatesToIndexes(),
			Ticks:     tickStrToTime(.Mach, ),
			CreatedAt: .MTimeSum,
		})
		 = append(, &types.LogReaderEntryPtr{
			TxId:     .ID,
			EntryIdx: ,
		})

	} else if strings.HasPrefix(.Text, "[ctx:new] ") {

		// [ctx:new] Foo
		 := .Text[len("[ctx:new] "):]

		 := .AddReaderEntry(.ID, &types.LogReaderEntry{
			Kind:      types.LogReaderCtx,
			States:    .StatesToIndexes(am.S{}),
			CreatedAt: .MTimeSum,
		})
		 = append(, &types.LogReaderEntryPtr{
			TxId:     .ID,
			EntryIdx: ,
		})

	} else if strings.HasPrefix(.Text, "[whenArgs:new] ") {

		// [whenArgs:new] Foo (arg1,arg2)
		 := strings.Split(.Text[len("[whenArgs:new] "):], " ")
		 := strings.Trim([1], "()")

		 := .AddReaderEntry(.ID, &types.LogReaderEntry{
			Kind:      types.LogReaderWhenArgs,
			States:    .StatesToIndexes(am.S{[0]}),
			CreatedAt: .MTimeSum,
			Args:      ,
		})
		 = append(, &types.LogReaderEntryPtr{
			TxId:     .ID,
			EntryIdx: ,
		})

	} else if strings.HasPrefix(.Text, "[pipe-in:add] ") ||
		strings.HasPrefix(.Text, "[pipe-in:remove] ") ||
		strings.HasPrefix(.Text, "[pipe-out:add] ") ||
		strings.HasPrefix(.Text, "[pipe-out:remove] ") {

		 := strings.HasPrefix(.Text, "[pipe-in:add] ") ||
			strings.HasPrefix(.Text, "[pipe-out:add] ")
		 := strings.HasPrefix(.Text, "[pipe-out")

		// [add:pipe] Foo to Mach2
		var  []string
		if  &&  {
			 = strings.Split(.Text[len("[pipe-out:add] "):], " to ")
		} else if ! &&  {
			 = strings.Split(.Text[len("[pipe-in:add] "):], " from ")
		} else if  && ! {
			 = strings.Split(.Text[len("[pipe-out:remove] "):], " to ")
		} else if ! && ! {
			 = strings.Split(.Text[len("[pipe-in:remove] "):], " from ")
		}
		 := types.LogReaderPipeIn
		if  {
			 = types.LogReaderPipeOut
		}
		 := am.MutationRemove
		if  {
			 = am.MutationAdd
		}

		 := strings.Split(strings.Trim([0], "[]"), " ")
		 := .AddReaderEntry(.ID, &types.LogReaderEntry{
			Kind:      ,
			Pipe:      ,
			States:    .StatesToIndexes(),
			CreatedAt: .MTimeSum,
			Mach:      [1],
		})
		 = append(, &types.LogReaderEntryPtr{
			TxId:     .ID,
			EntryIdx: ,
		})

		// remove GCed machines
	} else if strings.HasPrefix(.Text, "[pipe:gc] ") {
		 := strings.Split(.Text, " ")
		 := [1]
		var  []*types.LogReaderEntryPtr

		for ,  := range  {
			 := .GetReaderEntry(.TxId, .EntryIdx)
			 := .Kind == types.LogReaderPipeIn ||
				.Kind == types.LogReaderPipeOut
			if  && .Mach ==  {
				continue
			}

			 = append(, )
		}
		 = 

	} else if strings.HasPrefix(.Text, "[whenQueue:new] ") {

		// [whenQueue:new] 17
		 := strings.Split(.Text[len("[whenQueue:new] "):], " ")
		,  := strconv.Atoi([0])
		if  != nil {
			.Mach.Log("err: match missing: " + .Text)
			return 
		}

		 := .AddReaderEntry(.ID, &types.LogReaderEntry{
			Kind:      types.LogReaderWhenQueue,
			CreatedAt: .MTimeSum,
			QueueTick: ,
		})
		 = append(, &types.LogReaderEntryPtr{
			TxId:     .ID,
			EntryIdx: ,
		})

	} else

	//

	// MATCH (delete the oldest one)

	//

	if strings.HasPrefix(.Text, "[when:match] ") {

		// [when:match] Foo,Bar
		 := strings.Split(.Text[len("[when:match] "):], ",")
		 := false

		for ,  := range  {
			 := .GetReaderEntry(.TxId, .EntryIdx)

			// matched, delete and mark
			if  != nil && .Kind == types.LogReaderWhen &&
				am.StatesEqual(, .IndexesToStates(.States)) {

				 = slices.Delete(, , +1)
				.ClosedAt = *.Time
				 = true
				break
			}
		}

		if ! {
			.Mach.Log("err: match missing: " + .Text)
		}

	} else if strings.HasPrefix(.Text, "[whenNot:match] ") {

		// [whenNot:match] Foo,Bar
		 := strings.Split(.Text[len("[whenNot:match] "):], ",")
		 := false

		for ,  := range  {
			 := .GetReaderEntry(.TxId, .EntryIdx)

			// matched, delete and mark
			if  != nil && .Kind == types.LogReaderWhenNot &&
				am.StatesEqual(, .IndexesToStates(.States)) {

				 = slices.Delete(, , +1)
				.ClosedAt = *.Time
				 = true
				break
			}
		}

		if ! {
			.Mach.Log("err: match missing: " + .Text)
		}

	} else if strings.HasPrefix(.Text, "[whenTime:match] ") {

		// [whenTime:match] Foo,Bar [1 2]
		 := strings.Split(.Text[len("[whenTime:match] "):], " ")
		 := strings.Split([0], ",")
		 := strings.Split(strings.Trim([1], "[]"), " ")
		 := tickStrToTime(.Mach, )
		 := false

		for ,  := range  {
			 := .GetReaderEntry(.TxId, .EntryIdx)

			// matched, delete and mark
			if  != nil && .Kind == types.LogReaderWhenTime &&
				am.StatesEqual(, .IndexesToStates(.States)) &&
				slices.Equal(, .Ticks) {

				 = slices.Delete(, , +1)
				.ClosedAt = *.Time
				 = true
				break
			}
		}

		if ! {
			.Mach.Log("err: match missing: " + .Text)
		}

	} else if strings.HasPrefix(.Text, "[ctx:match] ") {

		// [ctx:match] Foo
		 := .Text[len("[ctx:match] "):]
		 := false

		for ,  := range  {
			 := .GetReaderEntry(.TxId, .EntryIdx)

			// matched, delete and mark
			if  != nil && .Kind == types.LogReaderCtx &&
				am.StatesEqual(am.S{}, .IndexesToStates(.States)) {

				 = slices.Delete(, , +1)
				.ClosedAt = *.Time
				 = true
				break
			}
		}

		if ! {
			.Mach.Log("err: match missing: " + .Text)
		}
	} else if strings.HasPrefix(.Text, "[whenArgs:match] ") {

		// [whenArgs:match] Foo (arg1,arg2)
		 := strings.Split(.Text[len("[whenArgs:match] "):], " ")
		 := strings.Trim([1], "()")
		 := false

		for ,  := range  {
			 := .GetReaderEntry(.TxId, .EntryIdx)

			// matched, delete and mark
			// TODO match arg names
			if  != nil && .Kind == types.LogReaderWhenArgs &&
				am.StatesEqual(am.S{[0]}, .IndexesToStates(.States)) &&
				.Args ==  {

				 = slices.Delete(, , +1)
				.ClosedAt = *.Time
				 = true
				break
			}
		}

		if ! {
			.Mach.Log("err: match missing: " + .Text)
		}
	} else if strings.HasPrefix(.Text, "[whenQueue:match] ") {

		// [whenQueue:match] 17
		 := strings.Split(.Text, " ")
		,  := strconv.Atoi([1])
		if  != nil {
			.Mach.Log("err: match missing: " + .Text)
			return 
		}
		 := false

		for ,  := range  {
			 := .GetReaderEntry(.TxId, .EntryIdx)

			// matched, delete and mark
			if  != nil && .Kind == types.LogReaderWhenQueue &&
				.QueueTick ==  {

				 = slices.Delete(, , +1)
				.ClosedAt = *.Time
				 = true
				break
			}
		}

		if ! {
			.Mach.Log("err: match missing: " + .Text)
		}
	}

	// TODO detached pipe handlers

	return 
}