package machine

import (
	
	
	
	
	
	
	
	
	
	
)

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

// ///// PUB UTILS

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

// DiffStates returns the states that are in states1 but not in dbgtypes.
func ( S,  S) S {
	// TODO optimize
	return slicesFilter(, func( string,  int) bool {
		return !slices.Contains(, )
	})
}

// SameStates return states present in both states1 and dbgtypes.
func ( S,  S) S {
	return slicesFilter(, func( string,  int) bool {
		return slices.Contains(, )
	})
}

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

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

	for ,  := range  {
		[] = cloneState()
	}

	return 
}

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

	// TODO move to Resolver

	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 
}

// IsActiveTick returns true if the tick represents an active state
// (odd number).
func ( uint64) bool {
	return %2 == 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
}

// SAdd 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 {
	// TODO test
	// TODO move to resolver
	if len() == 0 {
		return S{}
	}

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

	return slicesUniq()
}

// SRem removes groups > 1 from nr 1.
func ( S,  ...S) S {
	// TODO test
	// TODO move to resolver
	 := slices.Clone()
	if len() == 0 {
		return 
	}

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

	return 
}

// StateAdd 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].
func ( State,  State) State {
	// TODO example
	// TODO test
	// TODO move to resolver?
	 := cloneState()
	 := cloneState()

	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 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.
func ( State, ,  bool,  State) State {
	// TODO example
	// TODO test
	// TODO move to resolver?
	 := cloneState()
	 := cloneState()

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

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

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

	return CloneSchema()
}

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

	return LogLevel()
}

// ListHandlers returns a list of handler method names from a handler struct,
// limited to [states].
func ( any,  S) ([]string, error) {
	var  []string
	var  []error

	 := func( string) {
		,  := IsHandler(, )
		if  != "" && !slices.Contains(, ) {
			 = append(, fmt.Errorf(
				"%w: %s from handler %s", ErrStateMissing, , ))
		}
		if  != "" && !slices.Contains(, ) {
			 = append(, fmt.Errorf(
				"%w: %s from handler %s", ErrStateMissing, , ))
		}

		if  != "" || ( == HandlerAnyEnter ||  == HandlerAnyState) {
			 = append(, )
			// TODO verify method signatures early (returns and params)
		}
	}

	// methods
	 := reflect.TypeOf()
	for  := 0;  < .NumMethod(); ++ {
		 := .Method().Name
		()
	}

	// fields
	 := reflect.ValueOf().Elem()
	 := .Type()
	for  := 0;  < .NumField(); ++ {
		 := .Field().Type.Kind()
		if  != reflect.Func {
			continue
		}
		 := .Field().Name
		()
	}

	return , errors.Join(...)
}

// 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  == HandlerAnyEnter ||  == HandlerAnyState {
		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 "", ""
}

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

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

// IndexToTime returns "virtual time" with selected states active. It's useful
// to use time methods on a list of states, eg the called ones.
func ( S,  []int) Time {
	 := make(Time, len())
	for ,  := range  {
		[] = 1
	}

	return 
}

// IndexToStates decodes state indexes based on the provided index.
func ( S,  []int) S {
	 := make(S, len())
	for  := range  {
		 := "unknown" + strconv.Itoa([])
		if len() > [] && [] != -1 {
			 = [[]]
		}
		[] = 
	}

	return 
}

// StatesToIndex returns a subset of [index] that matches [states]. Unknown
// states are represented by -1.
func ( S,  S) []int {
	 := make([]int, len())
	for  := range  {
		[] = slices.Index(, [])
	}

	return 
}

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

// ///// UTILS

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

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

func padString( string,  int,  string) string {
	for {
		 += 
		if len() >  {
			return [0:]
		}
	}
}

func ( Schema) (Schema, error) {
	// TODO move to Resolver
	// TODO capitalize states

	 := CloneSchema()
	 := 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 , 
}

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

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

	return 
}

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

func randId() string {
	 := make([]byte, 16)
	,  := 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,
	}
}