package main

import (
	
	
	
	
	
	

	
	
	amhelp 
	am 
	arpc 
	amss 
	amtele 
	amprom 
	amgen 
)

var ss = states.MachTemplateStates

type S = am.S

func init() {
	// load dotenv
	// err := godotenv.Load("./examples/mach_template/example.env")
	 := godotenv.Load("example.env")
	if  != nil {
		panic()
	}

	// manual logging
	// amhelp.SetEnvLogLevel(am.LogOps)
	// os.Setenv(amhelp.EnvAmLogPrint, "2")

	// 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, am.Pass(&AChannel{
		ReturnCh: ,
	}))
	fmt.Printf("%v\n", <-)

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

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

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

	// async dispose
	.Add1(ss.Disposing, nil)
	<-.When1(ss.Disposed, nil)
	// soft dispose done
	<-.WhenDisposed()
	fmt.Printf("done")
	// 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(amhelp.LogArgsMapper)
	// connect to am-dbg
	amhelp.MachDebugEnv()
	// start a dedicated aRPC server for the REPL, create an addr file
	, _ = arpc.MachReplEnv(, &arpc.ReplOpts{
		AddrDir: ".",
		Args:    ArgsRpc,
	})
	if  != nil {
		return nil, 
	}

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

var _ = ss.Foo

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

var _ = ss.Bar

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

var _ = ss.Baz

func ( *TemplateHandlers) ( *am.Event) {
	 := am.ParseArgs[ABaz](.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: " + )
		// traced mutation
		.Mach.EvAdd1(, ss.BazDone, nil)
	})
}

var _ = ss.BazDone

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

var _ = ss.Channel

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

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

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

// ///// ARGS

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

const APrefix = "template"

// Args is shared pkg args for Any state
type Args struct {
	am.ArgsBase `json:"-"`
}

func (Args) () string {
	return APrefix
}

// -----

type AChannel struct {
	Args `json:"-"`

	// Return chan.
	ReturnCh chan<- []string
}

func (AChannel) () string {
	return ss.Channel
}

// -----

type ABaz struct {
	Args `json:"-"`

	// Address with logging.
	Addr string `log:"addr"`
}

func (ABaz) () string {
	return ss.Baz
}

// ----- RPC

func init() {
	for ,  := range ArgsRpc {
		gob.Register()
	}
}

// ArgsRpc will be available in the REPL.
var ArgsRpc = []am.ArgsApi{ABaz{}}

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

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

func ( *Tracer) () string {
	return "mytrace"
}

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