package main

import (
	
	
	
	
	
	
	

	
	amhelp 
	am 
	arpc 
	amss 
	amtele 
	amprom 
	amgen 
)

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 deactivates
	<-.WhenNot1(ss.Bar, nil)

	// async dispose
	.Add1(ss.Disposing, nil)
	<-.When1(ss.Disposed, nil)
	// soft dispose done
	<-.WhenDisposed()
	// fully disposed
}

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

// ///// STATE MACHINE

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

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

	 := &TemplateHandlers{
		DisposedHandlers: &amss.DisposedHandlers{},
	}
	,  := am.NewCommon(, "templ", states.MachTemplateSchema, ss.Names(),
		, nil, &am.Opts{Tags: []string{"tag:val", "tag2"}})
	if  != nil {
		return nil, 
	}
	.Mach = 
	// max 10 concurrent forks in BazState
	.PoolSetLimit(ss.Baz+am.SuffixState, 10)

	// telemetry

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

	.SemLogger().SetLevel(am.LogChanges)
	.SemLogger().SetArgsMapper(LogArgs)
	// connect to am-dbg
	amhelp.MachDebugEnv()
	// start a dedicated aRPC server for the REPL, create an addr file
	arpc.MachReplEnv(, &arpc.ReplOpts{
		AddrDir:  ".",
		Args:     ARpc{},
		ParseRpc: ParseRpc,
	})

	// 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, 
	}

	// TODO history

	return , nil
}

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

// ///// HANDLERS

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

type TemplateHandlers struct {
	*am.ExceptionHandler
	*amss.DisposedHandlers

	Mach *am.Machine
}

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

	// unblock
	.Mach.Fork(, , func() {
		fmt.Println("FooState")

		// nested unblocking goes without [e], which is not valid at this point
		.Mach.Go(, func() {
			fmt.Println("FooState.Go")
		})
	})
}

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)

	// very frequent multi-states should be rate-limited
	.Mach.PoolFork(, , func() {
		// like time.Wait, but with context
		if !amhelp.Wait(, time.Second) {
			_ = AddErrExample(, .Mach, nil, nil)
			return
		}
		fmt.Println("BazState: " + )
		.Mach.Add1(ss.BazDone, 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

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

func init() {
	gob.Register(ARpc{})
}

const APrefix = "template"

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

// ParseRpc parses [am.A] to *ARpc namespaced in [am.A]. Useful for REPLs.
func ( am.A) am.A {
	 := am.A{APrefix: &ARpc{}}
	,  := json.Marshal()
	if  == nil {
		json.Unmarshal(, [APrefix])
	}

	return 
}

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

// ///// ERRORS

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

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

// error mutations

// AddErrExample wraps an error in the ErrJoining sentinel and adds to a
// machine. If no err was provided, the function is no-op.
func (
	 *am.Event,  *am.Machine,  error,  am.A,
) am.Result {
	if  == nil {
		return am.Executed
	}
	 = fmt.Errorf("%w: %w", ErrExample, )

	return .EvAddErrState(, ss.ErrExample, , )
}

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

// ///// TRACER

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

type Tracer struct {
	*am.TracerNoOp
	// 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)
}