package machine

import (
	
	
	
	
	
	
)

// Transition represents processing of a single mutation within a machine.
type Transition struct {
	// Id is a unique identifier of the transition.
	Id string
	// Steps is a list of steps taken by this transition (so far).
	Steps []*Step
	// HasStateChanged is true if the transition has changed the state of the
	// machine. TODO useful?
	// HasStateChanged bool
	// TimeBefore is the machine time from before the transition.
	TimeBefore Time
	// TimeAfter is the machine time from after the transition. If the transition
	// has been canceled, this will be the same as TimeBefore. This field is the
	// same as TimeBefore, until the negotiation phase finishes.
	TimeAfter Time
	// TargetIndexes is a list of indexes of the target states.
	TargetIndexes []int
	// Enters is a list of states activated in this transition.
	Enters S
	// Enters is a list of states deactivated in this transition.
	Exits S
	// Mutation call which caused this transition
	Mutation *Mutation
	// Machine is the parent machine of this transition.
	Machine *Machine
	// MachApi is a subset of Machine.
	// TODO call when applicable instead of calling Machine
	// TODO rename to MachApi
	MachApi Api
	// LogEntries are log msgs produced during the transition.
	LogEntries []*LogEntry
	// PreLogEntries are log msgs produced before during the transition.
	PreLogEntries []*LogEntry
	// QueueLen is the length of the queue after the transition.
	QueueLen uint16
	// InternalLogEntriesLock is used to lock the logs to be collected by Tracers.
	// TODO getter?
	InternalLogEntriesLock sync.Mutex
	// IsCompleted returns true when the execution of the transition has been
	// fully completed.
	IsCompleted atomic.Bool
	// IsAccepted returns true if the transition has been accepted, which can
	// change during the transition's negotiation phase and while resolving
	// relations.
	IsAccepted atomic.Bool
	// TODO true for panic and timeouts
	IsBroken atomic.Bool
	// TODO confirms relations resolved and negotiation ended
	IsSettled atomic.Bool

	// last step info for panic recovery
	latestHandlerIsEnter bool
	latestHandlerIsFinal bool
	latestHandlerToState string

	cacheTargetStates atomic.Pointer[S]
	cacheStatesBefore atomic.Pointer[S]
	cacheClockBefore  atomic.Pointer[Clock]
	cacheActivated    S
	cacheDeactivated  S
	cacheSchema       Schema
}

// newTransition creates a new transition for the given mutation.
func newTransition( *Machine,  *Mutation) *Transition {
	.activeStatesMx.RLock()
	defer .activeStatesMx.RUnlock()

	 := .StateNames()
	 := .Schema()
	 := .time(nil)
	 := slices.Clone()
	 := .SemLogger()

	// gather what we know about TimeAfter at this point (except for checks)
	// TODO test case
	if !.IsCheck {
		 := IsActiveTick
		for ,  := range .Called {
			 := []
			switch {
			case .Type == MutationAdd && ([]) && [].Multi:
				[] += 2
			case .Type == MutationAdd && !([]):
				[] += 1
			case .Type == MutationRemove && ([]):
				[] += 1
			}
			// TODO support [MutationSet]
		}
	}

	 := &Transition{
		Id:          randId(),
		Mutation:    ,
		TimeBefore:  ,
		TimeAfter:   ,
		Machine:     ,
		MachApi:     ,
		cacheSchema: ,
	}

	 := slices.Clone(.activeStates)
	.cacheStatesBefore.Store(&)
	 := maps.Clone(.clock)
	.cacheClockBefore.Store(&)
	.IsAccepted.Store(true)

	// assign early to catch the logs
	.t.Store()

	// tracers
	.tracersMx.RLock()
	for ,  := range .tracers {
		if .Machine.IsDisposed() {
			break
		}
		.TransitionInit()
	}
	.tracersMx.RUnlock()

	// log stuff
	 := .CalledStates()
	 := .Type()
	 := ""
	if .IsArgs() || .Level() > LogExternal {
		 = .LogArgs(.ArgsMapper())
	}
	if .isLogSteps() {
		.addSteps(newSteps("", , StepRequested, 0)...)
	}
	if .IsAuto {
		.log(LogDecisions, "[%s:auto] %s%s", , j(), )
	} else {
		.log(LogOps, "[%s] %s%s", , j(), )
	}
	 := .Source
	if  != nil {
		.log(LogOps, "[source] %s/%s/%d", .MachId, .TxId, .MachTime)
	}

	// set up the mutation
	 := .statesToSet(, )
	 := .resolver.TargetStates(, , )

	// simulate TimeAfter (temporarily)
	.TargetIndexes = .Index()
	.cacheTargetStates.Store(&)

	 := StatesDiff(, )
	if len() > 0 {
		.log(LogOps, "[implied] %s", j())
	}

	.setupAccepted()
	if .IsAccepted.Load() {
		.setupExitEnter()
	}

	return 
}

func ( *Transition) ( MutationType,  S) S {
	 := .Machine

	switch  {

	case MutationRemove:
		 := slicesFilter(.activeStates, func( string,  int) bool {
			return !slices.Contains(, )
		})
		if .isLogSteps() {
			.addSteps(newSteps("", , StepRemove, 0)...)
		}

		return 

	case MutationAdd:
		 := slices.Concat(, .activeStates)
		if .isLogSteps() {
			.addSteps(newSteps("", StatesDiff(, .activeStates),
				StepSet, 0)...)
		}

		return 

	case MutationSet:
		 := 
		if .isLogSteps() {
			.addSteps(newSteps("", StatesDiff(, .activeStates),
				StepSet, 0)...)
			.addSteps(newSteps("", StatesDiff(.activeStates, ),
				StepRemove, 0)...)
		}

		return 
	}

	return nil
}

func ( *Transition) () {
	.cacheTargetStates.Store(nil)
	.cacheStatesBefore.Store(nil)
	.latestHandlerToState = ""
	.latestHandlerIsEnter = false
	.latestHandlerIsFinal = false
	.cacheClockBefore.Store(nil)
	.Mutation.cacheCalled.Store(nil)
	.cacheSchema = nil
}

// StatesBefore is a list of states before the transition.
func ( *Transition) () S {
	// cache
	if  := .cacheStatesBefore.Load();  != nil {
		return *
	}

	// TODO should preserve order?
	 := make(S, 0, len(.TimeBefore))
	 := .Machine
	if  == nil {
		return 
	}
	 := .StateNames()
	for  := range .TimeBefore {
		if IsActiveTick(.TimeBefore[]) {
			 = append(, [])
		}
	}

	return 
}

// TargetStates is a list of states after parsing the relations.
func ( *Transition) () S {
	// cache
	if  := .cacheTargetStates.Load();  != nil {
		return *
	}

	 := make(S, len(.TargetIndexes))
	 := .MachApi
	if  == nil {
		return nil
	}
	 := .StateNames()
	for ,  := range .TargetIndexes {
		if  == -1 {
			// disposed
			return S{}
		}
		[] = []
	}

	return 
}

// IsAuto returns true if the transition was triggered by an auto state.
// Thus, it cant trigger any other auto state mutations.
func ( *Transition) () bool {
	return .Mutation.IsAuto
}

// IsHealth returns true if the transition was health-related (StateHealthcheck,
// StateHeartbeat).
func ( *Transition) () bool {
	 := .CalledStates()

	if len() != 1 || .Type() != MutationAdd {
		return false
	}

	return [0] == StateHealthcheck || [0] == StateHeartbeat
}

// CalledStates return explicitly called / requested states of the transition.
func ( *Transition) () S {
	// cache
	if  := .Mutation.cacheCalled.Load();  != nil {
		return *
	}

	return IndexToStates(.MachApi.StateNames(), .Mutation.Called)
}

// TimeIndexAfter return TimeAfter bound to an index, for easy quering.
func ( *Transition) () TimeIndex {
	return TimeIndex{
		Time:  .TimeAfter,
		Index: .MachApi.StateNames(),
	}
}

// ClockBefore return the Clock from before the transition.
func ( *Transition) () Clock {
	// cache
	if  := .cacheClockBefore.Load();  != nil {
		return *
	}

	 := .Machine.StateNames()
	 := make(Clock, len())
	for ,  := range .TimeBefore {
		[[]] = 
	}

	return 
}

// ClockAfter return the Clock from before the transition.
func ( *Transition) () Clock {
	 := .Machine.StateNames()
	 := make(Clock, len())
	for ,  := range .TimeAfter {
		[[]] = 
	}

	return 
}

// Args returns the argument map passed to the mutation method
// (or an empty one).
func ( *Transition) () A {
	return .Mutation.Args
}

// Type returns the type of the mutation (add, remove, set).
func ( *Transition) () MutationType {
	return .Mutation.Type
}

// String representation of the transition and the steps taken so far.
func ( *Transition) () string {
	 := .Machine.StateNames()
	var  []string
	for ,  := range .Steps {
		 = append(, .StringFromIndex())
	}

	 := strings.Join(, "\n- ")
	if  != "" {
		 = "\n- " + 
	}

	 := ""
	 := .Mutation.Source
	if  != nil {
		 = fmt.Sprintf("\nsource: [%s](%s/%s/t%d)", .TxId,
			.MachId, .TxId, .MachTime)
	}

	return fmt.Sprintf("tx#%s\n%s%s%s", .Id, .Mutation.StringFromIndex(),
		, )
}

func ( *Transition) ( ...*Step) {
	.Steps = append(.Steps, ...)
}

func ( *Transition) () {
	 := .Machine

	// collect the exit handlers
	 := .TargetStates()
	 := StatesDiff(.activeStates, )
	.resolver.SortStates()

	// collect the enters handlers
	var  S
	for ,  := range  {
		// enter activate state only for multi states called directly
		 := .schema[]
		if !.is(S{}) || (.Multi && slices.Contains(.CalledStates(), )) {
			 = append(, )
		}
	}

	// save
	.Exits = 
	.Enters = 
}

func ( *Transition) () Result {
	 := .Machine
	 := Executed
	var  bool
	for ,  := range .CalledStates() {
		// only the active states
		if !.Machine.Is(S{}) {
			continue
		}

		 :=  + 
		.latestHandlerToState = 
		,  = .handle(, .Mutation.Args, false, false, true)
		if  && .isLogSteps() {
			 := newStep("", , StepHandler, 0)
			.IsSelf = true
			.addSteps()
		}
		if  == Canceled {
			break
		}
	}

	return 
}

func ( *Transition) () Result {
	for ,  := range .Enters {
		 := .Mutation.Args

		// FooEnter
		 := .emitHandler("", , false, true, +SuffixEnter, )
		if  == Canceled {
			if .IsAuto() {
				// partial auto state acceptance
				 := .TargetStates()
				 := slices.Index(, )
				.TargetIndexes = slices.Delete(.TargetIndexes, , +1)
				 = slices.Delete(, , +1)
				.cacheTargetStates.Store(&)
			} else {
				return 
			}
		}
	}

	return Executed
}

func ( *Transition) () Result {
	for ,  := range .Exits {
		// FooExit
		 := .emitHandler(, "", false, false, +SuffixExit,
			.Mutation.Args)
		if  == Canceled {
			if .IsAuto() {
				// partial auto state acceptance
				 := .TargetStates()
				 := slices.Index(, )
				.TargetIndexes = slices.Delete(.TargetIndexes, , +1)
				 = slices.Delete(, , +1)
				.cacheTargetStates.Store(&)
			} else {
				return 
			}
		}
	}

	return Executed
}

func ( *Transition) (
	,  string, ,  bool,  string,  A,
) Result {
	.latestHandlerToState = 
	,  := .Machine.handle(, , , , false)

	if  && .Machine.semLogger.IsSteps() {
		 := newStep(, , StepHandler, 0)
		.IsFinal = 
		.IsEnter = 
		.addSteps()
	}
	return 
}

func ( *Transition) () Result {
	 := slices.Concat(.Exits, .Enters)

	for ,  := range  {
		 := slices.Contains(.Enters, )

		var  string
		if  {
			 =  + SuffixState
			.latestHandlerToState = 
		} else {
			 =  + SuffixEnd
			.latestHandlerToState = ""
		}

		,  := .Machine.handle(, .Mutation.Args,
			true, , false)

		if  && .Machine.semLogger.IsSteps() {
			 := newStep("", , StepHandler, 0)
			.IsFinal = true
			.IsEnter = 
			.addSteps()
		}

		// final handler cancel means timeout
		if  == Canceled {
			return 
		}
	}

	return Executed
}

func ( *Transition) () Result {
	 := .StatesBefore()
	 := .TargetStates()
	var  S

	for  := range  {
		for  := range  {
			if [] == [] {
				continue
			}

			 := [] + []
			.latestHandlerToState = ""
			,  := .Machine.handle(, .Mutation.Args, false,
				false, false)

			if  && .Machine.semLogger.IsSteps() {
				 := newStep([], [], StepHandler, 0)
				.addSteps()
			}

			if  != Canceled {
				continue
			}

			// negotiation canceled
			if .IsAuto() {
				if  == nil {
					 = slices.Clone()
				}

				// partial auto state acceptance
				 := slices.Index(, [])
				if  == -1 {
					// already removed
					continue
				}
				.TargetIndexes = slices.Delete(.TargetIndexes, , +1)

				// update cache
				 = slices.Delete(, , +1)
				.cacheTargetStates.Store(&)

			} else {
				return 
			}
		}
	}

	return Executed
}

func ( *Transition) () Result {
	 := .Machine
	 := Executed
	if !.IsAccepted.Load() {
		 = Canceled
	}
	 := false
	 := .CalledStates()
	 := .Machine.handlerLoopRunning.Load()
	 := .semLogger.Level() == LogEverything

	// tracers
	.tracersMx.RLock()
	for  := 0; !.Machine.IsDisposed() &&  < len(.Machine.tracers); ++ {
		.Machine.tracers[].TransitionStart()
	}
	.tracersMx.RUnlock()

	// NEGOTIATION CALLS PHASE (cancellable)

	if  ||  {

		// FooExit handlers
		if  != Canceled {
			 = .emitExitEvents()
		}

		// FooEnter handlers
		if  != Canceled {
			 = .emitEnterEvents()
		}

		// FooFoo handlers
		if  != Canceled && .Type() != MutationRemove {
			 = .emitSelfEvents()
		}

		// BarFoo
		if  != Canceled {
			 = .emitStateStateEvents()
		}

		// none of the auto states has been accepted, so cancel
		if .IsAuto() && len(.TargetIndexes) == 0 {
			 = Canceled
		}

		// global AnyEnter handler
		if  != Canceled {
			 = .emitHandler(StateAny, StateAny, false, true,
				StateAny+SuffixEnter, .Mutation.Args)
		}
	}

	// skip for CanAdd and CanRemove
	if !.Mutation.IsCheck {
		// TODO extract

		// recheck auto txs for canceled handlers, remove direct and transitive Add
		// relation
		if .IsAuto() {
			 := StatesDiff(, .TargetStates())
			 := StatesDiff(, )
			 := .statesToSet(MutationAdd, )
			 := .resolver.TargetStates(, , .StateNames())
			.TargetIndexes = .Index()
			.cacheTargetStates.Store(&)
			// TODO cache states before?
		}

		// FINAL HANDLERS (non cancellable)
		if  != Canceled {

			// mutate the clocks
			.activeStatesMx.Lock()
			.setActiveStates(, .TargetStates(), .IsAuto())
			// gather new clock values, overwrite fake TimeAfter
			.activeStatesMx.Unlock()

			// always correct TimeAfter
			.TimeAfter = .time(nil)

			if  ||  || .subs.HasWhenArgs() {
				// FooState
				// FooEnd
				 = .emitFinalEvents()
			}

			// handle timeouts in final handlers the same as a panic
			// TODO test
			if  == Canceled {
				.recoverFinalPhase()
				// TODO safe to continue?
			}

			 = !.IsTime(.TimeBefore, nil)
		} else {
			// always correct TimeAfter
			.TimeAfter = .time(nil)
		}

		// global AnyState handler
		if  != Canceled && ( || ) {
			 = .emitHandler(StateAny, StateAny, true, true,
				StateAny+SuffixState, .Mutation.Args)
		}

		// basic OnChange handler
		if  := .onChange.Load();  != nil {
			(*)(, .TimeBefore, .TimeAfter)
		}

		// AUTO STATES
		if  == Canceled {
			.IsAccepted.Store(false)
		} else if !.disposing.Load() &&  && !.IsAuto() &&
			!.IsHealth() {

			,  := .resolver.NewAutoMutation()
			if  != nil {
				.log(LogOps, "[auto] %s", j())
				.PrependMut()
			}
		}

		// cache for subscriptions, mind partially accepted auto states
		if .IsAuto() {
			 := .StatesBefore()
			.cacheActivated = StatesDiff(.activeStates, )
			.cacheDeactivated = StatesDiff(, .activeStates)
		} else {
			.cacheActivated = .Enters
			.cacheDeactivated = .Exits
		}
	} else if  == Canceled {
		.IsAccepted.Store(false)
	}

	// collect previous log entries
	.logEntriesLock.Lock()
	.PreLogEntries = .logEntries
	.QueueLen = uint16(.queueLen.Load())
	.logEntries = nil
	.logEntriesLock.Unlock()
	.IsCompleted.Store(true)

	// tracers
	.tracersMx.RLock()
	for  := 0; !.Machine.IsDisposed() &&  < len(.Machine.tracers); ++ {
		.Machine.tracers[].TransitionEnd()
	}
	.tracersMx.RUnlock()

	if  == Canceled {
		return Canceled
	} else if .Mutation.IsCheck {
		return 
	}

	// TODO optimize: remove checks?
	if .Type() == MutationRemove {
		if .Not() {
			return Executed
		} else {
			// TODO error?
			return Canceled
		}
	} else {
		if .Is(.TargetStates()) {
			return Executed
		} else {
			// TODO error?
			return Canceled
		}
	}
}

func ( *Transition) () {
	 := .Machine
	// Dropping states doesn't require an acceptance
	if .Type() == MutationRemove {
		return
	}
	 := .CalledStates()
	 := StatesDiff(, .TargetStates())
	// Auto-states can be set partially
	if .IsAuto() {
		// partially accepted
		if len() < len() {
			return
		}
		// all rejected, reject the whole transition
		.IsAccepted.Store(false)
	}

	if len() <= 0 {
		return
	}

	// accept if check and one of called is multi
	 := false
	for ,  := range  {
		if .schema[].Multi {
			 = true
			break
		}
	}
	if .Mutation.IsCheck &&  {
		return
	}

	// no accepted

	.IsAccepted.Store(false)
	.log(LogOps, "[cancel:reject] %s", j())
	if .isLogSteps() {
		// TODO optimize: stop early on cancel
		.addSteps(newSteps("", , StepCancel, 0)...)
	}
}

func ( *Transition) () bool {
	 := .Machine.semLogger
	if .Mutation.IsCheck && !.IsCan() {
		return false
	}

	return .IsSteps()
}