package debugger

import (
	
	
	
	
	
	

	
	

	amhelp 
	am 
	arpc 
	ssam 
	
	
	
	amrelay 
)

// buildClientList builds the clientList with the list of clients.
// selectedIndex: index of the selected item, -1 for the current one.
// TODO ReqBuildClientListState
func ( *Debugger) ( int) {
	if .Mach.Not1(ss.ClientListVisible) {
		return
	}
	if !.buildCLScheduled.CompareAndSwap(false, true) {
		// debounce non-forced updates
		return
	}

	go func() {
		time.Sleep(sidebarUpdateDebounce)
		 := func() {
			.hBuildClientList()
			// draw after a debounce
			.draw(.clientList)
		}
		// TODO avoid eval
		go .Mach.Eval("hBuildClientList", , nil)
	}()
}

// TODO BuildClientListState
func ( *Debugger) ( int) {
	defer .buildCLScheduled.Store(false)

	if .Mach.Not1(ss.ClientListVisible) {
		return
	}

	// prev state
	 := ""
	var  *cview.ListItem
	if  == -1 {
		 = .clientList.GetCurrentItem()
	} else if  > -1 {
		 = .clientList.GetItem()
	}
	if  != nil {
		 = .GetReference().(*sidebarRef).name
	}

	// re-gen all
	.clientList.Clear()
	var  []string
	for ,  := range .Clients {
		 = append(, .Id)
	}

	// sort a-z, with value numbers
	humanSort()

	 := 0
	// TODO REWRITE to states
	for ,  := range  {
		if .hClientHasParent() {
			continue
		}
		// skip disconns TODO dont skip children
		 := .Clients[]
		if !.Connected.Load() && .Mach.Is1(ss.FilterDisconn) {
			continue
		}
		// skip rpc & relay TODO dont skip children
		if .Mach.Is1(ss.FilterRpcMachs) && machIsRpc(.MsgStruct) {
			continue
		}

		// create list item
		 := cview.NewListItem()
		.SetReference(&sidebarRef{name: , lvl: 0})
		.clientList.AddItem()

		if  == "" && .C != nil && .C.Id ==  {
			// pre-select the current machine
			.clientList.SetCurrentItem()
		} else if  ==  {
			// select the previously selected one
			.clientList.SetCurrentItem()
		}

		 = .hClientListChild(, , , , 1)

		++
	}

	var  uint64
	for ,  := range .Clients {
		 += .MTimeSum
	}

	.clientList.SetTitle(.P.Sprintf(
		" Machines:%d T:%v ", len(.Clients), ,
	))

	.hUpdateClientList()
}

func ( *Debugger) () {
	if .buildCLScheduled.Load() || .Mach.Not1(ss.ClientListVisible) {
		return
	}

	// debounce
	if !.updateCLScheduled.CompareAndSwap(false, true) {
		// debounce non-forced updates
		return
	}

	go func() {
		// TODO amhelp.Wait
		time.Sleep(sidebarUpdateDebounce)
		// canceled
		if !.updateCLScheduled.CompareAndSwap(true, false) {
			// debounce non-forced updates
			return
		}

		.Mach.Eval("updateClientList", func() {
			.hUpdateClientList()
			.drawClientList()
		}, nil)
	}()
}

func ( *Debugger) () {
	if .LayoutRoot == nil || .Mach.Not1(ss.ClientListVisible) {
		return
	}
	if ,  := .LayoutRoot.GetFrontPanel();  == "main" {
		.draw(.clientList)
	}
}

func ( *Debugger) () {
	defer .buildCLScheduled.Store(false)
	if .Mach.Not1(ss.ClientListVisible) {
		return
	}
	if .Mach.IsDisposed() || .Mach.Is1(ss.SelectingClient) ||
		.Mach.Is1(ss.HelpDialog) {
		return
	}

	, , ,  := .clientList.GetRect()
	 :=  - 13
	if  < 5 {
		 = 15
	}

	// count
	 := 0
	for ,  := range .clientList.GetItems() {
		 := .GetReference().(*sidebarRef)
		 := len(.name) + .lvl
		if  >  {
			 = 
		}
	}
	if  >  {
		 = 
	}

	 := ""

	// update
	for ,  := range .clientList.GetItems() {
		 := .GetReference().(*sidebarRef)
		 := .Clients[.name]
		if  == nil {
			// TODO happens with --clean-on-connect?
			// d.Mach.AddErr(fmt.Errorf("client %s doesnt exist", ref.name), nil)
			continue
		}

		 := ""
		 := " "
		 := .hClientHasParent(.Id)
		if  {
			 = " "
		}

		 := strings.Repeat("-", .lvl) +  + .name
		if len() >  {
			 = [:-2] + ".."
		}

		 := int(math.Max(0, float64(+1-len())))
		 :=  +
			strings.Repeat(" ", ) + 
		 := .hGetClientListLabel(, , )
		.SetMainText()

		// txt file
		if .params.OutputClients {
			if ! {
				 += "\n"
			}
			 += string(cview.StripTags([]byte(), true, false)) + "\n"
			for ,  := range .MsgStruct.Tags {
				 += strings.Repeat(" ", .lvl) +  +
					"  #" +  + "\n"
			}
		}
	}

	if len(.Clients) > 0 {
		var  uint64
		for ,  := range .Clients {
			 += .MTimeSum
		}

		.clientList.SetTitle(.P.Sprintf(
			" Machines:%d T:%v ", len(.Clients), ,
		))
	} else {
		.clientList.SetTitle(" Machines ")
	}

	// save to a file TODO skipped when file list not rendered
	if .params.OutputClients {
		_, _ = .clientListFile.Seek(0, 0)
		_ = .clientListFile.Truncate(0)
		_, _ = .clientListFile.Write([]byte())
	}
}

// TODO move
type sidebarRef struct {
	name string
	lvl  int
}

// TODO move
func machIsRpc( *dbg.DbgMsgStruct) bool {
	return .HasTag(arpc.TagRpcClient, "") ||
		.HasTag(arpc.TagRpcServer, "") ||
		.HasTag(arpc.TagRpcMux, "") ||
		.HasTag(amrelay.TagRelay, "")
}

func ( *Debugger) (
	 []string,  string,  int,  string,
	 int,
) int {
	for ,  := range  {
		 := .Clients[]

		// skip parents
		if !.hClientHasParent() || .MsgStruct.Parent !=  {
			continue
		}
		// skip disconns
		if !.Connected.Load() && .Mach.Is1(ss.FilterDisconn) {
			continue
		}
		// skip rpc & relay
		if .Mach.Is1(ss.FilterRpcMachs) && machIsRpc(.MsgStruct) {
			continue
		}

		// create list item
		 := cview.NewListItem()
		.SetReference(&sidebarRef{name: , lvl: })
		.clientList.AddItem()

		if  == "" && .C != nil && .C.Id ==  {
			// pre-select the current machine
			.clientList.SetCurrentItem()
		} else if  ==  {
			// select the previously selected one
			.clientList.SetCurrentItem()
		}

		 = 1 + .(, , , , +1)
	}
	return 
}

func ( *Debugger) ( string) bool {
	,  := .Clients[]
	if ! || .MsgStruct.Parent == "" {
		return false
	}
	_,  = .Clients[.MsgStruct.Parent]

	return 
}

func ( *Debugger) (
	 string,  *Client,  int,
) string {
	 := .clientList.GetCurrentItemIndex() == 
	 := .Mach.Is1(ss.ClientListFocused) &&
		!.Mach.WillBeAny(states.DebuggerGroups.Focused)

	var  int
	var  *dbg.DbgMsgTx
	// current tx of the selected client
	 := .hCurrentTx()
	if  != nil {
		 := .lastScrolledTxTime
		if .IsZero() {
			 = *.Time
		}
		 = .TxAtHTime()
		if  != -1 {
			 = .MsgTxs[]
		}
	}

	// Ready, Start, Error states indicators
	var  string
	 := false
	if  != nil {
		 := .MsgStruct.StatesIndex
		 := slices.Index(, ssam.BasicStates.Ready)
		 := slices.Index(, ssam.BasicStates.Start)
		 := slices.Index(, am.StateException)
		 =  != -1 && am.IsActiveTick(.Clocks[])
		if  != -1 && am.IsActiveTick(.Clocks[]) {
			 = "R"
		} else if  != -1 && am.IsActiveTick(.Clocks[]) {
			 = "S"
		}
		// push to the front
		if  {
			 = "E" + 
		}
	}
	if  == "" {
		 = " "
	}

	 := .P.Sprintf("%s %s|%d", , , +1)
	if +1 < len(.MsgTxs) {
		 += "+"
	}

	// mark selected
	if .C != nil && .Id == .C.Id {
		 = "[::bu]" + 
	}

	if !.Connected.Load() {
		if  && ! {
			 = "[" + theme.Grey + "]" + 
		} else if ! {
			 = "[" + theme.Grey + "]" + 
		} else {
			 = "[" + theme.BgPrimary + "]" + 
		}
	} else if  && .Connected.Load() {
		 = "[" + theme.Err + "]" + 
	} else if .HadErrSinceTx(, 100) {
		 = "[" + theme.ErrRecent + "]" + 
	}

	return 
}

func ( *Debugger) () {
	// TODO refac to a tree component
	.clientList = cview.NewList()
	.clientList.SetTitle(" Machines ")
	.clientList.SetBorder(true)
	.clientList.ShowSecondaryText(false)
	.clientList.SetSelectedFocusOnly(true)
	.clientList.SetMainTextColor(tcell.GetColor(theme.Active))
	.clientList.SetSelectedTextColor(tcell.GetColor(theme.White))
	.clientList.SetSelectedBackgroundColor(tcell.GetColor(theme.Highlight2))
	.clientList.SetHighlightFullLine(true)
	.clientList.SetChangedFunc(func( int,  *cview.ListItem) {
		// TODO deadlock from hBuildClientList
		// if d.Mach.EvalRunning() {
		// 	return
		// }
		go .Mach.Eval("clientList.SetChangedFunc", .hUpdateClientList, nil)
	})
	// switch clients and handle history
	.clientList.SetSelectedFunc(func( int,  *cview.ListItem) {
		 := .GetReference().(*sidebarRef)
		 := .name
		 := ""
		if ,  := .Client();  != nil {
			 = .Id
		}
		if  ==  {
			return
		}
		,  := context.WithTimeout(context.Background(), 2*time.Second)
		defer ()

		amhelp.Add1Async(
			, .Mach, ss.ClientSelected,
			ss.SelectingClient, Pass(&A{
				ClientId: ,
			}),
		)
		if .Err() != nil {
			.Mach.Log("timeout when selecting client %s", )
			return
		}
		// TODO do these in ClientSelectedState
		.Mach.Eval("clientList.SetSelectedFunc2", func() {
			.hPrependHistory(&types.MachAddress{MachId: })
			.hUpdateAddressBar()
			.draw(.addressBar)
		}, )
	})
	.clientList.SetSelectedAlwaysVisible(true)
	.clientList.SetScrollBarColor(tcell.GetColor(theme.Highlight2))
}

// TODO should switch in displayed order, but list no rendered in narrow
func ( *Debugger) ( *am.Event,  int) am.Result {
	 := slices.Collect(maps.Keys(.Clients))
	var  int
	if .C != nil {
		 = slices.Index(, .C.Id)
	}
	 := ( + ) % len()
	if  < 0 {
		 = len() - 1
	}

	return .Mach.EvAdd1(, ss.SelectingClient, Pass(&A{
		ClientId: .Clients[[]].Id,
	}))
}