package sqlite3

import (
	"errors"
	"reflect"
)

// CreateModule registers a new virtual table module name.
// If create is nil, the virtual table is eponymous.
//
// https://sqlite.org/c3ref/create_module.html
func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor[T]) error {
	var flags int

	const (
		VTAB_CREATOR     = 0x001
		VTAB_DESTROYER   = 0x002
		VTAB_UPDATER     = 0x004
		VTAB_RENAMER     = 0x008
		VTAB_OVERLOADER  = 0x010
		VTAB_CHECKER     = 0x020
		VTAB_TXN         = 0x040
		VTAB_SAVEPOINTER = 0x080
		VTAB_SHADOWTABS  = 0x100
	)

	if create != nil {
		flags |= VTAB_CREATOR
	}

	vtab := reflect.TypeFor[T]()
	if implements[VTabDestroyer](vtab) {
		flags |= VTAB_DESTROYER
	}
	if implements[VTabUpdater](vtab) {
		flags |= VTAB_UPDATER
	}
	if implements[VTabRenamer](vtab) {
		flags |= VTAB_RENAMER
	}
	if implements[VTabOverloader](vtab) {
		flags |= VTAB_OVERLOADER
	}
	if implements[VTabChecker](vtab) {
		flags |= VTAB_CHECKER
	}
	if implements[VTabTxn](vtab) {
		flags |= VTAB_TXN
	}
	if implements[VTabSavepointer](vtab) {
		flags |= VTAB_SAVEPOINTER
	}
	if implements[VTabShadowTabler](vtab) {
		flags |= VTAB_SHADOWTABS
	}

	var modulePtr ptr_t
	defer db.arena.Mark()()
	namePtr := db.arena.String(name)
	if connect != nil {
		modulePtr = db.wrp.AddHandle(vtabModule{
			vtabConstructor(create),
			vtabConstructor(connect)})
	}
	rc := res_t(db.wrp.Xsqlite3_create_module_go(int32(db.handle),
		int32(namePtr), int32(flags), int32(modulePtr)))
	return db.error(rc)
}

func implements[I any](typ reflect.Type) bool {
	return typ.Implements(reflect.TypeFor[I]())
}

// DeclareVTab declares the schema of a virtual table.
//
// https://sqlite.org/c3ref/declare_vtab.html
func (c *Conn) DeclareVTab(sql string) error {
	if c.interrupt.Err() != nil {
		return INTERRUPT
	}
	defer c.arena.Mark()()
	textPtr := c.arena.String(sql)
	rc := res_t(c.wrp.Xsqlite3_declare_vtab(int32(c.handle), int32(textPtr)))
	return c.error(rc)
}

// VTabConflictMode is a virtual table conflict resolution mode.
//
// https://sqlite.org/c3ref/c_fail.html
type VTabConflictMode uint8

const (
	VTAB_ROLLBACK VTabConflictMode = 1
	VTAB_IGNORE   VTabConflictMode = 2
	VTAB_FAIL     VTabConflictMode = 3
	VTAB_ABORT    VTabConflictMode = 4
	VTAB_REPLACE  VTabConflictMode = 5
)

// VTabOnConflict determines the virtual table conflict policy.
//
// https://sqlite.org/c3ref/vtab_on_conflict.html
func (c *Conn) VTabOnConflict() VTabConflictMode {
	return VTabConflictMode(c.wrp.Xsqlite3_vtab_on_conflict(int32(c.handle)))
}

// VTabConfigOption is a virtual table configuration option.
//
// https://sqlite.org/c3ref/c_vtab_constraint_support.html
type VTabConfigOption uint8

const (
	VTAB_CONSTRAINT_SUPPORT VTabConfigOption = 1
	VTAB_INNOCUOUS          VTabConfigOption = 2
	VTAB_DIRECTONLY         VTabConfigOption = 3
	VTAB_USES_ALL_SCHEMAS   VTabConfigOption = 4
)

// VTabConfig configures various facets of the virtual table interface.
//
// https://sqlite.org/c3ref/vtab_config.html
func (c *Conn) VTabConfig(op VTabConfigOption, args ...any) error {
	var i int32
	if op == VTAB_CONSTRAINT_SUPPORT && len(args) > 0 {
		if b, ok := args[0].(bool); ok && b {
			i = 1
		}
	}
	rc := res_t(c.wrp.Xsqlite3_vtab_config_go(int32(c.handle), int32(op), i))
	return c.error(rc)
}

// VTabConstructor is a virtual table constructor function.
type VTabConstructor[T VTab] func(db *Conn, module, schema, table string, arg ...string) (T, error)

// A VTab describes a particular instance of the virtual table.
// A VTab may optionally implement [io.Closer] to free resources.
//
// https://sqlite.org/c3ref/vtab.html
type VTab interface {
	// https://sqlite.org/vtab.html#xbestindex
	BestIndex(*IndexInfo) error
	// https://sqlite.org/vtab.html#xopen
	Open() (VTabCursor, error)
}

// A VTabDestroyer allows a virtual table to drop persistent state.
type VTabDestroyer interface {
	VTab
	// https://sqlite.org/vtab.html#sqlite3_module.xDestroy
	Destroy() error
}

// A VTabUpdater allows a virtual table to be updated.
// Implementations must not retain arg.
type VTabUpdater interface {
	VTab
	// https://sqlite.org/vtab.html#xupdate
	Update(arg ...Value) (rowid int64, err error)
}

// A VTabRenamer allows a virtual table to be renamed.
type VTabRenamer interface {
	VTab
	// https://sqlite.org/vtab.html#xrename
	Rename(new string) error
}

// A VTabOverloader allows a virtual table to overload SQL functions.
type VTabOverloader interface {
	VTab
	// https://sqlite.org/vtab.html#xfindfunction
	FindFunction(arg int, name string) (ScalarFunction, IndexConstraintOp)
}

// A VTabShadowTabler allows a virtual table to protect the content
// of shadow tables from being corrupted by hostile SQL.
//
// Implementing this interface signals that a virtual table named
// "mumble" reserves all table names starting with "mumble_".
type VTabShadowTabler interface {
	VTab
	// https://sqlite.org/vtab.html#the_xshadowname_method
	ShadowTables()
}

// A VTabChecker allows a virtual table to report errors
// to the PRAGMA integrity_check and PRAGMA quick_check commands.
//
// Integrity should return an error if it finds problems in the content of the virtual table,
// but should avoid returning a (wrapped) [Error], [ErrorCode] or [ExtendedErrorCode],
// as those indicate the Integrity method itself encountered problems
// while trying to evaluate the virtual table content.
type VTabChecker interface {
	VTab
	// https://sqlite.org/vtab.html#xintegrity
	Integrity(schema, table string, flags int) error
}

// A VTabTxn allows a virtual table to implement
// transactions with two-phase commit.
//
// Anything that is required as part of a commit that may fail
// should be performed in the Sync() callback.
// Current versions of SQLite ignore any errors
// returned by Commit() and Rollback().
type VTabTxn interface {
	VTab
	// https://sqlite.org/vtab.html#xBegin
	Begin() error
	// https://sqlite.org/vtab.html#xsync
	Sync() error
	// https://sqlite.org/vtab.html#xcommit
	Commit() error
	// https://sqlite.org/vtab.html#xrollback
	Rollback() error
}

// A VTabSavepointer allows a virtual table to implement
// nested transactions.
//
// https://sqlite.org/vtab.html#xsavepoint
type VTabSavepointer interface {
	VTabTxn
	Savepoint(id int) error
	Release(id int) error
	RollbackTo(id int) error
}

// A VTabCursor describes cursors that point
// into the virtual table and are used
// to loop through the virtual table.
// A VTabCursor may optionally implement
// [io.Closer] to free resources.
// Implementations of Filter must not retain arg.
//
// https://sqlite.org/c3ref/vtab_cursor.html
type VTabCursor interface {
	// https://sqlite.org/vtab.html#xfilter
	Filter(idxNum int, idxStr string, arg ...Value) error
	// https://sqlite.org/vtab.html#xnext
	Next() error
	// https://sqlite.org/vtab.html#xeof
	EOF() bool
	// https://sqlite.org/vtab.html#xcolumn
	Column(ctx Context, n int) error
	// https://sqlite.org/vtab.html#xrowid
	RowID() (int64, error)
}

// An IndexInfo describes virtual table indexing information.
//
// https://sqlite.org/c3ref/index_info.html
type IndexInfo struct {
	// Inputs
	Constraint  []IndexConstraint
	OrderBy     []IndexOrderBy
	ColumnsUsed uint64
	// Outputs
	ConstraintUsage []IndexConstraintUsage
	IdxNum          int
	IdxStr          string
	IdxFlags        IndexScanFlag
	OrderByConsumed bool
	EstimatedCost   float64
	EstimatedRows   int64
	// Internal
	c      *Conn
	handle ptr_t
}

// An IndexConstraint describes virtual table indexing constraint information.
//
// https://sqlite.org/c3ref/index_info.html
type IndexConstraint struct {
	Column int
	Op     IndexConstraintOp
	Usable bool
}

// An IndexOrderBy describes virtual table indexing order by information.
//
// https://sqlite.org/c3ref/index_info.html
type IndexOrderBy struct {
	Column int
	Desc   bool
}

// An IndexConstraintUsage describes how virtual table indexing constraints will be used.
//
// https://sqlite.org/c3ref/index_info.html
type IndexConstraintUsage struct {
	ArgvIndex int
	Omit      bool
}

// RHSValue returns the value of the right-hand operand of a constraint
// if the right-hand operand is known.
//
// https://sqlite.org/c3ref/vtab_rhs_value.html
func (idx *IndexInfo) RHSValue(column int) (Value, error) {
	return idx.c.columnValue(idx.c.wrp.Xsqlite3_vtab_rhs_value, idx.handle, column)
}

// Collation returns the name of the collation for a virtual table constraint.
//
// https://sqlite.org/c3ref/vtab_collation.html
func (idx *IndexInfo) Collation(column int) string {
	ptr := ptr_t(idx.c.wrp.Xsqlite3_vtab_collation(int32(idx.handle),
		int32(column)))
	return idx.c.wrp.ReadString(ptr, _MAX_NAME)
}

// Distinct determines if a virtual table query is DISTINCT.
//
// https://sqlite.org/c3ref/vtab_distinct.html
func (idx *IndexInfo) Distinct() int {
	return int(idx.c.wrp.Xsqlite3_vtab_distinct(int32(idx.handle)))
}

// In identifies and handles IN constraints.
//
// https://sqlite.org/c3ref/vtab_in.html
func (idx *IndexInfo) In(column, handle int) bool {
	b := idx.c.wrp.Xsqlite3_vtab_in(int32(idx.handle),
		int32(column), int32(handle))
	return b != 0
}

func (idx *IndexInfo) load() {
	// https://sqlite.org/c3ref/index_info.html
	mem := idx.c.wrp.Memory
	ptr := idx.handle

	nConstraint := int32(mem.Read32(ptr + 0))
	idx.Constraint = make([]IndexConstraint, nConstraint)
	idx.ConstraintUsage = make([]IndexConstraintUsage, nConstraint)
	idx.OrderBy = make([]IndexOrderBy, int32(mem.Read32(ptr+8)))

	constraintPtr := ptr_t(mem.Read32(ptr + 4))
	constraint := idx.Constraint
	for i := range idx.Constraint {
		constraint[i] = IndexConstraint{
			Column: int(int32(mem.Read32(constraintPtr + 0))),
			Op:     IndexConstraintOp(mem.Read(constraintPtr + 4)),
			Usable: mem.Read(constraintPtr+5) != 0,
		}
		constraintPtr += 12
	}

	orderByPtr := ptr_t(mem.Read32(ptr + 12))
	orderBy := idx.OrderBy
	for i := range orderBy {
		orderBy[i] = IndexOrderBy{
			Column: int(int32(mem.Read32(orderByPtr + 0))),
			Desc:   mem.Read(orderByPtr+4) != 0,
		}
		orderByPtr += 8
	}

	idx.EstimatedCost = mem.ReadFloat64(ptr + 40)
	idx.EstimatedRows = int64(mem.Read64(ptr + 48))
	idx.ColumnsUsed = mem.Read64(ptr + 64)
}

func (idx *IndexInfo) save() {
	// https://sqlite.org/c3ref/index_info.html
	mem := idx.c.wrp.Memory
	ptr := idx.handle

	usagePtr := ptr_t(mem.Read32(ptr + 16))
	for _, usage := range idx.ConstraintUsage {
		mem.Write32(usagePtr+0, uint32(usage.ArgvIndex))
		if usage.Omit {
			mem.Write(usagePtr+4, 1)
		}
		usagePtr += 8
	}

	mem.Write32(ptr+20, uint32(idx.IdxNum))
	if idx.IdxStr != "" {
		mem.Write32(ptr+24, uint32(idx.c.wrp.NewString(idx.IdxStr)))
		mem.WriteBool(ptr+28, true) // needToFreeIdxStr
	}
	if idx.OrderByConsumed {
		mem.WriteBool(ptr+32, true)
	}
	mem.WriteFloat64(ptr+40, idx.EstimatedCost)
	mem.Write64(ptr+48, uint64(idx.EstimatedRows))
	mem.Write32(ptr+56, uint32(idx.IdxFlags))
}

// IndexConstraintOp is a virtual table constraint operator code.
//
// https://sqlite.org/c3ref/c_index_constraint_eq.html
type IndexConstraintOp uint8

const (
	INDEX_CONSTRAINT_EQ        IndexConstraintOp = 2
	INDEX_CONSTRAINT_GT        IndexConstraintOp = 4
	INDEX_CONSTRAINT_LE        IndexConstraintOp = 8
	INDEX_CONSTRAINT_LT        IndexConstraintOp = 16
	INDEX_CONSTRAINT_GE        IndexConstraintOp = 32
	INDEX_CONSTRAINT_MATCH     IndexConstraintOp = 64
	INDEX_CONSTRAINT_LIKE      IndexConstraintOp = 65
	INDEX_CONSTRAINT_GLOB      IndexConstraintOp = 66
	INDEX_CONSTRAINT_REGEXP    IndexConstraintOp = 67
	INDEX_CONSTRAINT_NE        IndexConstraintOp = 68
	INDEX_CONSTRAINT_ISNOT     IndexConstraintOp = 69
	INDEX_CONSTRAINT_ISNOTNULL IndexConstraintOp = 70
	INDEX_CONSTRAINT_ISNULL    IndexConstraintOp = 71
	INDEX_CONSTRAINT_IS        IndexConstraintOp = 72
	INDEX_CONSTRAINT_LIMIT     IndexConstraintOp = 73
	INDEX_CONSTRAINT_OFFSET    IndexConstraintOp = 74
	INDEX_CONSTRAINT_FUNCTION  IndexConstraintOp = 150
)

// IndexScanFlag is a virtual table scan flag.
//
// https://sqlite.org/c3ref/c_index_scan_unique.html
type IndexScanFlag uint32

const (
	INDEX_SCAN_UNIQUE IndexScanFlag = 0x00000001
	INDEX_SCAN_HEX    IndexScanFlag = 0x00000002
)

type vtabModule = [2]VTabConstructor[VTab]

func vtabConstructor[T VTab](c VTabConstructor[T]) VTabConstructor[VTab] {
	return func(db *Conn, module, schema, table string, arg ...string) (VTab, error) {
		return c(db, module, schema, table, arg...)
	}
}

func (e *env) vtabConstruct(fn VTabConstructor[VTab], nArg, pArg, ppVTab, pzErr int32) int32 {
	arg := make([]string, nArg)
	for i := range nArg {
		ptr := ptr_t(e.Memory.Read32(ptr_t(pArg + i*ptrlen)))
		arg[i] = e.ReadString(ptr, _MAX_SQL_LENGTH)
	}

	tab, err := fn(e.DB.(*Conn), arg[0], arg[1], arg[2], arg[3:]...)
	if err == nil {
		e.vtabPutHandle(ppVTab, tab)
	}
	return e.vtabError(pzErr, _PTR_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_create(pMod, nArg, pArg, ppVTab, pzErr int32) int32 {
	module := e.vtabGetHandle(pMod).(vtabModule)
	return e.vtabConstruct(module[0], nArg, pArg, ppVTab, pzErr)
}

func (e *env) Xgo_vtab_connect(pMod, nArg, pArg, ppVTab, pzErr int32) int32 {
	module := e.vtabGetHandle(pMod).(vtabModule)
	return e.vtabConstruct(module[1], nArg, pArg, ppVTab, pzErr)
}

func (e *env) Xgo_vtab_disconnect(pVTab int32) int32 {
	err := e.vtabDelHandle(pVTab)
	return e.vtabError(0, _PTR_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_destroy(pVTab int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabDestroyer)
	err := errors.Join(vtab.Destroy(), e.vtabDelHandle(pVTab))
	return e.vtabError(0, _PTR_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_best_index(pVTab, pIdxInfo int32) int32 {
	var info IndexInfo
	info.handle = ptr_t(pIdxInfo)
	info.c = e.DB.(*Conn)
	info.load()

	vtab := e.vtabGetHandle(pVTab).(VTab)
	err := vtab.BestIndex(&info)

	info.save()
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_update(pVTab, nArg, pArg, pRowID int32) int32 {
	db := e.DB.(*Conn)
	args := callbackArgs(db, nArg, ptr_t(pArg))
	defer returnArgs(args)

	vtab := e.vtabGetHandle(pVTab).(VTabUpdater)
	rowID, err := vtab.Update(*args...)
	if err == nil {
		e.Memory.Write64(ptr_t(pRowID), uint64(rowID))
	}

	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_rename(pVTab, zNew int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabRenamer)
	err := vtab.Rename(e.ReadString(ptr_t(zNew), _MAX_NAME))
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_find_function(pVTab, nArg, zName, pxFunc int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabOverloader)
	f, op := vtab.FindFunction(int(nArg), e.ReadString(ptr_t(zName), _MAX_NAME))
	if op != 0 {
		var wrapper ptr_t
		wrapper = e.AddHandle(func(c Context, arg ...Value) {
			defer e.DelHandle(wrapper)
			f(c, arg...)
		})
		e.Memory.Write32(ptr_t(pxFunc), uint32(wrapper))
	}
	return int32(op)
}

func (e *env) Xgo_vtab_integrity(pVTab, zSchema, zTabName, mFlags, pzErr int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabChecker)
	schema := e.ReadString(ptr_t(zSchema), _MAX_NAME)
	table := e.ReadString(ptr_t(zTabName), _MAX_NAME)
	err := vtab.Integrity(schema, table, int(uint32(mFlags)))
	// xIntegrity should return OK - even if it finds problems in the content of the virtual table.
	// https://sqlite.org/vtab.html#xintegrity
	return e.vtabError(pzErr, _PTR_ERROR, err, _OK)
}

func (e *env) Xgo_vtab_begin(pVTab int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabTxn)
	err := vtab.Begin()
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_sync(pVTab int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabTxn)
	err := vtab.Sync()
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_commit(pVTab int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabTxn)
	err := vtab.Commit()
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_rollback(pVTab int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabTxn)
	err := vtab.Rollback()
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_savepoint(pVTab, id int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabSavepointer)
	err := vtab.Savepoint(int(id))
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_release(pVTab, id int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabSavepointer)
	err := vtab.Release(int(id))
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_vtab_rollback_to(pVTab, id int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTabSavepointer)
	err := vtab.RollbackTo(int(id))
	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_cur_open(pVTab, ppCur int32) int32 {
	vtab := e.vtabGetHandle(pVTab).(VTab)

	cursor, err := vtab.Open()
	if err == nil {
		e.vtabPutHandle(ppCur, cursor)
	}

	return e.vtabError(pVTab, _VTAB_ERROR, err, ERROR)
}

func (e *env) Xgo_cur_close(pCur int32) int32 {
	err := e.vtabDelHandle(pCur)
	return e.vtabError(0, _PTR_ERROR, err, ERROR)
}

func (e *env) Xgo_cur_filter(pCur, idxNum, idxStr, nArg, pArg int32) int32 {
	db := e.DB.(*Conn)
	args := callbackArgs(db, nArg, ptr_t(pArg))
	defer returnArgs(args)

	var idxName string
	if idxStr != 0 {
		idxName = e.ReadString(ptr_t(idxStr), _MAX_LENGTH)
	}

	cursor := e.vtabGetHandle(pCur).(VTabCursor)
	err := cursor.Filter(int(idxNum), idxName, *args...)
	return e.vtabError(pCur, _CURSOR_ERROR, err, ERROR)
}

func (e *env) Xgo_cur_eof(pCur int32) int32 {
	cursor := e.vtabGetHandle(pCur).(VTabCursor)
	if cursor.EOF() {
		return 1
	}
	return 0
}

func (e *env) Xgo_cur_next(pCur int32) int32 {
	cursor := e.vtabGetHandle(pCur).(VTabCursor)
	err := cursor.Next()
	return e.vtabError(pCur, _CURSOR_ERROR, err, ERROR)
}

func (e *env) Xgo_cur_column(pCur, pCtx, n int32) int32 {
	cursor := e.vtabGetHandle(pCur).(VTabCursor)
	db := e.DB.(*Conn)
	err := cursor.Column(Context{db, ptr_t(pCtx)}, int(n))
	return e.vtabError(pCur, _CURSOR_ERROR, err, ERROR)
}

func (e *env) Xgo_cur_rowid(pCur, pRowID int32) int32 {
	cursor := e.vtabGetHandle(pCur).(VTabCursor)

	rowID, err := cursor.RowID()
	if err == nil {
		e.Memory.Write64(ptr_t(pRowID), uint64(rowID))
	}

	return e.vtabError(pCur, _CURSOR_ERROR, err, ERROR)
}

const (
	_PTR_ERROR = iota
	_VTAB_ERROR
	_CURSOR_ERROR
)

func (e *env) vtabError(ptr int32, kind uint32, err error, def ErrorCode) int32 {
	const zErrMsgOffset = 8
	msg, code := errorCode(err, def)
	if ptr != 0 && msg != "" {
		switch kind {
		case _VTAB_ERROR:
			ptr = ptr + zErrMsgOffset // zErrMsg
		case _CURSOR_ERROR:
			ptr = int32(e.Memory.Read32(ptr_t(ptr))) + zErrMsgOffset // pVTab->zErrMsg
		}
		db := e.DB.(*Conn)
		if ptr := ptr_t(e.Memory.Read32(ptr_t(ptr))); ptr != 0 {
			db.wrp.Free(ptr)
		}
		e.Memory.Write32(ptr_t(ptr), uint32(db.wrp.NewString(msg)))
	}
	return int32(code)
}

func (e *env) vtabGetHandle(ptr int32) any {
	const handleOffset = 4
	handle := ptr_t(e.Memory.Read32(ptr_t(ptr) - handleOffset))
	return e.GetHandle(handle)
}

func (e *env) vtabDelHandle(ptr int32) error {
	const handleOffset = 4
	handle := ptr_t(e.Memory.Read32(ptr_t(ptr) - handleOffset))
	return e.DelHandle(handle)
}

func (e *env) vtabPutHandle(pptr int32, val any) {
	const handleOffset = 4
	handle := e.AddHandle(val)
	ptr := ptr_t(e.Memory.Read32(ptr_t(pptr)))
	e.Memory.Write32(ptr-handleOffset, uint32(handle))
}
