/**
 * @file action_db_lua_test.cpp
 * @brief Compile-time test for ActionDatabase Lua API.
 *
 * This test creates a Lua state, registers the ActionDatabase singleton
 * and the Lua action API, then runs Lua scripts that create actions
 * (including with behavior trees), goals, queries them, and verifies
 * the results match expectations.
 *
 * Build with:
 *   g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
 *       action_db_lua_test.cpp \
 *       ../components/ActionDatabase.cpp \
 *       ../components/GoapBlackboard.cpp \
 *       ../components/GoapGoal.cpp \
 *       ../components/GoapAction.cpp \
 *       ../../lua/lua-5.4.8/src/liblua.a \
 *       -o action_db_lua_test -lm
 *
 * Or via CMake (see CMakeLists.txt in this directory).
 */

#include <cstdio>
#include <cstring>
#include <cassert>
#include <string>
#include <vector>

// Ogre stub (provides Ogre::String, Ogre::Vector3, Ogre::LogManager)
// Must be included before any component headers that use Ogre types.
#include "ogre_stub.h"

// Include Lua
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}

// Include the components we need
#include "../components/ActionDatabase.hpp"
#include "../components/GoapBlackboard.hpp"
#include "../components/GoapAction.hpp"
#include "../components/GoapGoal.hpp"
#include "../components/BehaviorTree.hpp"

// Forward declare the registration function
namespace editScene
{
void registerLuaActionApi(lua_State *L);
}

// ---------------------------------------------------------------------------
// Test helpers
// ---------------------------------------------------------------------------

static int testCount = 0;
static int passCount = 0;

#define TEST(name)                                             \
	do {                                                   \
		testCount++;                                   \
		printf("  TEST %d: %s ... ", testCount, name); \
	} while (0)

#define PASS()                    \
	do {                      \
		passCount++;      \
		printf("PASS\n"); \
	} while (0)

#define FAIL(msg)                          \
	do {                               \
		printf("FAIL: %s\n", msg); \
		return 1;                  \
	} while (0)

// ---------------------------------------------------------------------------
// Helper: run a Lua string and check for errors
// ---------------------------------------------------------------------------

static bool runLua(lua_State *L, const char *code)
{
	if (luaL_dostring(L, code) != LUA_OK) {
		fprintf(stderr, "Lua error: %s\n", lua_tostring(L, -1));
		lua_pop(L, 1);
		return false;
	}
	return true;
}

// ---------------------------------------------------------------------------
// Test 1: Basic action creation and lookup
// ---------------------------------------------------------------------------

static int testBasicAction(lua_State *L)
{
	TEST("create and find a simple action");

	// Clear any previous state
	ActionDatabase::getSingleton().clear();

	// Create action via Lua
	bool ok = runLua(
		L,
		"ecs.action_db.add_action('test_action', 5, "
		"  { bits = { has_axe = true }, values = { stamina = 10 } }, "
		"  { bits = { has_wood = true }, values = { stamina = -5 } }"
		")");
	if (!ok)
		FAIL("failed to run Lua");

	// Verify via C++ API
	const GoapAction *action =
		ActionDatabase::getSingleton().findAction("test_action");
	if (!action)
		FAIL("action not found");
	if (action->name != "test_action")
		FAIL("wrong name");
	if (action->cost != 5)
		FAIL("wrong cost");

	// Verify preconditions
	if (!action->preconditions.getBit(0))
		FAIL("has_axe bit not set");
	if (action->preconditions.values.at("stamina") != 10)
		FAIL("wrong stamina precondition");

	// Verify effects
	if (!action->effects.getBit(1))
		FAIL("has_wood bit not set");
	if (action->effects.values.at("stamina") != -5)
		FAIL("wrong stamina effect");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 2: Action with behavior tree
// ---------------------------------------------------------------------------

static int testActionWithBehaviorTree(lua_State *L)
{
	TEST("create action with behavior tree");

	ActionDatabase::getSingleton().clear();

	bool ok = runLua(L,
			 "ecs.action_db.add_action('wave', 1, {}, {}, {"
			 "  type = 'sequence',"
			 "  children = {"
			 "    { type = 'setAnimationState', name = 'SM/Wave' },"
			 "    { type = 'delay', params = '2.0' },"
			 "    { type = 'setAnimationState', name = 'SM/Idle' }"
			 "  }"
			 "})");
	if (!ok)
		FAIL("failed to run Lua");

	const GoapAction *action =
		ActionDatabase::getSingleton().findAction("wave");
	if (!action)
		FAIL("action not found");

	// Verify behavior tree structure
	const BehaviorTreeNode &bt = action->behaviorTree;
	if (bt.type != "sequence")
		FAIL("expected sequence root");
	if (bt.children.size() != 3)
		FAIL("expected 3 children");

	if (bt.children[0].type != "setAnimationState")
		FAIL("child 0 wrong type");
	if (bt.children[0].name != "SM/Wave")
		FAIL("child 0 wrong name");

	if (bt.children[1].type != "delay")
		FAIL("child 1 wrong type");
	if (bt.children[1].params != "2.0")
		FAIL("child 1 wrong params");

	if (bt.children[2].type != "setAnimationState")
		FAIL("child 2 wrong type");
	if (bt.children[2].name != "SM/Idle")
		FAIL("child 2 wrong name");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 3: Nested behavior tree (selector with sequences)
// ---------------------------------------------------------------------------

static int testNestedBehaviorTree(lua_State *L)
{
	TEST("create action with nested behavior tree");

	ActionDatabase::getSingleton().clear();

	bool ok = runLua(
		L,
		"ecs.action_db.add_action('chop_tree', 3,"
		"  { bits = { near_tree = true }, values = { stamina = 15 } },"
		"  { bits = { has_wood = true }, values = { stamina = -8 } },"
		"  {"
		"    type = 'selector',"
		"    children = {"
		"      {"
		"        type = 'sequence',"
		"        children = {"
		"          { type = 'checkBit', name = 'has_axe', params = '1' },"
		"          { type = 'setAnimationState', name = 'SM/Chop' },"
		"          { type = 'delay', params = '3.0' }"
		"        }"
		"      },"
		"      {"
		"        type = 'sequence',"
		"        children = {"
		"          { type = 'debugPrint', name = 'No axe!' },"
		"          { type = 'setAnimationState', name = 'SM/Pickup' }"
		"        }"
		"      }"
		"    }"
		"  }"
		")");
	if (!ok)
		FAIL("failed to run Lua");

	const GoapAction *action =
		ActionDatabase::getSingleton().findAction("chop_tree");
	if (!action)
		FAIL("action not found");

	const BehaviorTreeNode &bt = action->behaviorTree;
	if (bt.type != "selector")
		FAIL("expected selector root");
	if (bt.children.size() != 2)
		FAIL("expected 2 children");

	// First child: sequence with 3 nodes
	const BehaviorTreeNode &seq1 = bt.children[0];
	if (seq1.type != "sequence")
		FAIL("child 0 should be sequence");
	if (seq1.children.size() != 3)
		FAIL("seq1 expected 3 children");
	if (seq1.children[0].type != "checkBit")
		FAIL("seq1 child 0 wrong type");
	if (seq1.children[0].name != "has_axe")
		FAIL("seq1 child 0 wrong name");
	if (seq1.children[0].params != "1")
		FAIL("seq1 child 0 wrong params");

	// Second child: sequence with 2 nodes
	const BehaviorTreeNode &seq2 = bt.children[1];
	if (seq2.type != "sequence")
		FAIL("child 1 should be sequence");
	if (seq2.children.size() != 2)
		FAIL("seq2 expected 2 children");
	if (seq2.children[0].type != "debugPrint")
		FAIL("seq2 child 0 wrong type");
	if (seq2.children[0].name != "No axe!")
		FAIL("seq2 child 0 wrong name");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 4: Goal creation and lookup
// ---------------------------------------------------------------------------

static int testGoal(lua_State *L)
{
	TEST("create and find a goal");

	ActionDatabase::getSingleton().clear();

	bool ok = runLua(L, "ecs.action_db.add_goal('test_goal', 80,"
			    "  { values = { health = 100, stamina = 80 } },"
			    "  'health < 50 || stamina < 30'"
			    ")");
	if (!ok)
		FAIL("failed to run Lua");

	const GoapGoal *goal =
		ActionDatabase::getSingleton().findGoal("test_goal");
	if (!goal)
		FAIL("goal not found");
	if (goal->name != "test_goal")
		FAIL("wrong name");
	if (goal->priority != 80)
		FAIL("wrong priority");
	if (goal->condition != "health < 50 || stamina < 30")
		FAIL("wrong condition");
	if (goal->target.values.at("health") != 100)
		FAIL("wrong health target");
	if (goal->target.values.at("stamina") != 80)
		FAIL("wrong stamina target");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 5: Action replacement (same name)
// ---------------------------------------------------------------------------

static int testActionReplacement(lua_State *L)
{
	TEST("replace action with same name");

	ActionDatabase::getSingleton().clear();

	// Create initial action
	runLua(L,
	       "ecs.action_db.add_action('replace_me', 1, "
	       "{ bits = { old_flag = true } }, { bits = { old_done = true } })");

	// Verify initial
	const GoapAction *first =
		ActionDatabase::getSingleton().findAction("replace_me");
	if (!first)
		FAIL("initial action not found");
	if (first->cost != 1)
		FAIL("initial cost wrong");

	// Replace with new definition
	runLua(L,
	       "ecs.action_db.add_action('replace_me', 99, "
	       "{ bits = { new_flag = true } }, { bits = { new_done = true } })");

	// Verify replacement
	const GoapAction *second =
		ActionDatabase::getSingleton().findAction("replace_me");
	if (!second)
		FAIL("replaced action not found");
	if (second->cost != 99)
		FAIL("replaced cost wrong");
	if (second->preconditions.getBit(0))
		FAIL("old precondition still present");

	// Should only be one action with this name
	int count = 0;
	for (const auto &a : ActionDatabase::getSingleton().actions) {
		if (a.name == "replace_me")
			count++;
	}
	if (count != 1)
		FAIL("expected exactly one action with this name");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 6: Remove action
// ---------------------------------------------------------------------------

static int testRemoveAction(lua_State *L)
{
	TEST("remove action");

	ActionDatabase::getSingleton().clear();

	runLua(L, "ecs.action_db.add_action('to_remove', 1)");
	runLua(L, "ecs.action_db.add_action('to_keep', 2)");

	if (!ActionDatabase::getSingleton().findAction("to_remove"))
		FAIL("action should exist before removal");

	// Remove via Lua
	bool ok = runLua(L,
			 "local r = ecs.action_db.remove_action('to_remove');"
			 "assert(r == true, 'remove should return true')");
	if (!ok)
		FAIL("failed to run Lua remove");

	if (ActionDatabase::getSingleton().findAction("to_remove"))
		FAIL("action should not exist after removal");
	if (!ActionDatabase::getSingleton().findAction("to_keep"))
		FAIL("other action should still exist");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 7: List actions
// ---------------------------------------------------------------------------

static int testListActions(lua_State *L)
{
	TEST("list actions");

	ActionDatabase::getSingleton().clear();

	runLua(L, "ecs.action_db.add_action('alpha', 1)");
	runLua(L, "ecs.action_db.add_action('beta', 2)");
	runLua(L, "ecs.action_db.add_action('gamma', 3)");

	bool ok = runLua(
		L, "local list = ecs.action_db.list_actions();"
		   "assert(#list == 3, 'expected 3 actions, got ' .. #list);"
		   "assert(list[1] == 'alpha', 'expected alpha first');"
		   "assert(list[2] == 'beta', 'expected beta second');"
		   "assert(list[3] == 'gamma', 'expected gamma third')");
	if (!ok)
		FAIL("list actions assertion failed");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 8: Find action via Lua (returns table)
// ---------------------------------------------------------------------------

static int testFindActionLua(lua_State *L)
{
	TEST("find action from Lua returns table");

	ActionDatabase::getSingleton().clear();

	runLua(L, "ecs.action_db.add_action('find_me', 7,"
		  "  { values = { x = 42 } },"
		  "  { values = { y = 99 } },"
		  "  { type = 'task', name = 'do_something' }"
		  ")");

	bool ok = runLua(
		L,
		"local a = ecs.action_db.find_action('find_me');"
		"assert(a ~= nil, 'action should exist');"
		"assert(a.name == 'find_me', 'wrong name');"
		"assert(a.cost == 7, 'wrong cost');"
		"assert(a.preconditions.values.x == 42, 'wrong precond');"
		"assert(a.effects.values.y == 99, 'wrong effects');"
		"assert(a.behaviorTree ~= nil, 'should have behaviorTree');"
		"assert(a.behaviorTree.type == 'task', 'wrong bt type');"
		"assert(a.behaviorTree.name == 'do_something', 'wrong bt name')");
	if (!ok)
		FAIL("find action Lua assertions failed");

	// Test nil for non-existent
	ok = runLua(L, "local a = ecs.action_db.find_action('nonexistent');"
		       "assert(a == nil, 'nonexistent should be nil')");
	if (!ok)
		FAIL("nonexistent action should be nil");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 9: Clear all
// ---------------------------------------------------------------------------

static int testClear(lua_State *L)
{
	TEST("clear all actions and goals");

	ActionDatabase::getSingleton().clear();

	runLua(L, "ecs.action_db.add_action('a1', 1)");
	runLua(L, "ecs.action_db.add_action('a2', 2)");
	runLua(L, "ecs.action_db.add_goal('g1', 10)");

	if (ActionDatabase::getSingleton().actions.size() != 2)
		FAIL("expected 2 actions before clear");
	if (ActionDatabase::getSingleton().goals.size() != 1)
		FAIL("expected 1 goal before clear");

	runLua(L, "ecs.action_db.clear()");

	if (ActionDatabase::getSingleton().actions.size() != 0)
		FAIL("expected 0 actions after clear");
	if (ActionDatabase::getSingleton().goals.size() != 0)
		FAIL("expected 0 goals after clear");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 10: Action with inventory behavior tree nodes
// ---------------------------------------------------------------------------

static int testInventoryBehaviorTree(lua_State *L)
{
	TEST("action with inventory behavior tree nodes");

	ActionDatabase::getSingleton().clear();

	bool ok = runLua(
		L, "ecs.action_db.add_action('gather_wood', 2,"
		   "  { bits = { near_forest = true } },"
		   "  { values = { wood_count = 3 } },"
		   "  {"
		   "    type = 'sequence',"
		   "    children = {"
		   "      { type = 'setAnimationState', name = 'SM/Gather' },"
		   "      { type = 'delay', params = '2.0' },"
		   "      { type = 'addItemToInventory',"
		   "        params = 'wood,Firewood,material,3,1.0,0' },"
		   "      { type = 'setAnimationState', name = 'SM/Idle' }"
		   "    }"
		   "  }"
		   ")");
	if (!ok)
		FAIL("failed to run Lua");

	const GoapAction *action =
		ActionDatabase::getSingleton().findAction("gather_wood");
	if (!action)
		FAIL("action not found");

	const BehaviorTreeNode &bt = action->behaviorTree;
	if (bt.type != "sequence")
		FAIL("expected sequence");
	if (bt.children.size() != 4)
		FAIL("expected 4 children");
	if (bt.children[2].type != "addItemToInventory")
		FAIL("wrong node type");
	if (bt.children[2].params != "wood,Firewood,material,3,1.0,0")
		FAIL("wrong inventory params");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 11: Action with teleport/disablePhysics behavior tree
// ---------------------------------------------------------------------------

static int testPhysicsBehaviorTree(lua_State *L)
{
	TEST("action with physics behavior tree nodes");

	ActionDatabase::getSingleton().clear();

	bool ok = runLua(
		L, "ecs.action_db.add_action('sit_on_chair', 1,"
		   "  { bits = { near_chair = true } },"
		   "  { bits = { is_sitting = true } },"
		   "  {"
		   "    type = 'sequence',"
		   "    children = {"
		   "      { type = 'teleportToChild', name = 'SitTarget' },"
		   "      { type = 'disablePhysics' },"
		   "      { type = 'setAnimationState', name = 'SM/Sit' },"
		   "      { type = 'delay', params = '5.0' },"
		   "      { type = 'setAnimationState', name = 'SM/Stand' },"
		   "      { type = 'enablePhysics' }"
		   "    }"
		   "  }"
		   ")");
	if (!ok)
		FAIL("failed to run Lua");

	const GoapAction *action =
		ActionDatabase::getSingleton().findAction("sit_on_chair");
	if (!action)
		FAIL("action not found");

	const BehaviorTreeNode &bt = action->behaviorTree;
	if (bt.children.size() != 6)
		FAIL("expected 6 children");
	if (bt.children[0].type != "teleportToChild")
		FAIL("wrong child 0 type");
	if (bt.children[0].name != "SitTarget")
		FAIL("wrong child 0 name");
	if (bt.children[1].type != "disablePhysics")
		FAIL("wrong child 1 type");
	if (bt.children[4].type != "setAnimationState")
		FAIL("wrong child 4 type");
	if (bt.children[4].name != "SM/Stand")
		FAIL("wrong child 4 name");
	if (bt.children[5].type != "enablePhysics")
		FAIL("wrong child 5 type");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 12: Action with blackboard check nodes
// ---------------------------------------------------------------------------

static int testBlackboardCheckBehaviorTree(lua_State *L)
{
	TEST("action with blackboard check behavior tree nodes");

	ActionDatabase::getSingleton().clear();

	bool ok = runLua(
		L,
		"ecs.action_db.add_action('travel_to_market', 5,"
		"  { bits = { is_awake = true }, values = { energy = 20 } },"
		"  { bits = { at_market = true }, values = { energy = -15 } },"
		"  {"
		"    type = 'sequence',"
		"    children = {"
		"      { type = 'checkValue', name = 'energy', params = '>= 20' },"
		"      { type = 'setAnimationState', name = 'SM/Walk' },"
		"      { type = 'delay', params = '5.0' },"
		"      { type = 'setAnimationState', name = 'SM/Idle' },"
		"      { type = 'setBit', name = 'at_market', params = '1' },"
		"      { type = 'setBit', name = 'at_home', params = '0' }"
		"    }"
		"  }"
		")");
	if (!ok)
		FAIL("failed to run Lua");

	const GoapAction *action =
		ActionDatabase::getSingleton().findAction("travel_to_market");
	if (!action)
		FAIL("action not found");

	const BehaviorTreeNode &bt = action->behaviorTree;
	if (bt.children.size() != 6)
		FAIL("expected 6 children");
	if (bt.children[0].type != "checkValue")
		FAIL("wrong child 0 type");
	if (bt.children[0].name != "energy")
		FAIL("wrong child 0 name");
	if (bt.children[0].params != ">= 20")
		FAIL("wrong child 0 params");
	if (bt.children[4].type != "setBit")
		FAIL("wrong child 4 type");
	if (bt.children[4].name != "at_market")
		FAIL("wrong child 4 name");
	if (bt.children[4].params != "1")
		FAIL("wrong child 4 params");
	if (bt.children[5].type != "setBit")
		FAIL("wrong child 5 type");
	if (bt.children[5].name != "at_home")
		FAIL("wrong child 5 name");
	if (bt.children[5].params != "0")
		FAIL("wrong child 5 params");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 13: Set and get bit names from Lua
// ---------------------------------------------------------------------------

static int testSetGetBitName(lua_State *L)
{
	TEST("set and get bit names from Lua");

	// Clear any previous bit names
	for (int i = 0; i < 64; i++)
		GoapBlackboard::setBitName(i, "");

	// Set bit names via Lua
	bool ok = runLua(L, "ecs.action_db.set_bit_name(0, 'has_axe');"
			    "ecs.action_db.set_bit_name(1, 'has_wood');"
			    "ecs.action_db.set_bit_name(5, 'is_hungry')");
	if (!ok)
		FAIL("failed to set bit names");

	// Verify via C++ API
	const char *name0 = GoapBlackboard::getBitName(0);
	if (!name0 || strcmp(name0, "has_axe") != 0)
		FAIL("bit 0 should be 'has_axe'");

	const char *name1 = GoapBlackboard::getBitName(1);
	if (!name1 || strcmp(name1, "has_wood") != 0)
		FAIL("bit 1 should be 'has_wood'");

	const char *name5 = GoapBlackboard::getBitName(5);
	if (!name5 || strcmp(name5, "is_hungry") != 0)
		FAIL("bit 5 should be 'is_hungry'");

	// Verify unset bits are null
	if (GoapBlackboard::getBitName(2) != nullptr)
		FAIL("bit 2 should be unset");

	// Verify via Lua get_bit_name
	ok = runLua(
		L,
		"local n0 = ecs.action_db.get_bit_name(0);"
		"assert(n0 == 'has_axe', 'expected has_axe, got ' .. tostring(n0));"
		"local n1 = ecs.action_db.get_bit_name(1);"
		"assert(n1 == 'has_wood', 'expected has_wood, got ' .. tostring(n1));"
		"local n5 = ecs.action_db.get_bit_name(5);"
		"assert(n5 == 'is_hungry', 'expected is_hungry, got ' .. tostring(n5));"
		"local n2 = ecs.action_db.get_bit_name(2);"
		"assert(n2 == nil, 'bit 2 should be nil, got ' .. tostring(n2))");
	if (!ok)
		FAIL("get_bit_name assertions failed");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 14: Find bit by name from Lua
// ---------------------------------------------------------------------------

static int testFindBitByName(lua_State *L)
{
	TEST("find bit by name from Lua");

	// Clear and set known bits
	for (int i = 0; i < 64; i++)
		GoapBlackboard::setBitName(i, "");
	GoapBlackboard::setBitName(3, "near_tree");
	GoapBlackboard::setBitName(7, "near_fire");

	bool ok = runLua(
		L,
		"local idx3 = ecs.action_db.find_bit_by_name('near_tree');"
		"assert(idx3 == 3, 'expected 3, got ' .. tostring(idx3));"
		"local idx7 = ecs.action_db.find_bit_by_name('near_fire');"
		"assert(idx7 == 7, 'expected 7, got ' .. tostring(idx7));"
		"local nilIdx = ecs.action_db.find_bit_by_name('nonexistent');"
		"assert(nilIdx == nil, 'expected nil, got ' .. tostring(nilIdx))");
	if (!ok)
		FAIL("find_bit_by_name assertions failed");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 15: Auto-assign bit from Lua
// ---------------------------------------------------------------------------

static int testAutoAssignBit(lua_State *L)
{
	TEST("auto-assign bit from Lua");

	// Clear all bits
	for (int i = 0; i < 64; i++)
		GoapBlackboard::setBitName(i, "");

	// Auto-assign a new name
	bool ok = runLua(
		L,
		"local idx = ecs.action_db.auto_assign_bit('my_flag');"
		"assert(idx == 0, 'expected first free slot 0, got ' .. tostring(idx));"
		"local name = ecs.action_db.get_bit_name(0);"
		"assert(name == 'my_flag', 'expected my_flag, got ' .. tostring(name))");
	if (!ok)
		FAIL("first auto_assign failed");

	// Auto-assign another - should get slot 1
	ok = runLua(
		L,
		"local idx = ecs.action_db.auto_assign_bit('other_flag');"
		"assert(idx == 1, 'expected slot 1, got ' .. tostring(idx))");
	if (!ok)
		FAIL("second auto_assign failed");

	// Auto-assign an already-existing name - should return existing index
	ok = runLua(
		L,
		"local idx = ecs.action_db.auto_assign_bit('my_flag');"
		"assert(idx == 0, 'expected existing slot 0, got ' .. tostring(idx))");
	if (!ok)
		FAIL("re-assign existing name failed");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 16: List bit names from Lua
// ---------------------------------------------------------------------------

static int testListBitNames(lua_State *L)
{
	TEST("list bit names from Lua");

	// Clear and set known bits
	for (int i = 0; i < 64; i++)
		GoapBlackboard::setBitName(i, "");
	GoapBlackboard::setBitName(0, "alpha");
	GoapBlackboard::setBitName(5, "beta");
	GoapBlackboard::setBitName(10, "gamma");

	bool ok = runLua(
		L,
		"local bits = ecs.action_db.list_bit_names();"
		"assert(#bits == 3, 'expected 3 bits, got ' .. #bits);"
		"assert(bits[1].index == 0 and bits[1].name == 'alpha', 'wrong bit 0');"
		"assert(bits[2].index == 5 and bits[2].name == 'beta', 'wrong bit 5');"
		"assert(bits[3].index == 10 and bits[3].name == 'gamma', 'wrong bit 10')");
	if (!ok)
		FAIL("list_bit_names assertions failed");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 17: Bit names auto-assigned when used in action preconditions
// ---------------------------------------------------------------------------

static int testAutoAssignInAction(lua_State *L)
{
	TEST("bit names auto-assigned in action preconditions");

	// Clear all bits
	for (int i = 0; i < 64; i++)
		GoapBlackboard::setBitName(i, "");

	// Create an action with a bit name that hasn't been defined yet
	bool ok = runLua(L, "ecs.action_db.add_action('test_auto_bit', 1,"
			    "  { bits = { brand_new_bit = true } },"
			    "  { bits = { another_new_bit = true } })");
	if (!ok)
		FAIL("failed to create action with auto-assigned bits");

	// The bits should have been auto-assigned
	int idx1 = GoapBlackboard::findBitByName("brand_new_bit");
	if (idx1 < 0)
		FAIL("brand_new_bit should have been auto-assigned");
	if (idx1 != 0)
		FAIL("brand_new_bit should be at slot 0");

	int idx2 = GoapBlackboard::findBitByName("another_new_bit");
	if (idx2 < 0)
		FAIL("another_new_bit should have been auto-assigned");
	if (idx2 != 1)
		FAIL("another_new_bit should be at slot 1");

	// Verify the action's preconditions use the correct bit
	const GoapAction *action =
		ActionDatabase::getSingleton().findAction("test_auto_bit");
	if (!action)
		FAIL("action not found");
	if (!action->preconditions.getBit(idx1))
		FAIL("precondition bit should be set");
	if (!action->effects.getBit(idx2))
		FAIL("effect bit should be set");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

int main()
{
	printf("ActionDatabase Lua API Tests\n");
	printf("============================\n\n");

	// Create Lua state
	lua_State *L = luaL_newstate();
	if (!L) {
		fprintf(stderr, "Failed to create Lua state\n");
		return 1;
	}
	luaL_openlibs(L);

	// Register the action API
	editScene::registerLuaActionApi(L);

	// Run tests
	int failures = 0;
	failures += testBasicAction(L);
	failures += testActionWithBehaviorTree(L);
	failures += testNestedBehaviorTree(L);
	failures += testGoal(L);
	failures += testActionReplacement(L);
	failures += testRemoveAction(L);
	failures += testListActions(L);
	failures += testFindActionLua(L);
	failures += testClear(L);
	failures += testInventoryBehaviorTree(L);
	failures += testPhysicsBehaviorTree(L);
	failures += testBlackboardCheckBehaviorTree(L);
	failures += testSetGetBitName(L);
	failures += testFindBitByName(L);
	failures += testAutoAssignBit(L);
	failures += testListBitNames(L);
	failures += testAutoAssignInAction(L);

	// Cleanup
	lua_close(L);

	printf("\nResults: %d/%d passed, %d failed\n", passCount, testCount,
	       failures);

	return failures > 0 ? 1 : 0;
}
