package luajit

/*
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdlib.h>

static int get_table_length(lua_State *L, int index) {
    return lua_objlen(L, index);
}
*/
import "C"
import (
	"fmt"
)

// TableValue represents any value that can be stored in a Lua table
type TableValue interface {
	~string | ~float64 | ~bool | ~int | ~map[string]interface{} | ~[]float64 | ~[]interface{}
}

func (s *State) GetTableLength(index int) int { return int(C.get_table_length(s.L, C.int(index))) }

// ToTable converts a Lua table to a Go map
func (s *State) ToTable(index int) (map[string]interface{}, error) {
	if s.safeStack {
		return stackGuardValue[map[string]interface{}](s, func() (map[string]interface{}, error) {
			if !s.IsTable(index) {
				return nil, fmt.Errorf("not a table at index %d", index)
			}
			return s.toTableUnsafe(index)
		})
	}
	if !s.IsTable(index) {
		return nil, fmt.Errorf("not a table at index %d", index)
	}
	return s.toTableUnsafe(index)
}

func (s *State) pushTableSafe(table map[string]interface{}) error {
	size := 2
	if err := s.checkStack(size); err != nil {
		return fmt.Errorf("insufficient stack space: %w", err)
	}

	s.NewTable()
	for k, v := range table {
		if err := s.pushValueSafe(v); err != nil {
			return err
		}
		s.SetField(-2, k)
	}
	return nil
}

func (s *State) pushTableUnsafe(table map[string]interface{}) error {
	s.NewTable()
	for k, v := range table {
		if err := s.pushValueUnsafe(v); err != nil {
			return err
		}
		s.SetField(-2, k)
	}
	return nil
}

func (s *State) toTableSafe(index int) (map[string]interface{}, error) {
	if err := s.checkStack(2); err != nil {
		return nil, err
	}
	return s.toTableUnsafe(index)
}

func (s *State) toTableUnsafe(index int) (map[string]interface{}, error) {
	absIdx := s.absIndex(index)
	table := make(map[string]interface{})

	// Check if it's an array-like table
	length := s.GetTableLength(absIdx)
	if length > 0 {
		array := make([]float64, length)
		isArray := true

		// Try to convert to array
		for i := 1; i <= length; i++ {
			s.PushNumber(float64(i))
			s.GetTable(absIdx)
			if s.GetType(-1) != TypeNumber {
				isArray = false
				s.Pop(1)
				break
			}
			array[i-1] = s.ToNumber(-1)
			s.Pop(1)
		}

		if isArray {
			return map[string]interface{}{"": array}, nil
		}
	}

	// Handle regular table
	s.PushNil()
	for C.lua_next(s.L, C.int(absIdx)) != 0 {
		key := ""
		valueType := C.lua_type(s.L, -2)
		if valueType == C.LUA_TSTRING {
			key = s.ToString(-2)
		} else if valueType == C.LUA_TNUMBER {
			key = fmt.Sprintf("%g", s.ToNumber(-2))
		}

		value, err := s.toValueUnsafe(-1)
		if err != nil {
			s.Pop(1)
			return nil, err
		}

		// Handle nested array case
		if m, ok := value.(map[string]interface{}); ok {
			if arr, ok := m[""]; ok {
				value = arr
			}
		}

		table[key] = value
		s.Pop(1)
	}

	return table, nil
}

// NewTable creates a new table and pushes it onto the stack
func (s *State) NewTable() {
	if s.safeStack {
		if err := s.checkStack(1); err != nil {
			// Since we can't return an error, we'll push nil instead
			s.PushNil()
			return
		}
	}
	C.lua_createtable(s.L, 0, 0)
}

// SetTable sets a table field with cached absolute index
func (s *State) SetTable(index int) {
	absIdx := index
	if s.safeStack && (index < 0 && index > LUA_REGISTRYINDEX) {
		absIdx = s.GetTop() + index + 1
	}
	C.lua_settable(s.L, C.int(absIdx))
}

// GetTable gets a table field with cached absolute index
func (s *State) GetTable(index int) {
	absIdx := index
	if s.safeStack && (index < 0 && index > LUA_REGISTRYINDEX) {
		absIdx = s.GetTop() + index + 1
	}

	if s.safeStack {
		if err := s.checkStack(1); err != nil {
			s.PushNil()
			return
		}
	}
	C.lua_gettable(s.L, C.int(absIdx))
}

// PushTable pushes a Go map onto the Lua stack as a table with stack checking
func (s *State) PushTable(table map[string]interface{}) error {
	if s.safeStack {
		return s.pushTableSafe(table)
	}
	return s.pushTableUnsafe(table)
}