package machine

import (
	
	
	
)

// RelationsResolver is an interface for parsing relations between states.
// Not thread-safe.
// TODO support custom relation types and additional state properties.
// TODO refac Relations
type RelationsResolver interface {
	// TargetStates returns the target states after parsing the relations.
	TargetStates(t *Transition, calledStates, index S) S
	// NewAutoMutation returns an (optional) auto mutation which is appended to
	// the queue after the transition is executed. It also returns the names of
	// the called states.
	NewAutoMutation() (*Mutation, S)
	// SortStates sorts the states in the order their handlers should be
	// executed.
	SortStates(states S)
	// RelationsOf returns a list of relation types of the given state.
	RelationsOf(fromState string) ([]Relation, error)
	// RelationsBetween returns a list of relation types between the given
	// states.
	RelationsBetween(fromState, toState string) ([]Relation, error)
	InboundRelationsOf(toState string) (S, error)
	// TODO InboundRelationsBetween

	// NewSchema runs when Machine receives a new struct.
	NewSchema(schema Schema, states S)
}

// DefaultRelationsResolver is the default implementation of the
// RelationsResolver with Add, Remove, Require and After. It can be overridden
// using Opts.Resolver.
// TODO refac RelationsDefault
type DefaultRelationsResolver struct {
	// TODO replace with TimeIndex, log(), Schema
	//   - for netmach mut filtering
	//   - for alternative impls
	Machine      *Machine
	Transition   *Transition
	Index        S
	topology     S
	statesBefore S
}

var _ RelationsResolver = &DefaultRelationsResolver{}

func ( *DefaultRelationsResolver) ( Schema,  S) {
	if .Machine == nil {
		// manual resolver instance
		return
	}

	.Index = 

	 := newGraph()
	for ,  := range .Index {
		 := .Machine.schema[]
		for ,  := range .Require {
			.AddEdge(, )
		}
	}

	,  := .TopologicalSort()
	if  != nil {
		// cycle, keep unsorted
		.Machine.log(LogChanges, "[resolver] %s: %s for %s",
			ErrRelation, , .Machine.id)
	} else {
		.topology = 
	}
}

// TargetStates implements RelationsResolver.TargetStates.
func ( *DefaultRelationsResolver) (
	 *Transition, ,  S,
) S {
	// TODO race from queue
	.Transition = 
	.Machine = .Machine
	.Index = 
	.statesBefore = .StatesBefore()

	 := .Machine
	 = .mustParseStates()
	 = .parseAdd()
	 = slicesUniq()
	 = .parseRequire()

	// start from the end
	// TODO optimize?
	 := slicesReverse()

	// collect blocked calledStates
	 := map[string]struct{}{}

	// remove already blocked calledStates
	 = slicesFilter(, func( string,  int) bool {
		 := .stateBlockedBy(, )

		// ignore blocking by already blocked states
		 = slicesFilter(, func( string,  int) bool {
			,  := []
			return !
		})
		if len() == 0 {
			return true
		}

		[] = struct{}{}
		// if state wasn't implied by another state (was one of the active
		// states) then make it a higher priority log msg
		var  LogLevel
		if .is(S{}) {
			 = LogOps
		} else {
			 = LogDecisions
		}

		.log(, "[rel:remove] %s by %s", , j())
		if .isLogSteps() {
			if .is(S{}) {
				.addSteps(newStep("", , StepRemove, 0))
			} else {
				.addSteps(newStep("", , StepRemoveNotActive, 0))
			}
		}

		return false
	})

	// states removed by the states which are about to be set
	var  S
	for ,  := range  {
		 := .schema[]
		if .Remove != nil {
			 = append(, .Remove...)
		}
	}
	 = slicesFilter(.parseAdd(), func( string,
		 int,
	) bool {
		return !slices.Contains(, )
	})
	 = slicesUniq()

	// Parsing required states allows to avoid cross-removal of states
	 := .parseRequire(slicesReverse())
	.SortStates()

	return 
}

// NewAutoMutation implements RelationsResolver.GetAutoMutation.
func ( *DefaultRelationsResolver) () (*Mutation, S) {
	 := .Transition
	 := .Machine
	var  S

	// check all Auto states
	for  := range .schema {
		if !.schema[].Auto {
			continue
		}
		// check if the state is blocked by another active state
		 := func() bool {
			for ,  := range .activeStates {
				if slices.Contains(.schema[].Remove, ) {
					.log(LogEverything, "[auto:rel:remove] %s by %s", ,
						)
					return true
				}
			}
			return false
		}
		if !.is(S{}) && !() {
			 = append(, )
		}
	}
	if len() < 1 {
		return nil, nil
	}

	 := Mutation{
		Type:   MutationAdd,
		Called: .Index(),
		IsAuto: true,
	}
	.cacheCalled.Store(&)

	return &, 
}

// SortStates implements RelationsResolver.SortStates.
func ( *DefaultRelationsResolver) ( S) {
	 := .Transition
	 := .Machine

	.sortRequire()

	// sort by After
	// TODO optimize / cache (but not in debug, to have steps)
	sort.SliceStable(, func(,  int) bool {
		 := []
		 := []
		 := .schema[]
		 := .schema[]

		// forward relations
		if slices.Contains(.After, ) {
			if .isLogSteps() {
				.addSteps(newStep(, , StepRelation, RelationAfter))
			}
			return false

		} else if slices.Contains(.After, ) {
			if .isLogSteps() {
				.addSteps(newStep(, , StepRelation, RelationAfter))
			}
			return true
		}

		return false
	})
}

// sortRequire sorts the states by Require relations.
func ( *DefaultRelationsResolver) ( S) {
	// TODO optimize with an index / cache
	sort.SliceStable(, func(,  int) bool {
		return slices.Index(.topology, []) <
			slices.Index(.topology, [])
	})
}

// TODO docs
func ( *DefaultRelationsResolver) ( S) S {
	// TODO optimize: loose Contains?
	 := .Transition
	 := 
	 := S{}
	 := true
	for  {
		 = false
		for ,  := range  {
			 := .Machine.schema[]

			if slices.Contains(.statesBefore, ) && !.Multi {
				continue
			}
			if slices.Contains(, ) {
				continue
			}

			// filter the Add relation from states called for removal
			var  S
			for ,  := range .Add {
				 := slices.Index(.Index, )
				if .Type() == MutationRemove &&
					slices.Contains(.Mutation.Called, ) {

					continue
				}
				 = append(, )
			}
			if  == nil {
				continue
			}

			if .Machine.semLogger.IsSteps() {
				.addSteps(newSteps(, , StepRelation,
					RelationAdd)...)
				.addSteps(newSteps("", , StepSet, 0)...)
			}
			 = append(, ...)
			 = append(, )
			 = true
		}
	}

	return 
}

// TODO docs
func ( *DefaultRelationsResolver) (
	 S,  string,
) S {
	 := .Machine
	 := .Transition
	 := S{}

	// TODO optimize by schema index eg ["Foo-Bar-Remove"]
	for ,  := range  {
		 := .schema[]
		if !slices.Contains(.Remove, ) {
			continue
		}

		if .isLogSteps() {
			.addSteps(newStep(, , StepRelation,
				RelationRemove))
		}
		 = append(, )
	}

	return 
}

// TODO docs
func ( *DefaultRelationsResolver) ( S) S {
	 := .Transition
	 := 0
	// maps of states with their required states missing
	 := map[string]S{}

	for  != len() {
		 = len()
		 = slicesFilter(, func( string,  int) bool {
			 := .Machine.schema[]
			 := .getMissingRequires(, , )
			if len() > 0 {
				[] = 
			}
			return len() == 0
		})
	}

	if len() > 0 {
		 := S{}
		for ,  := range  {
			 = append(, +"(-"+jw(, " -")+")")
		}
		if .IsAuto() {
			.Machine.log(LogDecisions, "[reject:auto] %s", jw(, " "))
		} else {
			.Machine.log(LogOps, "[reject] %s", jw(, " "))
		}
	}

	return 
}

// TODO docs
func ( *DefaultRelationsResolver) (
	 string,  State,  S,
) S {
	 := .Transition
	 := S{}

	for ,  := range .Require {
		if .isLogSteps() {
			.addSteps(newStep(, , StepRelation,
				RelationRequire))
		}
		if slices.Contains(, ) {
			continue
		}
		 = append(, )
		if .isLogSteps() {
			.addSteps(newStep(, "", StepRemoveNotActive, 0))
		}

		 := slices.Index(.Index, )
		if slices.Contains(.Mutation.Called, ) && .isLogSteps() {
			// TODO optimize: stop on cancel
			.addSteps(newStep("", , StepCancel, 0))
		}
	}

	return 
}

// RelationsBetween returns a list of outbound relations between
// fromState -> toState. Not thread safe.
func ( *DefaultRelationsResolver) (
	,  string,
) ([]Relation, error) {
	 := .Machine

	if !.Has1() {
		return nil, fmt.Errorf("%w: %s", ErrStateUnknown, )
	}
	if !.Has1() {
		return nil, fmt.Errorf("%w: %s", ErrStateUnknown, )
	}

	 := .schema[]
	var  []Relation

	if .Add != nil && slices.Contains(.Add, ) {
		 = append(, RelationAdd)
	}

	if .Require != nil && slices.Contains(.Require, ) {
		 = append(, RelationRequire)
	}

	if .Remove != nil && slices.Contains(.Remove, ) {
		 = append(, RelationRemove)
	}

	if .After != nil && slices.Contains(.After, ) {
		 = append(, RelationAfter)
	}

	return , nil
}

// RelationsOf returns a list of relation types of the given state.
// Not thread safe.
func ( *DefaultRelationsResolver) ( string) (
	[]Relation, error,
) {
	 := .Machine

	if !.Has1() {
		return nil, fmt.Errorf("%w: %s", ErrStateUnknown, )
	}
	 := .schema[]

	var  []Relation
	if .Add != nil {
		 = append(, RelationAdd)
	}

	if .Require != nil {
		 = append(, RelationRequire)
	}

	if .Remove != nil {
		 = append(, RelationRemove)
	}

	if .After != nil {
		 = append(, RelationAfter)
	}

	return , nil
}

// InboundRelationsOf returns a list of states pointing to [toState].
// Not thread safe.
func ( *DefaultRelationsResolver) ( string) (
	S, error,
) {
	 := .Machine

	if !.Has1() {
		return nil, fmt.Errorf("%w: %s", ErrStateUnknown, )
	}

	var  S
	for ,  := range .schema {
		if  ==  {
			continue
		}

		if slices.Contains(.Add, ) {
			 = append(, )
		}

		if slices.Contains(.Require, ) {
			 = append(, )
		}

		if slices.Contains(.Remove, ) {
			 = append(, )
		}

		if slices.Contains(.After, ) {
			 = append(, )
		}
	}

	return , nil
}

// graph represents a directed graph using an adjacency list.
type graph struct {
	vertices map[string][]string
}

// newGraph creates a new states graph.
func newGraph() *graph {
	return &graph{vertices: make(map[string][]string)}
}

// AddEdge adds a directed edge from src to dest.
func ( *graph) (,  string) {
	.vertices[] = append(.vertices[], )
}

// TopologicalSort performs a topological sort on the graph.
func ( *graph) () ([]string, error) {
	 := make(map[string]bool)
	var  []string
	 := make(map[string]bool)

	var  func(string) error
	 = func( string) error {
		if [] {
			return fmt.Errorf("state %s has a Require cycle", )
		}
		if ![] {
			[] = true
			for ,  := range .vertices[] {
				if  := ();  != nil {
					return 
				}
			}
			[] = false
			[] = true
			 = append(, )
		}

		return nil
	}

	for  := range .vertices {
		if ![] {
			if  := ();  != nil {
				return nil, 
			}
		}
	}

	return , nil
}