package node

import (
	
	
	
	

	
	amhelp 
	am 
	
	
	ssrpc 
	ampipe 
)

var (
	ssW = states.WorkerStates
	sgW = states.WorkerGroups
)

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

	Name string
	Kind string
	// AcceptClient is the ID of a client, passed by the supervisor. Worker should
	// only accept connections from this client.
	AcceptClient string

	// ConnTimeout is the time to wait for an outbound connection to be
	// established.
	ConnTimeout     time.Duration
	DeliveryTimeout time.Duration

	// BootAddr is the address of the bootstrap machine.
	BootAddr string
	// BootRpc is the RPC client connection to bootstrap machine, which passes
	// connection info to the Supervisor.
	BootRpc *rpc.Client

	// LocalAddr is the address of the local RPC server.
	LocalAddr string
	// LocalRpc is the local RPC server, used by the Supervisor to connect.
	LocalRpc *rpc.Server

	// PublicAddr is the address of the public RPC server.
	PublicAddr string
	// PublicRpc is the public RPC server, used by the Client to connect.
	PublicRpc *rpc.Server
}

// NewWorker initializes a new Worker instance and returns it, or an error if
// validation fails.
func ( context.Context,  string,  am.Schema,
	 am.S,  *WorkerOpts,
) (*Worker, error) {
	// validate
	if  == "" {
		return nil, errors.New("worker: kind required")
	}
	if  == nil {
		return nil, errors.New("worker: stateNames required")
	}
	if  == nil {
		return nil, errors.New("worker: workerStruct required")
	}
	if  == nil {
		 = &WorkerOpts{}
	}

	 := fmt.Sprintf("%s-%s-%s", , utils.Hostname(), utils.RandId(6))

	 := &Worker{
		ConnTimeout:     5 * time.Second,
		DeliveryTimeout: 5 * time.Second,
		Name:            ,
		Kind:            ,
	}

	if amhelp.IsDebug() {
		// increase timeouts using context.WithTimeout directly
		.DeliveryTimeout = 10 * .DeliveryTimeout
	}

	,  := am.NewCommon(, "nw-"+.Name, , , ,
		.Parent, &am.Opts{Tags: []string{"node-worker"}})
	if  != nil {
		return nil, 
	}

	.SemLogger().SetArgsMapper(LogArgs)
	.Mach = 
	amhelp.MachDebugEnv()

	// check base states
	 = amhelp.Implements(.StateNames(), ssW.Names())
	if  != nil {
		 := fmt.Errorf(
			"client has to implement am/node/states/WorkerStates: %w", )
		return nil, 
	}

	return , nil
}

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

// ///// HANDLERS

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

func ( *Worker) ( *am.Event) {
	// TODO handle RPC errors
}

func ( *Worker) ( *am.Event) bool {
	 := ParseArgs(.Args)
	return .LocalAddr != ""
}

func ( *Worker) ( *am.Event) {
	var  error
	 := .Mach.NewStateCtx(ssW.Start)
	 := ParseArgs(.Args)
	.BootAddr = .LocalAddr

	// local RPC
	 := &rpc.ServerOpts{
		PayloadState: ssW.SuperSendPayload,
		Parent:       .Mach,
	}
	.LocalRpc,  = rpc.NewServer(, "localhost:0", "nw-loc-"+.Name, .Mach,
		)
	if  != nil {
		_ = AddErrRpc(.Mach, , nil)
		return
	}
	.LocalRpc.DeliveryTimeout = .DeliveryTimeout
	 = errors.Join(
		// bind to Ready state
		rpc.BindServer(.LocalRpc.Mach, .Mach, ssW.LocalRpcReady,
			ssW.SuperConnected),
		// bind to err
		ampipe.BindErr(.LocalRpc.Mach, .Mach, ssW.ErrSupervisor))
	if  != nil {
		.Mach.AddErr(, nil)
		return
	}

	// public RPC
	 = &rpc.ServerOpts{
		PayloadState: ssW.ClientSendPayload,
		Parent:       .Mach,
	}
	.PublicRpc,  = rpc.NewServer(, "0.0.0.0:0", "nw-pub-"+.Name,
		.Mach, )
	if  != nil {
		_ = AddErrRpc(.Mach, , nil)
		return
	}
	.PublicRpc.DeliveryTimeout = .DeliveryTimeout
	 = errors.Join(
		// bind to Ready state
		rpc.BindServer(.PublicRpc.Mach, .Mach, ssW.PublicRpcReady,
			ssW.ClientConnected),
		// bind to err
		ampipe.BindErr(.PublicRpc.Mach, .Mach, ssW.ErrClient))
	if  != nil {
		.Mach.AddErr(, nil)
		return
	}

	// start
	if .LocalRpc.Start() != am.Executed {
		_ = AddErrRpc(.Mach, nil, nil)
		return
	}
	if .PublicRpc.Start() != am.Executed {
		_ = AddErrRpc(.Mach, nil, nil)
		return
	}
}

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

	if .PublicRpc != nil {
		.PublicRpc.Stop(true)
	}
	if .LocalRpc != nil {
		.LocalRpc.Stop(true)
	}
	if .BootRpc != nil {
		// TODO ctx
		go .BootRpc.Stop(context.TODO(), true)
	}
	if .Dispose {
		.Mach.Dispose()
	}
}

func ( *Worker) ( *am.Event) {
	.LocalAddr = .LocalRpc.Addr
}

func ( *Worker) ( *am.Event) {
	.PublicAddr = .PublicRpc.Addr
}

func ( *Worker) ( *am.Event) {
	var  error
	 := .Mach.NewStateCtx(ssW.RpcReady)
	.Mach.EvAdd1(, ssW.Ready, nil)

	// connect to the bootstrap machine
	 := &rpc.ClientOpts{Parent: .Mach}
	.BootRpc,  = rpc.NewClient(, .BootAddr, "nw-"+.Name,
		states.BootstrapSchema, states.BootstrapStates.Names(), )
	if  != nil {
		_ = AddErrRpc(.Mach, , nil)
		return
	}
	 = ampipe.BindErr(.BootRpc.Mach, .Mach, "")
	if  != nil {
		_ = AddErrRpc(.Mach, , nil)
		return
	}
	.BootRpc.Start()

	// unblock
	go func() {
		// wait for the bootstrap client to be ready
		 := amhelp.WaitForAll(, .ConnTimeout,
			.BootRpc.Mach.When1(ssrpc.ClientStates.Ready, nil))
		if .Err() != nil {
			return // expired
		}
		if  != nil {
			_ = AddErrRpc(.Mach, , nil)
			return
		}

		// pass the local port to [bootstrap.WorkerAddState] via RPC
		.BootRpc.Worker.EvAdd1(, ssB.WorkerAddr, PassRpc(&A{
			LocalAddr:  .LocalAddr,
			PublicAddr: .PublicAddr,
			Id:         .Mach.Id(),
		}))
		.Mach.Log("Passed the local port to the bootstrap machine")
		// dispose
		go .BootRpc.Stop(.Mach.Ctx(), true)
	}()
}

func ( *Worker) ( *am.Event) {
	.Mach.Remove1(ssW.Healthcheck, nil)
}

func ( *Worker) ( *am.Event) bool {
	 := ParseArgs(.Args)
	return  != nil && .Id != ""
}

func ( *Worker) ( *am.Event) {
	.Mach.Remove1(ssW.ServeClient, nil)

	 := ParseArgs(.Args)
	.AcceptClient = .Id
	.PublicRpc.AllowId = .AcceptClient
}

func ( *Worker) ( *am.Event) bool {
	// use SuperSendPayload and ClientSendPayload instead
	return false
}

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

// ///// METHODS

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

// Start connects the worker to the bootstrap RPC.
func ( *Worker) ( string) am.Result {
	return .Mach.Add1(ssW.Start, Pass(&A{LocalAddr: }))
}

// Stop halts the worker's state machine and optionally disposes of its
// resources based on the dispose flag.
func ( *Worker) ( bool) {
	.Mach.Remove1(ssW.Start, Pass(&A{Dispose: }))
}

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

// ///// MISC

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

type WorkerOpts struct {
	// Parent is a parent state machine for a new Worker state machine. See
	// [am.Opts].
	Parent am.Api
	// TODO
	Tags []string
}