package frostdb

import (
	
	
	
	

	
	
	

	am 
)

// type Machine struct {
// 	Id        string
// 	CreatedAt time.Time
// 	UpdatedAt time.Time
// 	Time      *datatypes.JSON
// 	TimeSum   uint64
// }

type Time struct {
	Id uint64

	// special fields

	MutType      uint64
	TimeSum      uint64
	TimeDiff     uint64
	TransitionId string

	// state ticks

	States map[string]uint64 `frostdb:",rle_dict,asc(1),null_first"`
}

type Transition struct {
	Id string

	SourceTx   string
	SourceMach string
	IsAuto     bool
	IsAccepted bool
	IsCheck    bool
	IsBroken   bool
	QueueLen   uint16

	// normal queue props
	QueuedAt   uint64
	ExecutedAt uint64

	// extra
	Called    []uint64
	Arguments map[string]string
}

type Memory struct {
	Store *frostdb.ColumnStore
	Db    *frostdb.DB
}

func ( context.Context) (*Memory, error) {
	,  := frostdb.New(frostdb.WithWAL(), frostdb.WithStoragePath("."))
	if  != nil {
		return nil, 
	}
	,  := .DB(, "amhist")
	if  != nil {
		return nil, 
	}

	return &Memory{
		Db:    ,
		Store: ,
	}, nil
}

func ( *Memory) () error {
	return .Db.Close()
}

// TODO better params
func ( *Memory) (
	 func( error),  *am.Machine, ,
	 am.S,  int,
) (*Tracer, error) {

	 := &Tracer{
		TracerNoOp:       &am.TracerNoOp{},
		loop:             &errgroup.Group{},
		db:               .Db,
		lastActivated:    make(map[string]time.Time),
		calledAllowlist:  ,
		changedAllowlist: ,
		maxEntries:       ,
		onErr:            ,
		tTimes:           make(map[string]*frostdb.GenericTable[*Time]),
		tTransitions:     make(map[string]*frostdb.GenericTable[*Transition]),
	}
	.loop.SetLimit(1)

	.MachineInit()

	return , .BindTracer()
}

type Tracer struct {
	*am.TracerNoOp

	StoreChecks bool
	Active      atomic.Int32

	db *frostdb.DB
	// LastActivated is a map of state names to the last time they were activated
	// TODO keep?
	lastActivated map[string]time.Time
	// limits tracked states
	calledAllowlist am.S
	// limits tracked states
	changedAllowlist am.S
	maxEntries       int
	loop             *errgroup.Group
	onErr            func(err error)

	// tables

	// tMachines    *frostdb.GenericTable[Machine]
	tTimes       map[string]*frostdb.GenericTable[*Time]
	tTransitions map[string]*frostdb.GenericTable[*Transition]
}

func ( *Tracer) ( *am.Transition) {
	if .Mutation.IsCheck && !.StoreChecks {
		return
	}

	 := .Machine
	 := .CalledStates()

	// TODO optional
	// TODO track both called and changed
	 := true
	for ,  := range .calledAllowlist {
		if slices.Contains(, ) {
			 = true
			break
		}
	}
	if ! {
		return
	}

	 := make([]uint64, len())
	for ,  := range .Mutation.Called {
		[] = uint64()
	}

	// time and tx records
	 := &Time{
		MutType:      uint64(.Mutation.Type),
		TimeSum:      .Machine.Time(nil).Sum(nil),
		TimeDiff:     .TimeAfter.Sum(nil) - .TimeBefore.Sum(nil),
		TransitionId: .Id,
		States:       make(map[string]uint64, len(ssB.Names())),
	}
	 := &Transition{
		Id:         .Id,
		IsAuto:     .Mutation.IsAuto,
		IsAccepted: .IsAccepted.Load(),
		IsCheck:    .Mutation.IsCheck,
		IsBroken:   .IsBroken.Load(),
		QueuedAt:   .Mutation.QueueTick,
		Called:     ,
		Arguments:  .Mutation.MapArgs(.SemLogger().ArgsMapper()),
		QueueLen:   .QueueLen(),
	}

	// optional fields
	if .Mutation.QueueTick > 0 {
		 := .QueueTick()
		.ExecutedAt = 
	}
	if .Mutation.Source != nil {
		.SourceTx = .Mutation.Source.TxId
		.SourceMach = .Mutation.Source.MachId
	}

	// collect state ticks
	for ,  := range ssB.Names() {
		.States[] = .Machine.Tick()
	}

	// fork
	.Active.Add(1)
	go .loop.Go(func() error {
		// create
		// err := gorm.G[TimeMyMach1](t.db).Create(mach.Ctx(), record)
		// if err != nil {
		// 	t.onErr(err)
		// }
		,  := .tTransitions[.Id()].Write(.Ctx(), )
		if  != nil {
			.onErr()
			return 
		}
		_,  = .tTimes[.Id()].Write(.Ctx(), )
		if  != nil {
			.onErr()
			return 
		}

		.Active.Add(-1)

		return nil
	})
}

func ( *Tracer) ( am.Api) context.Context {

	// TODO append new machine state?
	// machine := Machine{
	// 	Id:        mach.Id(),
	// 	CreatedAt: time.Now(),
	// 	UpdatedAt: time.Now(),
	// }
	//
	// // machines
	// var err error
	// t.tMachines, err = frostdb.NewGenericTable[Machine](
	// 	t.db, "machines", memory.DefaultAllocator,
	// )
	// if err != nil {
	// 	t.onErr(err)
	// 	return nil
	// }

	// times
	,  := frostdb.NewGenericTable[*Time](
		.db, "times"+.Id(), memory.DefaultAllocator,
	)
	if  != nil {
		.onErr()
		return nil
	}
	.tTimes[.Id()] = 

	// transitions
	,  := frostdb.NewGenericTable[*Transition](
		.db, "transitions"+.Id(), memory.DefaultAllocator,
	)
	if  != nil {
		.onErr()
		return nil
	}
	.tTransitions[.Id()] = 

	return nil
}

// MOCKS TODO import from basic

// BasicStatesDef contains all the basic states.
type BasicStatesDef struct {
	*am.StatesBase

	// ErrNetwork indicates a generic network error.
	ErrNetwork string
	// ErrHandlerTimeout indicates one of the state machine handlers has timed
	// out.
	ErrHandlerTimeout string

	// Start indicates the machine should be working. Removing start can force
	// stop the machine.
	Start string
	// Ready indicates the machine meets criteria to perform work.
	Ready string
	// Healthcheck is a periodic request making sure that the machine is still
	// alive.
	Healthcheck string
	// Heartbeat is a periodic state that ensures the integrity of the machine.
	Heartbeat string
}

var BasicSchema = am.Schema{
	// Errors

	ssB.Exception: {Multi: true},
	ssB.ErrNetwork: {
		Multi:   true,
		Require: am.S{am.StateException},
	},
	ssB.ErrHandlerTimeout: {
		Multi:   true,
		Require: am.S{am.StateException},
	},

	// Basics

	ssB.Start:       {},
	ssB.Ready:       {Require: am.S{ssB.Start}},
	ssB.Healthcheck: {Multi: true},
	ssB.Heartbeat:   {},
}

// EXPORTS AND GROUPS

var (
	ssB = am.NewStates(BasicStatesDef{})

	// BasicStates contains all the states for the Basic machine.
	BasicStates = ssB
)