package main

import (
	
	
	
	
	

	amtele 
	amprom 
	amgen 
	

	
	amhelp 
	am 
	arpc 
)

var ss = states.MachTemplateStates

type S = am.S

func init() {
	// am-dbg is required for debugging, go run it
	// go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
	amhelp.EnableDebugging(true)
}

func main() {
	,  := context.WithCancel(context.Background())
	defer ()

	// init
	,  := NewTemplate(, 0)
	if  != nil {
		panic()
	}

	// add Start and Bar
	.Add(S{ss.Start, ss.Bar}, nil)

	// getting data back via a channel (needs to be buffered)
	 := make(chan []string, 1)
	.Add1(ss.Channel, Pass(&A{
		ReturnCh: ,
	}))
	fmt.Printf("%v", <-)

	 := make(chan struct{})
	go func() {
		// wait for 100 BazDone
		 := .WhenTicks(ss.BazDone, 100, nil)

		for range 100 {
			.Add1(ss.Baz, Pass(&A{
				Addr: "localhost:8090",
			}))
		}
		<-
		close()
	}()

	// wait until Bar goes away
	<-.WhenNot1(ss.Bar, nil)

	// atomic dispose
	.Add1(ss.Disposing, nil)
	<-.When1(ss.Disposed, nil)

	// release resources
	.Dispose()
	<-.WhenDisposed()
}

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

// ///// STATE MACHINE

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

func ( context.Context,  int) (*am.Machine, error) {
	// init
	// use the schema from ./states
	// any struct can be used as handlers

	 := &TemplateHandlers{
		p:    amhelp.Pool(10),
		bazP: amhelp.Pool(1),
	}
	,  := am.NewCommon(, "templ", states.MachTemplateSchema, ss.Names(),
		, nil, &am.Opts{Tags: []string{"tag:val", "tag2"}})
	if  != nil {
		return nil, 
	}
	.Mach = 

	// inject groups and infer parents tree
	.SetGroups(states.MachTemplateGroups, states.MachTemplateStates)

	// telemetry

	.SemLogger().SetLevel(am.LogChanges)
	.SemLogger().SetArgsMapper(LogArgs)
	amhelp.MachDebugEnv()
	// start a dedicated aRPC server for the REPL, create an addr file
	arpc.MachReplEnv()

	// parent-only exporters

	// export metrics to prometheus
	amprom.MachMetricsEnv()

	// grafana dashboard
	 = amgen.MachDashboardEnv()
	if  != nil {
		.AddErr(, nil)
	}

	// open telemetry traces
	 = amtele.MachBindOtelEnv()
	if  != nil {
		.AddErr(, nil)
	}

	// manual tracing

	 := &Tracer{}
	 = .BindTracer()
	if  != nil {
		return nil, 
	}

	return , nil
}

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

// ///// HANDLERS

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

type TemplateHandlers struct {
	*am.ExceptionHandler
	Mach *am.Machine

	// general p for handlers
	p *errgroup.Group
	// multi states should be rate-limitted separately
	bazP *errgroup.Group
}

func ( *TemplateHandlers) ( *am.Event) {
	 := .Mach.NewStateCtx(ss.Bar)

	// unblock
	.p.Go(func() error {
		if .Err() != nil {
			return nil // expired
		}
		fmt.Println("FooState")
		return nil
	})
}

func ( *TemplateHandlers) ( *am.Event) bool {
	// accept de-activation only if Baz happened 10x more
	return .Mach.Tick(ss.Baz) > .Mach.Tick(ss.Bar)*10
}

func ( *TemplateHandlers) ( *am.Event) {
	 := ParseArgs(.Args)
	 := .Addr

	// multi states rely on context of other states
	 := .Mach.NewStateCtx(ss.Start)

	// multi states should be rate-limitted
	.bazP.Go(func() error {
		// like time.Wait, but with context
		if !amhelp.Wait(, time.Second) {
			_ = AddErrExample(, .Mach, nil, nil)
			return nil
		}
		fmt.Println("BazState: " + )
		.Mach.Add1(ss.BazDone, nil)

		return nil
	})
}

func ( *TemplateHandlers) ( *am.Event) {
	// new transition (will probably be canceled)
	.Mach.Remove1(ss.Bar, nil)
}

func ( *TemplateHandlers) ( *am.Event) bool {
	 := ParseArgs(.Args)
	// only buffered channel can pass
	return  != nil && cap(.ReturnCh) > 0
}

func ( *TemplateHandlers) ( *am.Event) {
	// no validation needed
	ParseArgs(.Args).ReturnCh <- []string{"hello", "machines"}
}

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

// ///// ARGS

// ///// ///// /////
// TODO add RPC args example from pkg/node

const APrefix = "am_node"

// A is a struct for node arguments. It's a typesafe alternative to [am.A].
type A struct {
	Id   string `log:"id"`
	Addr string `log:"addr"`

	// non-rpc fields

	ReturnCh chan<- []string
}

// ARpc is a subset of A, that can be passed over RPC.
type ARpc struct {
	Id   string `log:"id"`
	Addr string `log:"addr"`
}

// ParseArgs extracts A from [am.Event.Args][APrefix].
func ( am.A) *A {
	if ,  := [APrefix].(*ARpc);  {
		return amhelp.ArgsToArgs(, &A{})
	} else if ,  := [APrefix].(ARpc);  {
		return amhelp.ArgsToArgs(&, &A{})
	}
	if ,  := [APrefix].(*A);  != nil {
		return 
	}
	return &A{}
}

// Pass prepares [am.A] from A to pass to further mutations.
func ( *A) am.A {
	return am.A{APrefix: }
}

// PassRpc prepares [am.A] from A to pass over RPC.
func ( *A) am.A {
	return am.A{APrefix: amhelp.ArgsToArgs(, &ARpc{})}
}

// LogArgs is an args logger for A.
func ( am.A) map[string]string {
	 := ParseArgs()
	if  == nil {
		return nil
	}

	return amhelp.ArgsToLogMap(, 0)
}

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

// ///// ERRORS

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

var ErrExample = errors.New("error example")

// error mutations

// AddErrExample wraps an error in the ErrJoining sentinel and adds to a
// machine.
func (
	 *am.Event,  *am.Machine,  error,  am.A,
) error {
	if  != nil {
		 = fmt.Errorf("%w: %w", ErrExample, )
	} else {
		 = ErrExample
	}
	.EvAddErrState(, ss.ErrExample, , )

	return 
}

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

// ///// TRACER

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

type Tracer struct {
	*am.NoOpTracer
	// This machine's clock has been updated and needs to be synced.
	dirty atomic.Bool
}

// TransitionEnd sends a message when a transition ends
func ( *Tracer) ( *am.Transition) {
	.dirty.Store(true)
}