package machine

import (
	
	
	
	
	
	
	
	
	
	
	
)

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

// ///// TIME

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

// Time

// Time is machine time, an ordered list of state ticks. It's like Clock, but
// indexed by int, instead of string.
// TODO use math/big?
type Time []uint64

// Increment adds 1 to a state's tick value
func ( Time) ( int) Time {
	 := make(Time, len())
	copy(, )
	if  < len() {
		[]++
	}
	return 
}

// String returns an integer representation of the time slice.
func ( Time) () string {
	 := ""
	for ,  := range  {
		if  != "" {
			 += " "
		}
		 += strconv.Itoa(int())
	}

	return 
}

// Add sums 2 instances of Time and returns a new one.
func ( Time) ( Time) Time {
	 := make(Time, len())
	if len() != len() {
		return 
	}

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

	return 
}

// ToIndex returns a string-indexed version of Time.
func ( Time) ( S) *TimeIndex {
	return &TimeIndex{
		Time:  ,
		Index: ,
	}
}

// Filter returns a subset of the Time slice for the given state indexes.
// It's not advised to slice an already sliced time slice.
func ( Time) ( []int) Time {
	 := make(Time, len())
	for ,  := range  {
		if  >= len() {
			continue
		}
		[] = []
	}

	return 
}

// Sum returns a sum of ticks for each state in Time, or narrowed down to
// [idxs].
func ( Time) ( []int) uint64 {
	// total sum
	if  == nil {
		var  uint64
		for ,  := range  {
			 += 
		}
		return 
	}

	// selective sum
	var  uint64
	for ,  := range  {
		if  >= len() {
			continue
		}
		 += []
	}

	return 
}

// DiffSince returns the number of ticks for each state in Time since the
// passed machine time.
func ( Time) ( Time) Time {
	 := make(Time, len())
	if len() != len() {
		return 
	}

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

	return 
}

// NonZeroStates returns a list of state indexes with non-zero ticks in this
// machine time slice.
func ( Time) () []int {
	 := make([]int, 0, len())

	for ,  := range  {
		if  != 0 {
			 = append(, )
		}
	}

	return 
}

// After returns true if at least 1 tick in time1 is after time2, optionally
// accepting equal values for true. Requires a deterministic states
// order, eg by using [Machine.VerifyStates].
func ( Time) ( bool,  Time) bool {
	 := len()
	for ,  := range  {
		// shorter time2 cant be after time1
		if  <=  {
			break
		}
		if  < [] || ( == [] && !) {
			return false
		}
	}

	return true
}

// Before returns true if at least 1 tick in time1 is before time2, optionally
// accepting equal values for true. Requires a deterministic states
// order, eg by using [Machine.VerifyStates].
func ( Time) ( bool,  Time) bool {
	 := len()
	for ,  := range  {
		// shorter time2 cant be after time1
		if  <=  {
			break
		}
		if  > [] || ( == [] && !) {
			return false
		}
	}

	return true
}

// Equal checks if time1 is equal to time2. Requires a deterministic states
// order, eg by using [Machine.VerifyStates].
func ( Time) ( bool,  Time) bool {
	if  && len() != len() {
		return false
	}

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

	return true
}

// Time - state checking

// Tick is [Machine.Tick] but for an int-based time slice.
func ( Time) ( int) uint64 {
	// out of bound falls back to 0
	if len() <=  {
		return 0
	}

	return []
}

// Is1 is [Machine.Is1] but for an int-based time slice.
func ( Time) ( int) bool {
	if  == -1 ||  >= len() {
		return false
	}
	return IsActiveTick([])
}

// Is is [Machine.Is] but for an int-based time slice.
func ( Time) ( []int) bool {
	if len() == 0 {
		return false
	}

	for ,  := range  {
		// -1 is not found or mach disposed
		if  == -1 {
			return false
		}
		if !IsActiveTick([]) {
			return false
		}
	}

	return true
}

// Not is [Machine.Not] but for an int-based time slice.
func ( Time) ( []int) bool {
	if len() == 0 {
		return true
	}

	for ,  := range  {
		// -1 is not found or mach disposed
		if  != -1 && IsActiveTick([]) {
			return false
		}
	}

	return true
}

// Not1 is [Machine.Not1] but for an int-based time slice.
func ( Time) ( int) bool {
	if  == -1 ||  >= len() {
		return false
	}

	return !IsActiveTick([])
}

// Any is [Machine.Any] but for an int-based time slice.
func ( Time) ( ...[]int) bool {
	for ,  := range  {
		if .Is() {
			return true
		}
	}

	return false
}

// Any1 is [Machine.Any1] but for an int-based time slice.
func ( Time) ( ...int) bool {
	for ,  := range  {
		if .Is1() {
			return true
		}
	}

	return false
}

// ActiveStates returns a list of active state indexes in this machine time
// slice. When idxs isn't nil, only the passed indexes are considered.
func ( Time) ( []int) []int {
	 := make([]int, 0, len())
	for ,  := range  {
		if !IsActiveTick() {
			continue
		}
		 = append(, )
	}

	return 
}

// TimeIndex

// TimeIndex is [Time] with a bound state index (list of state names). It's not
// suitable for storage, use [Time] instead. See [Clock] for a simpler type with
// ticks indexes by state names.
type TimeIndex struct {
	Time
	Index S
}

func ( S,  []int) *TimeIndex {
	 := &TimeIndex{
		Index: ,
		Time:  make(Time, len()),
	}
	for ,  := range  {
		.Time[] = 1
	}

	return 
}

// String returns a string representation of the time slice.
func ( TimeIndex) () string {
	return j(.ActiveStates(nil))
}

// StateName returns the name of the state at the given index.
func ( TimeIndex) ( int) string {
	if  >= len(.Index) {
		return ""
	}

	return .Index[]
}

// Sum is [Time.Sum] but for a sting-based time slice.
func ( TimeIndex) ( S) uint64 {
	return .Time.Sum(StatesToIndex(.Index, ))
}

// Filter is [Time.Filter] but for a sting-based time slice.
func ( TimeIndex) ( S) *TimeIndex {
	return .Time.Filter(StatesToIndex(.Index, )).ToIndex()
}

// NonZeroStates is [Time.NonZeroStates] but for a sting-based time slice.
func ( TimeIndex) () S {
	return IndexToStates(.Index, .Time.NonZeroStates())
}

// TimeIndex - state checking

// Is is [Machine.Is] but for a sting-based time slice.
func ( TimeIndex) ( S) bool {
	return .Time.Is(StatesToIndex(.Index, ))
}

// Is1 is [Machine.Is1] but for a sting-based time slice.
func ( TimeIndex) ( string) bool {
	return .Time.Is(StatesToIndex(.Index, S{}))
}

// Not is [Machine.Not] but for a sting-based time slice.
func ( TimeIndex) ( S) bool {
	return .Time.Not(StatesToIndex(.Index, ))
}

// Not1 is [Machine.Not1] but for a sting-based time slice.
func ( TimeIndex) ( string) bool {
	return .Time.Not(StatesToIndex(.Index, S{}))
}

// Any is [Machine.Any] but for a sting-based time slice.
func ( TimeIndex) ( ...string) bool {
	 := make([][]int, len())
	for ,  := range  {
		[] = StatesToIndex(.Index, S{})
	}

	return .Time.Any(...)
}

// Any1 is [Machine.Any1] but for a sting-based time slice.
func ( TimeIndex) ( ...string) bool {
	return .Time.Any1(StatesToIndex(.Index, )...)
}

// ActiveStates is [Machine.ActiveStates] but for a sting-based time slice.
func ( TimeIndex) ( S) S {
	 := S{}
	for ,  := range .Time {
		if !IsActiveTick() {
			continue
		}
		 := "unknown" + strconv.Itoa()
		if len(.Index) >  {
			 = .Index[]
		}
		if  != nil && !slices.Contains(, ) {
			continue
		}

		 = append(, )
	}

	return 
}

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

// ///// LOGGING, TRACING

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

// Context

type (
	CtxKeyName struct{}
	CtxValue   struct {
		Id    string
		State string
		Tick  uint64
	}
)

var CtxKey = &CtxKeyName{}

// LoggerFn is a logging function for the machine.
type LoggerFn func(level LogLevel, msg string, args ...any)

// LogArgsMapperFn is a function that maps arguments to be logged. Useful for
// debugging. See NewArgsMapper.
type LogArgsMapperFn func(args A) map[string]string

type LogEntry struct {
	Level LogLevel
	Text  string
}

// LogLevel enum
type LogLevel int

const (
	// LogNothing means no logging, including external msgs.
	LogNothing LogLevel = iota
	// LogExternal will show ony external user msgs.
	LogExternal
	// LogChanges means logging state changes and external msgs.
	LogChanges
	// LogOps means LogChanges + logging all the operations.
	LogOps
	// LogDecisions means LogOps + logging all the decisions behind them.
	LogDecisions
	// LogEverything means LogDecisions + all event and handler names, and more.
	LogEverything
)

func ( LogLevel) () string {
	switch  {
	case LogNothing:
		fallthrough
	default:
		return "nothing"
	case LogExternal:
		return "external"
	case LogChanges:
		return "changes"
	case LogOps:
		return "ops"
	case LogDecisions:
		return "decisions"
	case LogEverything:
		return "everything"
	}
}

// SemLogger is a semantic logger for structured events. It's consist of:
// - enable / enabled methods
// - text logger utils
// - setters for external semantics (eg pipes)
// It's WIP, and eventually it will replace (but not remove) the text logger.
type SemLogger interface {
	// TODO implement empty methods
	// TODO add SetTag, RemoveTag, JoinTopic, LeaveTopic, custom graph
	//  links / edges

	// graph

	// AddPipeOut informs that [sourceState] has been piped out into [targetMach].
	// The name of the target state is unknown.
	AddPipeOut(addMut bool, sourceState, targetMach string)
	// AddPipeIn informs that [targetState] has been piped into this machine from
	// [sourceMach]. The name of the source state is unknown.
	AddPipeIn(addMut bool, targetState, sourceMach string)
	// RemovePipes removes all pipes for the passed machine ID.
	RemovePipes(machId string)

	// details

	// IsCan return true when the machine is logging Can* methods.
	IsCan() bool
	// EnableCan enables / disables logging of Can* methods.
	EnableCan(enable bool)
	// IsSteps return true when the machine is logging transition steps.
	IsSteps() bool
	// EnableSteps enables / disables logging of transition steps.
	EnableSteps(enable bool)
	// IsGraph returns true when the machine is logging graph structures.
	IsGraph() bool
	// EnableGraph enables / disables logging of graph structures.
	EnableGraph(enable bool)
	// EnableId enables or disables the logging of the machine's ID in log
	// messages.
	EnableId(val bool)
	// IsId returns true when the machine is logging the machine's ID in log
	// messages.
	IsId() bool
	// EnableQueued enables or disables the logging of queued mutations.
	EnableQueued(val bool)
	// IsQueued returns true when the machine is logging queued mutations.
	IsQueued() bool
	// EnableStateCtx enables or disables the logging of active state contexts.
	EnableStateCtx(val bool)
	// IsStateCtx returns true when the machine is logging active state contexts.
	IsStateCtx() bool
	// EnableWhen enables or disables the logging of "when" methods.
	EnableWhen(val bool)
	// IsWhen returns true when the machine is logging "when" methods.
	IsWhen() bool
	// EnableArgs enables or disables the logging known args.
	EnableArgs(val bool)
	// IsArgs returns true when the machine is logging known args.
	IsArgs() bool

	// logger

	// SetLogger sets a custom logger function.
	SetLogger(logger LoggerFn)
	// Logger returns the current custom logger function or nil.
	Logger() LoggerFn
	// SetLevel sets the log level of the machine.
	SetLevel(lvl LogLevel)
	// Level returns the log level of the machine.
	Level() LogLevel
	// SetEmpty creates an empty logger that does nothing and sets the log
	// level in one call. Useful when combined with am-dbg. Requires LogChanges
	// log level to produce any output.
	SetEmpty(lvl LogLevel)
	// SetSimple takes log.Printf and sets the log level in one
	// call. Useful for testing. Requires LogChanges log level to produce any
	// output.
	SetSimple(logf func(format string, args ...any), level LogLevel)
	// SetArgsMapper accepts a function which decides which mutation arguments
	// to log. See NewArgsMapper or create your own manually.
	SetArgsMapper(mapper LogArgsMapperFn)
	// ArgsMapper returns the current log args mapper function.
	ArgsMapper() LogArgsMapperFn
}

// SemConfig defines a config for SemLogger.
type SemConfig struct {
	// TODO
	Full     bool
	Steps    bool
	Graph    bool
	Can      bool
	Queued   bool
	Args     bool
	When     bool
	StateCtx bool
}

type semLogger struct {
	mach   *Machine
	steps  atomic.Bool
	graph  atomic.Bool
	queued atomic.Bool
	args   atomic.Bool
	can    atomic.Bool
}

// implement [SemLogger]
var _ SemLogger = &semLogger{}

func ( *semLogger) ( LogArgsMapperFn) {
	.mach.logArgs.Store(&)
}

func ( *semLogger) () LogArgsMapperFn {
	 := .mach.logArgs.Load()
	if  == nil {
		return nil
	}
	return *
}

func ( *semLogger) ( bool) {
	.mach.logId.Store()
}

func ( *semLogger) () bool {
	return .mach.logId.Load()
}

func ( *semLogger) ( LoggerFn) {
	if  == nil {
		.mach.logger.Store(nil)

		return
	}
	.mach.logger.Store(&)
}

func ( *semLogger) () LoggerFn {
	if  := .mach.logger.Load();  != nil {
		return *
	}

	return nil
}

func ( *semLogger) ( LogLevel) {
	.mach.logLevel.Store(&)
}

func ( *semLogger) () LogLevel {
	return *.mach.logLevel.Load()
}

func ( *semLogger) ( LogLevel) {
	var  LoggerFn = func( LogLevel,  string,  ...any) {
		// no-op
	}
	.mach.logger.Store(&)
	.mach.logLevel.Store(&)
}

func ( *semLogger) (
	 func( string,  ...any),  LogLevel,
) {
	if  == nil {
		panic("logf cannot be nil")
	}

	var  LoggerFn = func( LogLevel,  string,  ...any) {
		(, ...)
	}
	.mach.logger.Store(&)
	.mach.logLevel.Store(&)
}

func ( *semLogger) ( bool, ,  string) {
	 := "remove"
	if  {
		 = "add"
	}
	.mach.log(LogOps, "[pipe-out:%s] %s to %s", , ,
		)
}

func ( *semLogger) ( bool, ,  string) {
	 := "remove"
	if  {
		 = "add"
	}
	.mach.log(LogOps, "[pipe-in:%s] %s from %s", , ,
		)
}

func ( *semLogger) ( string) {
	.mach.log(LogOps, "[pipe:gc] %s", )
}

func ( *semLogger) () bool {
	return .steps.Load()
}

func ( *semLogger) ( bool) {
	.steps.Store()
}

func ( *semLogger) () bool {
	return .can.Load()
}

func ( *semLogger) ( bool) {
	.can.Store()
}

func ( *semLogger) () bool {
	return .graph.Load()
}

func ( *semLogger) ( bool) {
	.graph.Store()
}

func ( *semLogger) () bool {
	return .queued.Load()
}

func ( *semLogger) ( bool) {
	.queued.Store()
}

func ( *semLogger) () bool {
	return .args.Load()
}

func ( *semLogger) ( bool) {
	.args.Store()
}

// TODO more data types

func ( *semLogger) ( bool) {
	// TODO
}

func ( *semLogger) () bool {
	return false
}

func ( *semLogger) () bool {
	return false
}

func ( *semLogger) ( bool) {
	// TODO
}

// LogArgs is a list of common argument names to be logged. Useful for
// debugging.
var LogArgs = []string{"name", "id", "port", "addr", "err"}

// LogArgsMaxLen is the default maximum length of the arg's string
// representation.
var LogArgsMaxLen = 20

// NewArgsMapper returns a matcher function for LogArgs. Useful for debugging
// untyped argument maps.
//
// maxLen: maximum length of the arg's string representation). Default to
// LogArgsMaxLen,
func ( []string,  int) func( A) map[string]string {
	if  == 0 {
		 = LogArgsMaxLen
	}

	return func( A) map[string]string {
		 := make([]bool, len())
		 := 0
		for ,  := range  {
			,  := []
			[] = 
			++
		}
		if  == 0 {
			return nil
		}
		 := make(map[string]string)
		for ,  := range  {
			if ![] {
				continue
			}
			[] = TruncateStr(fmt.Sprintf("%v", []), )
		}

		return 
	}
}

func ( map[string]string) string {
	if len() == 0 {
		return ""
	}

	// sort by name
	var  []string
	 := slices.Collect(maps.Keys())
	slices.Sort()
	for ,  := range  {
		 := []
		 = append(, +"="+)
	}

	return " (" + strings.Join(, " ") + ")"
}

// Tracer is an interface for logging machine transitions and events, used by
// Opts.Tracers and Machine.BindTracer.
type Tracer interface {
	TransitionInit(transition *Transition)
	TransitionStart(transition *Transition)
	TransitionEnd(transition *Transition)
	MutationQueued(machine Api, mutation *Mutation)
	HandlerStart(transition *Transition, emitter string, handler string)
	HandlerEnd(transition *Transition, emitter string, handler string)
	// MachineInit is called only for machines with tracers added via
	// Opts.Tracers.
	MachineInit(machine Api) context.Context
	MachineDispose(machID string)
	NewSubmachine(parent, machine Api)
	Inheritable() bool
	QueueEnd(machine Api)
	SchemaChange(machine Api, old Schema)
	VerifyStates(machine Api)
}

// NoOpTracer is a no-op implementation of Tracer, used for embedding.
// TODO rename to TracerNoOp
type NoOpTracer struct{}

func ( *NoOpTracer) ( *Transition)          {}
func ( *NoOpTracer) ( *Transition)         {}
func ( *NoOpTracer) ( *Transition)           {}
func ( *NoOpTracer) ( Api,  *Mutation) {}
func ( *NoOpTracer) (
	 *Transition,  string,  string) {
}

func ( *NoOpTracer) (
	 *Transition,  string,  string) {
}

func ( *NoOpTracer) ( Api) context.Context {
	return nil
}
func ( *NoOpTracer) ( string)      {}
func ( *NoOpTracer) (,  Api) {}
func ( *NoOpTracer) ( Api)              {}

func ( *NoOpTracer) ( Api,  Schema) {}
func ( *NoOpTracer) ( Api)             {}
func ( *NoOpTracer) () bool                    { return false }

var _ Tracer = &NoOpTracer{}

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

// ///// EVENTS, WHEN, EMITTERS

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

var emitterNameRe = regexp.MustCompile(`/\w+\.go:\d+`)

// Event struct represents a single event of a Mutation within a Transition.
// One event can have 0-n handlers.
type Event struct {
	// Ctx is an optional context this event is constrained by.
	Ctx context.Context
	// Name of the event / handler
	Name string
	// MachineId is the ID of the parent machine.
	MachineId string
	// TransitionId is the ID of the parent transition.
	TransitionId string
	// Args is a map of named arguments for a Mutation.
	Args A
	// IsCheck is true if this event is a check event, fired by one of Can*()
	// methods. Useful for avoiding flooding the log with errors.
	IsCheck bool

	// Machine is the machine that the event belongs to. It can be used to access
	// the current Transition and Mutation.
	machine *Machine
}

// Mutation returns the Mutation of an Event.
func ( *Event) () *Mutation {
	 := .Machine().Transition()
	if  == nil {
		return nil
	}
	return .Mutation
}

func ( *Event) () *Machine {
	return .machine
}

// Transition returns the Transition of an Event.
func ( *Event) () *Transition {
	if .machine == nil {
		return nil
	}

	return .Machine().t.Load()
}

// IsValid confirm this event should still be processed. Useful for negotiation
// handlers, which can't use state context.
func ( *Event) () bool {
	 := .Transition()
	if  == nil {
		return false
	}
	// optional ctx
	if .Ctx != nil && .Ctx.Err() != nil {
		return false
	}

	return .TransitionId == .Id && !.IsCompleted.Load() &&
		.IsAccepted.Load()
}

// Export clones only the essential data of the Event. Useful for tracing vs GC.
func ( *Event) () *Event {
	 := .MachineId
	if .Machine() == nil {
		 = .Machine().Id()
	}

	return &Event{
		MachineId:    ,
		Name:         .Name,
		TransitionId: .TransitionId,
		IsCheck:      .IsCheck,
	}
}

// Clone clones the event struct, making it writable.
func ( *Event) () *Event {
	 := .Export()

	// non-exportable fields
	.Args = .Args
	.Ctx = .Ctx

	return 
}

// SwapArgs clone the event and assign new args.
func ( *Event) ( A) *Event {
	 := .Clone()
	.Args = 

	return 
}

func ( *Event) () string {
	 := .Machine()
	if  == nil {
		return .Mutation().String()
	}

	return .Mutation().StringFromIndex(.StateNames())
}

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

// ///// ERROR HANDLING

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

// ExceptionArgsPanic is an optional argument ["panic"] for the StateException
// state which describes a panic within a Transition handler.
type ExceptionArgsPanic struct {
	CalledStates S
	StatesBefore S
	Transition   *Transition
	LastStep     *Step
	StackTrace   string
}

// ExceptionHandler provide a basic StateException state support, as should be
// embedded into handler structs in most of the cases. Because ExceptionState
// will be called after [Machine.HandlerDeadline], it should handle locks
// on its own (to not race with itself).
type ExceptionHandler struct{}

type recoveryData struct {
	err   any
	stack string
}

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 
}

// ExceptionState is a final entry handler for the StateException state.
// Args:
// - err error: The error that caused the StateException state.
// - panic *ExceptionArgsPanic: Optional details about the panic.
func ( *ExceptionHandler) ( *Event) {
	// TODO handle ErrHandlerTimeout to ErrHandlerTimeoutState (if present)

	 := ParseArgs(.Args)
	 := .Err
	 := .ErrTrace
	 := .Machine()

	// err
	if  == nil {
		 = errors.New("missing error in args to ExceptionState")
	}
	if .Panic == nil {
		 := strings.TrimSpace(.Error())

		// TODO more mutation info
		if .LogStackTrace &&  != "" {
			.log(LogChanges, "[error] %s\n%s", , )
		} else {
			.log(LogChanges, "[error] %s", )
		}

		return
	}

	// handler panic info
	 := .Panic.Transition.Mutation.Type
	if .LogStackTrace &&  != "" {
		// stack trace
		.log(LogChanges, "[error:%s] %s (%s)\n%s", ,
			j(.Panic.CalledStates), , )
	} else {
		// no stack trace
		.log(LogChanges, "[error:%s] %s (%s)", ,
			j(.Panic.CalledStates), )
	}
}

// NewLastTxTracer returns a Tracer that logs the last transition.
func () *LastTxTracer {
	return &LastTxTracer{}
}

// TODO add TTL, ctx
type LastTxTracer struct {
	*NoOpTracer
	lastTx atomic.Pointer[Transition]
}

func ( *LastTxTracer) ( *Transition) {
	.lastTx.Store()
}

// Load returns the last transition.
func ( *LastTxTracer) () *Transition {
	return .lastTx.Load()
}

func ( *LastTxTracer) () string {
	 := .lastTx.Load()
	if  == nil {
		return ""
	}

	return .String()
}

func newClosedChan() chan struct{} {
	 := make(chan struct{})
	close()
	return 
}