package debugger

import (
	
	
	
	
	
	
	
	
	
	
	

	
	

	amgraph 
	amhelp 
	am 
	
	
	amvis 
)

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

// ///// HANDLERS

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

var _ = ss.WebReqDiag

// TODO enter

func ( *Debugger) ( *am.Event) {
	 := am.ParseArgs[A](.Args)
	 := .HttpRequest
	 := .HttpResponseWriter
	 := .DiagType
	 := .DoneChan
	defer close()

	 := .Value
	 := .RequestURI
	switch {

	// diagram viewer
	case  == "/viewer/"+:
		// adjust HTML
		 := string(amvis.HtmlDiagram)
		 = strings.ReplaceAll(, "localhost:6831", .listenAddrHttp)
		 = strings.ReplaceAll(, `const TYPE = "mach";`,
			fmt.Sprintf(`const TYPE = "%s";`, ))
		// TODO <title>am-vis machine diagram</title>

		,  := .Write([]byte())
		.Mach.EvAddErrState(, ss.ErrWeb, , nil)

	// default svg symlink
	case strings.HasPrefix(, "/viewer/"++".svg"):
		 := filepath.Join(.params.OutputDir, "am-vis-"++".svg")
		,  := os.ReadFile()
		// skip errs for state when no state
		if  != "state" && *.selectedState.Load() != "" {
			.Mach.EvAddErrState(, ss.ErrWeb, , nil)
		}
		if  != nil {
			return
		}

		// send
		.Header().Set("Content-Type", "image/svg+xml")
		_,  = .Write()
		.Mach.EvAddErrState(, ss.ErrWeb, , nil)
	}
}

var _ = ss.WebSocketDiag

// TODO enter

func ( *Debugger) ( *am.Event) {
	 := .Mach
	// TODO ctx type per diag type
	,  := context.WithCancel(.NewStateCtx(ss.Start, ))
	 := am.ParseArgs[A](.Args)
	 := .WebSocketConn
	 := .HttpRequest
	 := .DoneChan

	// check params
	if .Params.Load().OutputDiagrams == types.ParamsOutputDiagramsNone {
		.Close(websocket.StatusNormalClosure, "Diagrams disabled")
		()
		return
	}

	// diagram types TODO keep all as states once partial negotiation lands
	var  string
	var  string
	var  chan struct{}
	// TODO support >1 client (use states instead)
	switch .DiagType {

	case types.DiagramTypeMach:
		// readyState = ss.DiagramsMachReady
		 = ss.DiagramsMachRendering
		 = .diagMachUpdate
		go func() {
			.Mach.EvAddErrState(, ss.ErrDiagrams,
				.diagramsMachUpdating(), nil)
		}()

	case types.DiagramTypeGraph:
		 = ss.DiagramsGraphRendering
		 = ss.DiagramsGraphReady

	case types.DiagramTypeState:
		// readyState = ss.DiagramsStatesRendering
		 = ss.DiagramsStatesRendering
		 = .diagStateUpdate
		go func() {
			.Mach.EvAddErrState(, ss.ErrDiagrams,
				.diagramsStateUpdating(), nil)
		}()

	case types.DiagramTypeSteps:
		 = .diagStepsUpdate
		.hDiagramsStepsRendering(, .hCurrentTx(), .hCurrentTxParsed())
	}

	// WS push update loop
	.Go(, func() {
		defer close()

		// chan based updates
		if  != nil {
			.diagramWsUpdateChan(, , , , )
			return
		}

		// state based updates
		.diagramsWsUpdateStates(, , , , )
	})

	// WS read loop with closing
	.Fork(, , func() {
		for {
			// msgType, msg, err := ws.Read(r.Context())
			, ,  := .Read(.Context())
			if  != nil {
				.Log("websocket closed")
				if websocket.CloseStatus() == -1 {
					 = fmt.Errorf("websocket read: %w", )
					.EvAddErrState(, ss.ErrWeb, , nil)
				}

				// close up
				()
				return
			}
		}
	})
}

// GRAPH

var _ = ss.DiagramsGraphRendering

func ( *Debugger) ( *am.Event) bool {
	return len(.Clients) > 0 &&
		.params.OutputDiagrams != types.ParamsOutputDiagramsNone
}

func ( *Debugger) ( *am.Event) {
	 := .Mach.NewStateCtx(ss.DiagramsGraphRendering, )
	.hUpdateGraphHash()

	 := .graphHash
	 := path.Join(.params.OutputDir, "diagrams")
	// TODO optimize
	,  := .graph.Clone()
	if  != nil {
		.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
		return
	}

	.Mach.Fork(, , func() {
		 := fmt.Sprintf("graph-%s", )
		 := path.Join(, )

		// render if missing
		if ,  := os.Stat( + ".svg");  != nil {
			 := amvis.NewRenderer(, .Mach.Log)
			// TODO fix RPC conns not visible
			amvis.PresetMap()
			.OutputFilename = 
			 := .GenDiagrams()
			if .Err() != nil {
				return // expired
			}
			if  != nil {
				.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
				return
			}
		}

		// symlink
		_ = .diagramsLink(, types.DiagramTypeGraph)
		.Mach.EvAdd1(, ss.DiagramsGraphReady, nil)
	})
}

// MACH

var _ = ss.DiagramsMachRendering

func ( *Debugger) ( *am.Event) bool {
	return .params.OutputDiagrams != types.ParamsOutputDiagramsNone &&
		.C != nil
}

func ( *Debugger) ( *am.Event) {
	 := .Mach.NewStateCtx(ss.DiagramsMachRendering, )
	 := .params.OutputDiagrams.Value
	 := .C
	 := fmt.Sprintf("mach-%s-%d-%s", .Id, , .SchemaHash)
	// clone the current graph TODO optimize
	,  := .graph.Clone()
	if  != nil {
		.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
		return
	}

	// state groups
	var  S
	if  := .SelectedGroup;  != "" &&
		.params.OutputDiagGroup == types.ParamsOutDiagGroupSkip {

		 = .MsgSchemaParsed.Groups[]
		 = fmt.Sprintf("mach-%s-%s-%d-%s",
			.Id, types.NormalizeGroupName(), , .SchemaHash)
	}

	// unblock
	.Mach.Fork(, , func() {
		// update or render
		 := .diagramsMachRender(, , .Id, , , )
		if .Err() != nil {
			return // expired
		}
		if  != nil {
			.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
			return
		}

		// link and load DOM
		_ = .diagramsLink(, types.DiagramTypeMach)
		,  := os.Open(.diagramsDiagPath() + ".svg")
		if  != nil {
			.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
			return
		}
		,  := goquery.NewDocumentFromReader()
		if  != nil {
			.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
			return
		}

		.Mach.EvAdd1(, ss.DiagramsMachReady, Pass(&A{
			DiagDom:  ,
			DiagName: ,
		}))
	})
}

var _ = ss.DiagramsMachReady

// TODO enter

func ( *Debugger) ( *am.Event) {
	 := .Mach.NewStateCtx(ss.DiagramsMachReady)
	 := am.ParseArgs[A](.Args)
	.diagMachDom.Store(.DiagDom)
	.diagMachName.Store(&.DiagName)

	.Mach.Go(, func() {
		.Mach.EvAddErrState(, ss.ErrDiagrams,
			.diagramsMachUpdating(), nil)
	})
}

// STATE

var _ = ss.DiagramsStatesRendering

func ( *Debugger) ( *am.Event) bool {
	return .params.OutputDiagrams != types.ParamsOutputDiagramsNone &&
		.C != nil
}

func ( *Debugger) ( *am.Event) {
	 := .Mach.NewStateCtx(ss.DiagramsStatesRendering)
	 := .C
	 := path.Join(.params.OutputDir, "diagrams")
	 := .MsgStruct.StatesIndex
	 := .SelectedGroup
	 := .params.OutputDiagGroup != types.ParamsOutDiagGroupNone
	 := .SchemaHash
	var  S
	if  != "" {
		 = .MsgSchemaParsed.Groups[]
	}
	 := .Id
	 := .MsgStruct.States.Clone()

	// TODO pool state
	.Mach.Go(, func() {
		for ,  := range  {
			if .Err() != nil {
				return // expired
			}
			// skip
			if  == am.StateStart ||  == am.StateReady {
				continue
			}

			 := .diagramsStateDiagName(
				, , , , ,
			)

			// gen
			 := .diagramsStateRender(, , ,
				, , )
			if .Err() != nil {
				return // expired
			}
			if  != nil {
				.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
				return
			}

			// push update (if relevant)
			if *.selectedState.Load() ==  {
				.diagramsStatesPush(, .diagramsDiagPath())
			}
		}

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

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

// ///// SUB-HANDLERS

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

func ( *Debugger) (
	 context.Context,  *dbg.DbgMsgTx,  *types.MsgTxParsed,
) {
	if  == nil {
		return
	}

	 := .C
	 := am.CtxToEv()
	 := .MsgStruct.StatesIndex

	// reset files
	_ = .txFileMd.Truncate(0)
	_ = .diagStepsFileD2.Truncate(0)
	_ = .diagStepsFileD2Svg.Truncate(0)
	_ = .diagStepsFileMermaid.Truncate(0)
	_ = .diagStepsFileMermaidAscii.Truncate(0)

	// TODO move to cursor set
	_, _ = .txFileMd.WriteAt([]byte(.TxString()), 0)

	if .Steps == nil {
		return
	}

	var  am.S
	if .params.OutputDiagGroup != types.ParamsOutDiagGroupNone &&
		.SelectedGroup != "" {

		 = .MsgSchemaParsed.Groups[.SelectedGroup]
	}

	 := amvis.Transition{
		Log:        amhelp.MachToSlog(.Mach),
		Tx:         ,
		TxParsed:   ,
		PrevTx:     .hPrevTx(),
		StateTrace: .hStateTrace(),
		Group:      ,
		// TODO add queue-tx with stack trace
	}

	// unblock
	.Mach.Go(, func() {
		if  := .diagramsStepsRender(, , );  != nil {
			if .Err() != nil {
				return // expired
			}
			.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
		}

		// push update
		select {
		case .diagStepsUpdate <- struct{}{}:
		default:
			// skip
		}
	})
}

func ( *Debugger) (
	 *Client,  *dbg.DbgMsgTx,
) *amvis.Filters {
	//

	 := .MsgStruct.StatesIndex
	 := &amvis.Filters{
		MachId:   .Id,
		Index:    ,
		Selected: am.S{.C.SelectedState},
	}
	if  != nil {
		.Active = .ActiveStates()

		// dim
		if .params.OutputDiagTx != types.ParamsOutDiagTxNone {
			 := .MsgTxsParsed[.CursorTx1-1]
			 := .CalledStatesIdxs
			switch .params.OutputDiagTx {
			case types.ParamsOutDiagTxMutated:
				 = slices.Concat(.StatesAdded, .StatesRemoved)
			case types.ParamsOutDiagTxTouched:
				fallthrough
			case types.ParamsOutDiagTxRelations:
				 = .StatesTouched
			}
			.Highlighted = .IndexesToStates()
		}

		// collect touched rels TODO add to step timeline 1-by-1
		if .params.OutputDiagTx == types.ParamsOutDiagTxRelations {
			for ,  := range .Steps {
				if .Type != am.StepRelation {
					continue
				}

				.HighlightedRels = append(.HighlightedRels,
					[3]string{
						.GetFromState(),
						.GetToState(),
						.RelType.String(),
					})
			}
		}
	}
	if .params.OutputDiagGroup == types.ParamsOutDiagGroupHide &&
		.SelectedGroup != "" {

		.Visible = .MsgSchemaParsed.Groups[.SelectedGroup]
	}

	return 
}

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

// ///// METHODS

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

func ( *Debugger) ( context.Context) error {
	// check params
	if .Params.Load().OutputDiagrams == types.ParamsOutputDiagramsNone {
		return nil
	}

	// skip if no cache to update
	if .diagMachDom.Load() == nil {
		return nil
	}

	// wait for Mach Ready
	select {
	case <-.Done():
		return nil
	case <-.Mach.When1(ss.DiagramsMachReady, ):
	}
	,  := .diagramsMachFilters()
	if .Err() != nil {
		return nil // expired
	}
	if  != nil {
		return 
	}

	// update cache DOM
	 := .diagramsDiagPath(*.diagMachName.Load())
	 = amvis.UpdateCache(, +".svg",
		.diagMachDom.Load(), )
	if .Err() != nil {
		return nil // expired
	}
	if  != nil {
		return 
	}

	// push update
	select {
	case .diagMachUpdate <- struct{}{}:
	default:
		// skip
	}

	return nil
}

// diagramsStateUpdating will link the correct state diagram.
func ( *Debugger) ( context.Context) error {
	// check params
	if .Params.Load().OutputDiagrams == types.ParamsOutputDiagramsNone {
		return nil
	}

	 := *.selectedState.Load()
	 := *.selectedClient.Load()
	 := *.selectedGroup.Load()
	 := *.selectedSchemaHash.Load()

	 := .params.OutputDiagGroup != types.ParamsOutDiagGroupNone
	 := .diagramsStateDiagName(, , ,
		, )

	if  := .diagramsLink(, types.DiagramTypeState);  != nil {
		return 
	}

	// push update if ready
	 := .diagramsDiagPath()
	if ,  := os.Stat( + ".svg");  == nil {
		.diagramsStatesPush(, )
	} else {
		select {
		case .diagStateUpdate <- struct{}{}:
		default:
			// skip
		}
		// TODO push "loading" event
	}

	return nil
}

func ( *Debugger) (
	 context.Context,
) (*amvis.Filters, error) {
	//

	return amhelp.EvalGetter(, "diagramsFilters", 3, .Mach,
		func() (*amvis.Filters, error) {
			 := .hCurrentTx()
			return .hDiagramsFilters(.C, ), nil
		})
}

func ( *Debugger) (
	 context.Context,  chan struct{},  string,
	 *websocket.Conn,  *http.Request,
) {
	//

	 := am.CtxToEv()
	 := .Mach

	for {
		// "loading" indicator
		if .Mach.Is1() {
			// debounce
			time.Sleep(100 * time.Millisecond)
			if .Mach.Is1() {

				// show progress msg
				,  := json.Marshal(types.WsDiagMsg{
					Event: "loading",
				})
				if  != nil {
					.EvAddErrState(, ss.ErrWeb, , nil)
					continue
				}
				 = .Write(.Context(), websocket.MessageText, )
				if  != nil {
					.EvAddErrState(, ss.ErrWeb, , nil)
					// TODO continue?
					return
				}
			}
		}

		// refresh event
		select {
		case <-.Done():
			return

		case <-:
			// msg
			 := ""
			// TODO no eval
			if  := .MachAddr();  != nil {
				 = .MachId
			}
			,  := json.Marshal(types.WsDiagMsg{
				Event: "refresh",
				Id:    ,
			})
			if  != nil {
				.EvAddErrState(, ss.ErrWeb, , nil)
				continue
			}

			// send
			 = .Write(.Context(), websocket.MessageText, )
			if  != nil {
				.EvAddErrState(, ss.ErrWeb, , nil)
				// TODO continue?
				return
			}

			continue
		}
	}
}

func ( *Debugger) (
	 context.Context,  string,  *websocket.Conn,
	 *http.Request,  string,
) {
	//
	 := am.CtxToEv()
	 := .Mach

	var  context.Context
	var  context.CancelFunc
	var  uint64
	for {
		// step ctx - release prev run's wait chans
		if  != nil {
			()
		}
		,  = context.WithCancel()
		defer ()

		// wait if tick served
		 := .Mach.Tick()
		if  ==  {
			// wait for rendering to start
			if  != "" {
				select {
				case <-.Done():
					return
				case <-.When1(, ):
				}
			}
		}
		 = 

		// show progress msg
		,  := json.Marshal(types.WsDiagMsg{
			Event: "loading",
		})
		if  != nil {
			.EvAddErrState(, ss.ErrWeb, , nil)
			continue
		}
		 = .Write(.Context(), websocket.MessageText, )
		if  != nil {
			.EvAddErrState(, ss.ErrWeb, , nil)
			// TODO continue?
			return
		}

		// wait for rendering to end
		if  != "" {
			select {

			case <-.Done():
				return

			case <-.When1(, ):
				// ok
			}
		}

		// msg
		,  = json.Marshal(types.WsDiagMsg{
			Event: "refresh",
			Id:    .MachAddr().MachId,
			Group: *.diagSkipGroup.Load(),
		})
		if  != nil {
			.Log(.Error())
			continue
		}

		// send
		 = .Write(.Context(), websocket.MessageText, )
		if  != nil {
			.EvAddErrState(, ss.ErrWeb, , nil)
			// TODO continue?
			return
		}
	}
}

func ( *Debugger) (
	 context.Context,  *amgraph.Graph,  string,
	 int,  string,  S,
) error {
	 := .diagramsDiagPath()

	// check cache
	if ,  := os.Stat( + ".svg");  == nil {
		return nil
	}

	 := amvis.NewRenderer(, .Mach.Log)
	amvis.PresetSingle()
	// TODO enum
	switch  {
	default:
		return fmt.Errorf("unknown diagram detail level: %d", )

		// single (simple)
	case 1:
		.RenderNestSubmachines = false
		.RenderStart = false
		.RenderInherited = false
		.RenderPipes = false
		.RenderHalfPipes = false
		.RenderHalfConns = false
		.RenderHalfHierarchy = false
		.RenderParentRel = false

		// single (detailed)
	case 2:
		.RenderNestSubmachines = false
		.RenderStart = true
		.RenderInherited = true
		.RenderPipes = false
		.RenderHalfPipes = false
		.RenderHalfConns = false
		.RenderHalfHierarchy = false

		// single (external)
	case 3:
		.RenderNestSubmachines = true
		.RenderStart = true
		.RenderInherited = true
		.RenderPipes = true
	}

	.Mach.Log("rendering graphs lvl %d", )

	// common
	.RenderActive = false
	.RenderMachs = []string{}
	.OutputFilename = 
	if len() > 0 {
		.RenderAllowlist = 
	}

	return .GenDiagrams()
}

func ( *Debugger) (
	 context.Context,  am.Schema,  string,  string,
	 string,  S,
) error {
	//

	// cached?
	 := .diagramsDiagPath()
	,  := os.Stat( + ".svg")
	if  == nil {
		return nil
	}

	// clone the current graph TODO optimize
	 := am.CtxToEv()
	,  := .graph.Clone()
	if  != nil {
		.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
		return nil
	}

	 := .AdjacentStates()
	 := S{}.Add()
	for ,  := range  {
		// skip popular states
		if  == am.StateReady ||  == am.StateStart {
			continue
		}

		 = .Add(.AdjacentStates())
	}
	 = .Unique()

	// group narrow down
	if  != nil {
		 = slices.DeleteFunc(, func( string) bool {
			return !slices.Contains(, )
		})
	}

	// skip empty sets
	if len() == 0 {
		return nil
	}

	// keep blocking
	if .Err() != nil {
		return nil // expired
	}
	// mark rendering as in-progress TODO dedicated state
	// d.Mach.EvRemove1(e, ss.DiagramsScheduled, nil)

	 := amvis.NewRenderer(, .Mach.Log)
	amvis.PresetSingle()
	.RenderNestSubmachines = false
	.RenderStart = false
	.RenderActive = false
	.RenderReady = false
	.RenderInherited = true
	.RenderPipes = true
	.RenderHalfPipes = false
	.RenderHalfConns = false
	.RenderHalfHierarchy = false
	.RenderParentRel = false
	// TODO add pipes
	.RenderZoom = S{[0]}
	.RenderMachTitles = false
	.RenderAllowlist = 
	.RenderMachs = []string{.C.Id}
	.OutputMermaid = false
	.OutputFilename = 
	// TODO test
	// vis.OutputElk = false
	// TODO skip Start, Ready

	return .GenDiagrams()
}

func ( *Debugger) (
	 context.Context,  amvis.Transition,  am.S,
) error {
	//

	 := am.CtxToEv()
	var  error

	// D2
	, ,  := .D2(, )
	if .Err() != nil {
		return .Err()
	}
	 = errors.Join(, )

	// mermaid
	, ,  := .Mermaid()
	if .Err() != nil {
		return .Err()
	}
	 = errors.Join(, )

	// store
	.Mach.Eval("diagramsStepsRender", func() {
		if  == nil {
			,  := .diagStepsFileD2.WriteAt([]byte(), 0)
			if  != nil {
				.Mach.EvAddErr(, , nil)
			} else {
				_, _ = .diagStepsFileD2Svg.WriteAt(, 0)
			}
		}

		if  == nil {
			,  := .diagStepsFileMermaid.WriteAt([]byte(), 0)
			if  != nil {
				.Mach.EvAddErr(, , nil)
			} else {
				_, _ = .diagStepsFileMermaidAscii.WriteAt([]byte(), 0)
			}
		}
	}, )

	return errors.Join(
		,
		.diagramsLink("steps.d2", types.DiagramTypeSteps),
	)
}

func ( *Debugger) (
	 string,  types.DiagramType,
) error {
	 := .Params.Load().OutputDir
	 := .Value

	// symlink am-vis.svg -> ID-LVL-HASH.svg
	 := path.Join(, "am-vis-"++".svg")
	 := path.Join("diagrams", +".svg")
	_ = os.Remove()

	.Mach.Log("diaglink %s -> %s", , )

	return os.Symlink(, )
}

// diagramsDiagPath returns a path to the diagram file WITHOUT the extensions.
func ( *Debugger) ( string) string {
	return path.Join(.Params.Load().OutputDir, "diagrams", )
}

func ( *Debugger) (
	,  string,  string,  string,  bool,
) string {
	//

	 := fmt.Sprintf("state-%s-%s-%s", , , )

	// narrow down to a state group
	if  != "" &&  {
		 = fmt.Sprintf("state-%s-%s-%s-%s",
			, types.NormalizeGroupName(), , )
	}

	return 
}

func ( *Debugger) ( context.Context,  string) {
	// push even with errs
	defer func() {
		select {
		case .diagStateUpdate <- struct{}{}:
		default:
			// skip
		}
	}()
	 := am.CtxToEv()

	// overlay active states, cache DOM
	,  := os.Open( + ".svg")
	if  != nil {
		// file is optional
		return
	}

	var  *goquery.Document
	if *.diagStatePath.Load() ==  {
		 = .diagStateDom.Load()
	} else {
		,  = goquery.NewDocumentFromReader()
		if .Err() != nil {
			return // expired
		}
		if  != nil {
			.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
			return
		}

		.diagStateDom.Store()
		.diagStatePath.Store(&)
	}

	,  := .diagramsMachFilters()
	if .Err() != nil {
		return // expired
	}
	if  != nil {
		.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
		return
	}

	// only active states filter
	 := &amvis.Filters{
		Active: .Active,
		Index:  .Index,
	}

	// update cache DOM
	 = amvis.UpdateCache(, +".svg", , )
	if .Err() != nil {
		return // expired
	}
	if  != nil {
		.Mach.EvAddErrState(, ss.ErrDiagrams, , nil)
		return
	}
}