package luajit

/*
#include <lua.h>
#include <lauxlib.h>
*/
import "C"
import "fmt"

// LuaError represents an error from the Lua state
type LuaError struct {
	Code    int
	Message string
}

func (e *LuaError) Error() string {
	return fmt.Sprintf("lua error (code=%d): %s", e.Code, e.Message)
}

// Stack management constants from lua.h
const (
	LUA_MINSTACK      = 20      // Minimum Lua stack size
	LUA_MAXSTACK      = 1000000 // Maximum Lua stack size
	LUA_REGISTRYINDEX = -10000  // Pseudo-index for the Lua registry
	LUA_GLOBALSINDEX  = -10002  // Pseudo-index for globals table
)

// checkStack ensures there is enough space on the Lua stack
func (s *State) checkStack(n int) error {
	if C.lua_checkstack(s.L, C.int(n)) == 0 {
		return fmt.Errorf("stack overflow (cannot allocate %d slots)", n)
	}
	return nil
}

// safeCall wraps a potentially dangerous C call with stack checking
func (s *State) safeCall(f func() C.int) error {
	// Save current stack size
	top := s.GetTop()

	// Ensure we have enough stack space (minimum 20 slots as per Lua standard)
	if err := s.checkStack(LUA_MINSTACK); err != nil {
		return err
	}

	// Make the call
	status := f()

	// Check for errors
	if status != 0 {
		err := &LuaError{
			Code:    int(status),
			Message: s.ToString(-1),
		}
		s.Pop(1) // Remove error message
		return err
	}

	// Verify stack integrity
	newTop := s.GetTop()
	if newTop < top {
		return fmt.Errorf("stack underflow: %d slots lost", top-newTop)
	}

	return nil
}

// stackGuard wraps a function with stack checking and restoration
func stackGuard[T any](s *State, f func() (T, error)) (T, error) {
	// Save current stack size
	top := s.GetTop()

	// Run the protected function
	result, err := f()

	// Restore stack size
	newTop := s.GetTop()
	if newTop > top {
		s.Pop(newTop - top)
	}

	return result, err
}

// stackGuardValue executes a function that returns a value and error with stack protection
func stackGuardValue[T any](s *State, f func() (T, error)) (T, error) {
	// Save current stack size
	top := s.GetTop()

	// Run the protected function
	result, err := f()

	// Restore stack size
	newTop := s.GetTop()
	if newTop > top {
		s.Pop(newTop - top)
	}

	return result, err
}

// stackGuardErr executes a function that only returns an error with stack protection
func stackGuardErr(s *State, f func() error) error {
	// Save current stack size
	top := s.GetTop()

	// Run the protected function
	err := f()

	// Restore stack size
	newTop := s.GetTop()
	if newTop > top {
		s.Pop(newTop - top)
	}

	return err
}

// getStackTrace returns the current Lua stack trace
func (s *State) getStackTrace() string {
	// Push debug.traceback function
	s.GetGlobal("debug")
	if !s.IsTable(-1) {
		s.Pop(1)
		return "stack trace not available (debug module not loaded)"
	}

	s.GetField(-1, "traceback")
	if !s.IsFunction(-1) {
		s.Pop(2)
		return "stack trace not available (debug.traceback not found)"
	}

	// Call debug.traceback
	if err := s.safeCall(func() C.int {
		return C.lua_pcall(s.L, 0, 1, 0)
	}); err != nil {
		return fmt.Sprintf("error getting stack trace: %v", err)
	}

	// Get the resulting string
	trace := s.ToString(-1)
	s.Pop(1) // Remove the trace string

	return trace
}
