package machine

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
)

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

// ///// PUB UTILS

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

// IsActiveTick returns true if the tick represents an active state
// (odd number).
func ( uint64) bool {
	return %2 == 1
}

// NextActive returns the absolute tick when the state will be active again
// (excluding the currently active tick).
func ( uint64) uint64 {
	if IsActiveTick() {
		return  + 2
	}
	return  + 1
}

// NextInactive returns the absolute tick when the state will be inactive again
// (excluding the currently inactive tick).
func ( uint64) uint64 {
	if !IsActiveTick() {
		return  + 2
	}
	return  + 1
}

// NextActiveIn returns the number of ticks until the state will be active again
// (excluding the currently active tick).
func ( uint64) int {
	if IsActiveTick() {
		return 2
	}
	return 1
}

// NextInactiveIn returns the number of ticks until the state will be inactive
// again (excluding the currently inactive tick).
func ( uint64) int {
	if !IsActiveTick() {
		return 2
	}
	return 1
}

// IsQueued returns true if the mutation has been queued, and the result
// represents the queue time it will be processed.
func ( Result) bool {
	return  > Canceled
}

// EnvLogLevel returns a log level from an environment variable, AM_LOG by
// default.
func ( string) LogLevel {
	if  == "" {
		 = EnvAmLog
	}
	,  := strconv.Atoi(os.Getenv())

	return LogLevel()
}

// TODO prevent using these names as state names
var handlerSuffixes = []string{
	SuffixEnter, SuffixExit, SuffixState, SuffixEnd, StateAny,
}

// IsHandler checks if a method name is a handler method, by returning a state
// name.
func ( S,  string) (string, string) {
	if  == StateAny+SuffixEnter ||  == StateAny+SuffixState {
		return "", ""
	}

	// suffixes
	for ,  := range handlerSuffixes {
		if strings.HasSuffix(, ) && len() != len() &&
			 != StateAny+ {
			return [0 : len()-len()], ""
		}
	}

	// AnyFoo
	if strings.HasPrefix(, StateAny) && len() != len(StateAny) &&
		 != StateAny+SuffixState {

		return [len(StateAny):], ""
	}

	// FooBar
	for ,  := range  {
		if !strings.HasPrefix(, ) {
			continue
		}

		for ,  := range  {
			if + ==  {
				return , 
			}
		}
	}

	return "", ""
}

// TestMockClock mocks the internal clock of the machine. Only for testing.
func ( *Machine,  Clock) {
	.clock = 
}

// AMerge merges 2 or more maps into 1. Useful for passing args from many
// packages.
func [ comparable,  any]( ...map[]) map[] {
	 := map[]{}

	for ,  := range  {
		for ,  := range  {
			[] = 
		}
	}

	return 
}

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

// ///// STATE LIST

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

// S (state names) is a string list of state names.
type S []string

// FilterIndex returns a subset of S from specified indexes.
func ( S) ( []int) S {
	return IndexToStates(, )
}

// FilterPrefix returns all the states with a prefix.
func ( S) ( ...string) S {
	 := S{}
	for ,  := range  {
		 = .Add(StatesWithPrefix(, ))
	}

	return 
}

// FilterMatch returns all the states matching a regexp.
func ( S) ( *regexp.Regexp) S {
	 := S{}
	for ,  := range  {
		if .MatchString() {
			 = append(, )
		}
	}

	return 
}

// Prefix lists all states with a prefix.
func ( S) ( string) S {
	return StatesPrefix(, )
}

// Add concatenates multiple state lists into one, removing duplicates.
// Useful for merging lists of states, eg a state group with other states
// involved in a relation.
func ( S) ( ...S) S {
	if len() == 0 {
		return 
	}

	 = append([]S{}, ...)
	return slicesUniq(slices.Concat(...))
}

// Add1 is like [S.Add], but for single state names.
func ( S) ( ...string) S {
	 := slices.Clone()
	 := append(, ...)
	return slicesUniq()
}

// Delete removes states from all passed state groups.
func ( S) ( ...S) S {
	return SRem(, ...)
}

// Delete1 is like [S.Delete], but for single state names.
func ( S) ( ...string) S {
	return SRem(, )
}

// Sub returns the states from [s] that are missing in [other] (subtract).
func ( S) ( S) S {
	return StatesDiff(, )
}

// Shared returns states present in both S and other (overlap).
func ( S) ( S) S {
	return StatesShared(, )
}

// Equal returns true if states1 and states2 are equal, regardless of
// order.
func ( S) ( S) bool {
	return StatesEqual(, )
}

// EqualOrder is like [S.Equal], but also checks the order of states.
func ( S) ( S) bool {
	if len() != len() {
		return false
	}

	for  := range  {
		if [] != [] {
			return false
		}
	}

	return true
}

// Index returns an index of passed [states]. Unknown states are
// represented by -1.
func ( S) ( S) []int {
	return StatesToIndex(, )
}

func ( S) () string {
	return Hash(strings.Join(, ","), 4)
}

// ----- old

// IndexToStates is deprecated, use [S.FilterIndex].
func ( S,  []int) S {
	 := make(S, len())
	for  := range  {
		 := "unknown" + strconv.Itoa([])
		if len() > [] && [] != -1 {
			 = [[]]
		}
		[] = 
	}

	return 
}

// StatesToIndex is deprecated, use [S.Index].
func ( S,  S) []int {
	 := make([]int, len())
	for  := range  {
		[] = slices.Index(, [])
	}

	return 
}

// StatesDiff is deprecated, use [S.Sub].
func ( S,  S) S {
	// TODO optimize
	return slicesFilter(, func( string,  int) bool {
		return !slices.Contains(, )
	})
}

// StatesShared is deprecated, use [S.Shared].
func ( S,  S) S {
	return slicesFilter(, func( string,  int) bool {
		return slices.Contains(, )
	})
}

// StatesEqual is deprecated, use [S.Equal].
func ( S,  S) bool {
	return slicesEvery(, ) && slicesEvery(, )
}

// StatesPrefix is deprecated, use [S.Prefix].
func ( string,  S) S {
	 := make(S, len())
	for ,  := range  {
		[] =  + 
	}

	return 
}

// StatesWithPrefix is deprecated, use [S.FilterPrefix].
func ( string,  S) S {
	 := S{}
	for ,  := range  {
		if strings.HasPrefix(, ) {
			 = append(, )
		}
	}

	return 
}

// SAdd is deprecated, use [S.Add].
func ( ...S) S {
	// TODO test
	// TODO move to resolver
	if len() == 0 {
		return S{}
	}

	 := slices.Clone([0])
	for  := 1;  < len(); ++ {
		 = append(, []...)
	}

	return slicesUniq()
}

// SRem is deprecated, use [S.Delete].
func ( S,  ...S) S {
	// TODO test
	// TODO move to resolver
	 := slices.Clone()
	if len() == 0 {
		return 
	}

	for  := 1;  < len(); ++ {
		for  := 0;  < len([]); ++ {
			 = slicesWithout(, [][])
		}
	}

	return 
}

// ///// ///// /////n

// ///// SCHEMA

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

// ----- STATE

// State defines a single state of a machine, its properties, relations, and tags.
// nolint:lll
type State struct {
	Auto    bool     `json:"auto,omitempty" yaml:"auto,omitempty" toml:"auto,omitempty"`
	Multi   bool     `json:"multi,omitempty" yaml:"multi,omitempty" toml:"multi,omitempty"`
	Require S        `json:"require,omitempty" yaml:"require,omitempty" toml:"require,omitempty"`
	Add     S        `json:"add,omitempty" yaml:"add,omitempty" toml:"add,omitempty"`
	Remove  S        `json:"remove,omitempty" yaml:"remove,omitempty" toml:"remove,omitempty"`
	After   S        `json:"after,omitempty" yaml:"after,omitempty" toml:"after,omitempty"`
	Tags    []string `json:"tags,omitempty" yaml:"tags,omitempty" toml:"tags,omitempty"`
}

// Extend adds new states to relations of the source state, without
// removing existing ones. Useful for adjusting shared stated to a specific
// machine. Only "true" values for Auto and Multi are applied from [overlay].
//
//	ssS.HandshakeDone: SharedSchema[ssS.HandshakeDone].Extend(
//		am.State{
//			Require: S{ssS.ClientConnected},
//			Remove: S{Exception},
//		}),
func ( State) ( State) State {
	return StateAdd(, )
}

// Set replaces passed relations and properties of the source state.
// Only relations in the overlay state are replaced, the rest is preserved.
// If [overlay] has all fields `nil`, then only [auto] and [multi] get applied.
//
//	 ssS.HandshakeDone: s.Set(false, true, State{
//			Remove: S{"C"},
//		})
func ( State) (,  bool,  State) State {
	return StateSet(, , , )
}

// SetRels is like [State.Set], but inherits properties (Auto, Multi).
func ( State) ( State) State {
	return StateSet(, .Auto, .Multi, )
}

func ( State) () State {
	 := State{
		Auto:  .Auto,
		Multi: .Multi,
	}

	if .Require != nil {
		.Require = slices.Clone(.Require)
	}
	if .Add != nil {
		.Add = slices.Clone(.Add)
	}
	if .Remove != nil {
		.Remove = slices.Clone(.Remove)
	}
	if .After != nil {
		.After = slices.Clone(.After)
	}
	if .Tags != nil {
		.Tags = slices.Clone(.Tags)
	}

	return 
}

func ( State) ( string) bool {
	return slices.Contains(.Tags, )
}

// ----- old

// StateAdd is deprecated, use [State.Extend].
func ( State,  State) State {
	// TODO example
	// TODO test
	 := .Clone()
	 := .Clone()

	if .Auto {
		.Auto = true
	}
	if .Multi {
		.Multi = true
	}

	// relations
	if .Add != nil {
		.Add = SAdd(.Add, .Add)
	}
	if .Remove != nil {
		.Remove = SAdd(.Remove, .Remove)
	}
	if .Require != nil {
		.Require = SAdd(.Require, .Require)
	}
	if .After != nil {
		.After = SAdd(.After, .After)
	}

	return 
}

// StateSet is Deprecated, use [State.Set].
func ( State, ,  bool,  State) State {
	// TODO example
	// TODO test
	 := .Clone()
	 := .Clone()

	// properties
	.Auto = 
	.Multi = 

	// relations
	if .Add != nil {
		.Add = .Add
	}
	if .Remove != nil {
		.Remove = .Remove
	}
	if .Require != nil {
		.Require = .Require
	}
	if .After != nil {
		.After = .After
	}

	return 
}

// ----- SCHEMA

// Schema is a map of state names to state definitions.
type Schema map[string]State

// Merge merges multiple state structs into one, overriding the previous
// state definitions. No relation-level merging takes place.
func ( Schema) ( ...Schema) Schema {
	// TODO mark all-but-last states as Inherited?
	// TODO example
	// TODO test
	// defaults
	 := len()
	switch  {
	case 0:
		return 
	}

	 := .Clone()
	for  := 0;  < ; ++ {
		maps.Copy(, [])
	}

	return .Clone()
}

// Prefix will prefix all state names with [prefix]. removeDups will skip
// overlaps eg "FooFooName" will be "Foo".
func ( Schema) (
	 string,  bool, ,  S,
) Schema {
	 := Schema{}
	for ,  := range  {
		 = .Clone()

		if len() > 0 && !slices.Contains(, ) {
			continue
		} else if len() > 0 && slices.Contains(, ) {
			continue
		}

		for ,  := range .After {
			 := 
			if ! || !strings.HasPrefix(, ) {
				 =  + 
			}
			.After[] = 
		}
		for ,  := range .Add {
			 := 
			if ! || !strings.HasPrefix(, ) {
				 =  + 
			}
			.Add[] = 
		}
		for ,  := range .Remove {
			 := 
			if ! || !strings.HasPrefix(, ) {
				 =  + 
			}
			.Remove[] = 
		}
		for ,  := range .Require {
			 := 
			if ! || !strings.HasPrefix(, ) {
				 =  + 
			}
			.Require[] = 
		}

		 := 
		if ! || !strings.HasPrefix(, ) {
			 =  + 
		}

		// build
		[] = 
	}

	return 
}

// Clone deep clones the states struct and returns a copy.
func ( Schema) () Schema {
	 := make(Schema)

	for ,  := range  {
		[] = .Clone()
	}

	return 
}

// Parse sanitizes the schema and returns potential errors.
func ( Schema) () (Schema, error) {
	// TODO capitalize states
	// TODO ErrFoo must require Exception

	 := .Clone()
	 := slices.Collect(maps.Keys())
	var  error
	for ,  := range  {

		// avoid self removal
		if slices.Contains(.Remove, ) {
			.Remove = slicesWithout(.Remove, )
		}

		// don't Remove if in Add
		for ,  := range .Add {
			if slices.Contains(.Remove, ) {
				.Remove = slicesWithout(.Remove, )

				// check if exists
			} else if !slices.Contains(, ) {
				.Add = slicesWithout(.Add, )
			}
		}

		// avoid being after itself
		if slices.Contains(.After, ) {
			.After = slicesWithout(.After, )
		}

		// detect require-remove conflicts
		for ,  := range .Require {
			if slices.Contains(.Remove, ) {
				 = errors.Join(, fmt.Errorf(
					"%w: require-remove conflict for %s to %s",
					ErrSchema, , ,
				))
			}
		}

		// remove references to non-existing states
		for ,  := range .Remove {
			if !slices.Contains(, ) {
				.Remove = slicesWithout(.Remove, )
			}
		}
		for ,  := range .After {
			if !slices.Contains(, ) {
				.After = slicesWithout(.After, )
			}
		}
		for ,  := range .Remove {
			if !slices.Contains(, ) {
				.Remove = slicesWithout(.Remove, )
			}
		}

		[] = 
	}

	return , 
}

// FilterByTag returns a subset schema with states having the given tag.
func ( Schema) ( string) Schema {
	 := Schema{}
	for ,  := range  {
		if slices.Contains(.Tags, ) {
			[] = 
		}
	}

	return 
}

// Names is a random-order list of defined state names.
func ( Schema) () S {
	return slices.Collect(maps.Keys())
}

// ----- old

// SchemaMerge is deprecated, use [Schema.Merge].
func ( ...Schema) Schema {
	 := len()
	switch  {
	case 0:
		return Schema{}
	case 1:
		return [0]
	}

	 := make(Schema)
	for  := 0;  < ; ++ {
		// TODO clone?
		maps.Copy(, [])
	}

	return .Clone()
}

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

// ///// HANDLERS

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

// handler represents a list of transition handler methods.
type handler struct {
	id  string
	num int
	// map-based handler
	isMap bool
	mx    sync.Mutex
	opts  *BindOpts

	// maps
	negotiations map[string]HandlerNegotiation
	finals       map[string]HandlerFinal

	// reflect
	h            any
	methods      *reflect.Value
	methodCache  map[string]reflect.Value
	missingCache map[string]struct{}
}

// BindOpts define how the methods are bound to the state machine. Optional.
type BindOpts struct {
	// Id of this binding, optional.
	Id string
	// method name will have this prefix removed
	MethodPrefixTrim string
	// only states with this prefix
	StatePrefix string
}

type handlerCall struct {
	fn *reflect.Value
	// TODO debug only
	name        string
	negotiation HandlerNegotiation
	final       HandlerFinal
	event       *Event
	timeout     bool
}

func ( *handlerCall) () bool {
	 := true
	if .fn != nil {
		 := .fn.Call([]reflect.Value{reflect.ValueOf(.event)})
		if len() > 0 {
			// TODO log err?
			, _ = [0].Interface().(bool)
		}
	} else if .negotiation != nil {
		 = .negotiation(.event)
	} else if .final != nil {
		.final(.event)
	} else {
		panic("invalid handler call " + .name)
	}

	return 
}

func newHandlerCallStruct(
	 *Event,  *handler,  string,  *Machine,
) *handlerCall {
	//

	// cache
	,  := .missingCache[]
	if  {
		.mx.Unlock()

		return nil
	}
	,  := .methodCache[]
	if ! {
		 = .methods.MethodByName()

		// support field handlers
		if !.IsValid() {
			 = .methods.Elem().FieldByName()
		}
		if !.IsValid() {
			.missingCache[] = struct{}{}
			.mx.Unlock()

			return nil
		}
		.methodCache[] = 
	}
	.mx.Unlock()

	// call the handler
	.log(LogOps, "[handler:%d] %s", .num, )
	.currentHandler.Store()

	// tracers
	.tracersMx.RLock()
	for  := range .tracers {
		.tracers[].HandlerStart(.t.Load(), .id, )
	}
	.tracersMx.RUnlock()

	return &handlerCall{
		fn:      &,
		name:    ,
		event:   ,
		timeout: false,
	}
}

func newHandlerCallMap(
	 *Event,  *handler,  string,  *Machine,
) *handlerCall {
	//

	 := strings.HasSuffix(, SuffixState) ||
		strings.HasSuffix(, SuffixEnd)

	if  {
		if ,  := .finals[]; ! {
			.mx.Unlock()
			return nil
		}
	} else {
		if ,  := .negotiations[]; ! {
			.mx.Unlock()
			return nil
		}
	}
	.mx.Unlock()

	// call the handler
	.log(LogOps, "[handler:%d] %s", .num, )
	.currentHandler.Store()

	// tracers
	.tracersMx.RLock()
	for  := range .tracers {
		.tracers[].HandlerStart(.t.Load(), .id, )
	}
	.tracersMx.RUnlock()

	 := &handlerCall{
		name:    ,
		event:   ,
		timeout: false,
	}

	if  {
		.final = .finals[]
	} else {
		.negotiation = .negotiations[]
	}

	return 
}

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

// ///// UTILS

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

// truncateStr with shorten the string and leave a tripedot suffix.
func truncateStr( string,  int) string {
	if len() <=  {
		return 
	}
	if  < 5 {
		return [:]
	} else {
		return [:-3] + "..."
	}
}

// j joins state names into a single string
func j( []string) string {
	return strings.Join(, " ")
}

// jw joins state names into a single string with a separator.
func jw( []string,  string) string {
	return strings.Join(, )
}

func closeSafe[ any]( chan ) {
	select {
	case <-:
	default:
		close()
	}
}

// compareArgs return true if args2 is a subset of args1.
func compareArgs(,  A) bool {
	 := true

	for ,  := range  {
		// TODO better comparisons
		if [] !=  {
			 = false
			break
		}
	}

	return 
}

// RandId generates a random ID of the given length (defaults to 8).
func randId( int) string {
	if  == 0 {
		 = 16
	}
	++
	 =  / 2

	 := make([]byte, )
	,  := rand.Read()
	if  != nil {
		return "error"
	}

	return hex.EncodeToString()
}

func slicesWithout[ ~[],  comparable]( ,  )  {
	 := slices.Index(, )
	 := slices.Clone()
	if  == -1 {
		return 
	}
	return slices.Delete(, , +1)
}

// slicesNone returns true if none of the elements of coll2 are in coll1.
func slicesNone[ ~[],  ~[],  comparable]( ,  ) bool {
	for ,  := range  {
		if slices.Contains(, ) {
			return false
		}
	}
	return true
}

// slicesEvery returns true if all elements of coll2 are in coll1.
func slicesEvery[ ~[],  ~[],  comparable]( ,  ) bool {
	for ,  := range  {
		if !slices.Contains(, ) {
			return false
		}
	}
	return true
}

// TODO replace with slices.DeleteFunc
func slicesFilter[ ~[],  any]( ,  func( ,  int) bool)  {
	 := make(, 0, len())
	for ,  := range  {
		if (, ) {
			 = append(, )
		}
	}
	return 
}

func slicesReverse[ ~[],  any]( )  {
	 := make(, len())
	for  := range  {
		[] = [len()-1-]
	}
	return 
}

func slicesUniq[ comparable]( []) [] {
	if len() == 0 {
		return []{}
	}
	 := make(map[]struct{}, len())
	 := make([], 0, len())
	for ,  := range  {
		if ,  := []; ! {
			[] = struct{}{}
			 = append(, )
		}
	}

	return 
}

func cloneOptions( *Opts) *Opts {
	if  == nil {
		return &Opts{}
	}

	return &Opts{
		Id:                   .Id,
		HandlerTimeout:       .HandlerTimeout,
		DontPanicToException: .DontPanicToException,
		DontLogStackTrace:    .DontLogStackTrace,
		DontLogId:            .DontLogId,
		Resolver:             .Resolver,
		LogLevel:             .LogLevel,
		Tracers:              .Tracers,
		LogArgs:              .LogArgs,
		QueueLimit:           .QueueLimit,
		Parent:               .Parent,
		ParentId:             .ParentId,
		Tags:                 .Tags,
		DetectEval:           .DetectEval,
	}
}

func ( string) string {
	if  == "" {
		return ""
	}

	// Decode the first character to find out exactly how many bytes it uses
	,  := utf8.DecodeRuneInString()

	// Uppercase the first character, then append the rest of the original string
	return strings.ToUpper(string()) + [:]
}

// OptArgs will return the first [A] from a list.
func ( []A) A {
	if len() > 0 {
		return [0]
	}
	return nil
}

// OptCtx will return the first [context.Context] from a list.
func ( []context.Context) context.Context {
	if len() > 0 {
		return [0]
	}
	return nil
}

// OptEv will return the first [*Event] from a list.
func ( []*Event) *Event {
	if len() > 0 {
		return [0]
	}
	return nil
}

// optBindOpts will return the first [BindOpts] from a list.
func optBindOpts( []BindOpts) BindOpts {
	if len() > 0 {
		return [0]
	}
	return BindOpts{}
}

// goroutineNum returns the ID of the current goroutine, or 0 if err.
// Numbers are re-assigned, so this is not a bulletproof way of IDing threads.
func goroutineNum() int64 {
	 := make([]byte, 4024)
	 := runtime.Stack(, false)
	 := string([:])
	 := strings.Split(, "\n")
	 := strings.Split([0], " ")
	,  := strconv.Atoi([1])

	return int64()
}

func captureStackTrace() string {
	 := make([]byte, 4024)
	 := runtime.Stack(, false)
	 := string([:])
	 := strings.Split(, "\n")
	 := strings.Contains(, "panic")
	slices.Reverse()

	 := []string{
		"AddErr", "AddErrState", "Remove", "Remove1", "Add", "Add1", "Set",
	}
	// TODO trim tails start at reflect.Value.Call({
	//  with asyncmachine 2 frames down

	// trim the head, remove junk
	 := false
	for ,  := range  {
		if  && strings.HasPrefix(, "panic(") {
			 = [:-1]
			break
		}

		for ,  := range  {
			if strings.Contains("machine.(*Machine)."++"(", ) {
				 = [:-1]
				 = true
				break
			}
		}
		if  {
			break
		}
	}
	slices.Reverse()
	 := strings.Join(, "\n")

	if  := os.Getenv(EnvAmTraceFilter);  != "" {
		 = strings.ReplaceAll(, , "")
	}

	return 
}

// Hash is a general hashing function.
func ( string,  int) string {
	 := md5.New()
	.Write([]byte())
	if  == 0 {
		 = 6
	}

	 := hex.EncodeToString(.Sum(nil))
	// short hash
	return [:]
}