/**
 * @file behavior_tree_lua_test.cpp
 * @brief Standalone test for Behavior Tree Lua API.
 *
 * This test creates a Lua state, registers the behavior tree Lua API,
 * then runs Lua scripts that register node handlers, create nodes,
 * list/unregister handlers, and verify the results.
 *
 * Build with:
 *   g++ -std=c++17 -I. -I../.. -I../../lua/lua-5.4.8/src \
 *       behavior_tree_lua_test.cpp \
 *       ../lua/LuaBehaviorTreeApi.cpp \
 *       ../../lua/lua-5.4.8/src/liblua.a \
 *       -o behavior_tree_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/BehaviorTree.hpp"
#include "../components/GoapBlackboard.hpp"

// Forward declare the registration function
namespace editScene
{
void registerLuaBehaviorTreeApi(lua_State *L);
int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
			    uint64_t entityId, const std::string &params);
}

// ---------------------------------------------------------------------------
// 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", std::string(msg).c_str()); \
		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;
}

// ---------------------------------------------------------------------------
// Helper: get a global integer from Lua
// ---------------------------------------------------------------------------

static int getGlobalInt(lua_State *L, const char *name)
{
	lua_getglobal(L, name);
	int val = (int)lua_tointeger(L, -1);
	lua_pop(L, 1);
	return val;
}

// ---------------------------------------------------------------------------
// Helper: get a global string from Lua
// ---------------------------------------------------------------------------

static std::string getGlobalString(lua_State *L, const char *name)
{
	lua_getglobal(L, name);
	const char *s = lua_tostring(L, -1);
	std::string val = s ? s : "";
	lua_pop(L, 1);
	return val;
}

// ---------------------------------------------------------------------------
// Test 1: Register a node handler and verify it's stored
// ---------------------------------------------------------------------------

static int testRegisterNode(lua_State *L)
{
	TEST("register a node handler");

	bool ok = runLua(L, "ecs.behavior_tree.register_node('test_node', "
			    "  function(entity_id, params) "
			    "    return 'success' "
			    "  end"
			    ")");
	if (!ok)
		FAIL("failed to run Lua");

	// Verify via list_nodes
	ok = runLua(L, "local nodes = ecs.behavior_tree.list_nodes()\n"
		       "found = false\n"
		       "for i, name in ipairs(nodes) do\n"
		       "  if name == 'test_node' then found = true end\n"
		       "end\n"
		       "test_result = found\n");
	if (!ok)
		FAIL("failed to list nodes");

	lua_getglobal(L, "test_result");
	if (!lua_toboolean(L, -1))
		FAIL("test_node not found in list");
	lua_pop(L, 1);

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 2: Register multiple nodes and list them
// ---------------------------------------------------------------------------

static int testListNodes(lua_State *L)
{
	TEST("list multiple registered nodes");

	bool ok = runLua(L, "ecs.behavior_tree.register_node('node_a', "
			    "  function(e, p) return 'success' end)\n"
			    "ecs.behavior_tree.register_node('node_b', "
			    "  function(e, p) return 'failure' end)\n"
			    "ecs.behavior_tree.register_node('node_c', "
			    "  function(e, p) return 'running' end)\n"
			    "local nodes = ecs.behavior_tree.list_nodes()\n"
			    "count = #nodes\n");
	if (!ok)
		FAIL("failed to register/list nodes");

	int count = getGlobalInt(L, "count");
	if (count < 3)
		FAIL("expected at least 3 nodes");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 3: Unregister a node handler
// ---------------------------------------------------------------------------

static int testUnregisterNode(lua_State *L)
{
	TEST("unregister a node handler");

	bool ok = runLua(
		L,
		"local removed = ecs.behavior_tree.unregister_node('node_a')\n"
		"unreg_result = removed\n"
		"local nodes = ecs.behavior_tree.list_nodes()\n"
		"found_a = false\n"
		"for i, name in ipairs(nodes) do\n"
		"  if name == 'node_a' then found_a = true end\n"
		"end\n");
	if (!ok)
		FAIL("failed to unregister node");

	lua_getglobal(L, "unreg_result");
	if (!lua_toboolean(L, -1))
		FAIL("unregister_node returned false");
	lua_pop(L, 1);

	lua_getglobal(L, "found_a");
	if (lua_toboolean(L, -1))
		FAIL("node_a still found after unregister");
	lua_pop(L, 1);

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 4: Unregister non-existent node returns false
// ---------------------------------------------------------------------------

static int testUnregisterNonExistent(lua_State *L)
{
	TEST("unregister non-existent node returns false");

	bool ok = runLua(
		L,
		"local removed = ecs.behavior_tree.unregister_node('nonexistent')\n"
		"unreg_none = removed\n");
	if (!ok)
		FAIL("failed to run Lua");

	lua_getglobal(L, "unreg_none");
	if (lua_toboolean(L, -1))
		FAIL("unregister of non-existent should return false");
	lua_pop(L, 1);

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 5: Create a behavior tree node via create_node
// ---------------------------------------------------------------------------

static int testCreateNode(lua_State *L)
{
	TEST("create_node returns correct node table");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('test_node', "
		   "  'key=val,count=42')\n"
		   "node_type = node.type\n"
		   "node_name = node.name\n"
		   "node_params = node.params\n");
	if (!ok)
		FAIL("failed to create node");

	std::string type = getGlobalString(L, "node_type");
	std::string name = getGlobalString(L, "node_name");
	std::string params = getGlobalString(L, "node_params");

	if (type != "luaTask")
		FAIL("expected type 'luaTask', got '" + type + "'");
	if (name != "test_node")
		FAIL("expected name 'test_node', got '" + name + "'");
	if (params != "key=val,count=42")
		FAIL("expected params 'key=val,count=42', got '" + params +
		     "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 6: Create node without params (optional)
// ---------------------------------------------------------------------------

static int testCreateNodeNoParams(lua_State *L)
{
	TEST("create_node without params");

	// Register a Lua handler first so create_node creates a luaTask node
	bool ok = runLua(L, "ecs.behavior_tree.register_node('simple_node', "
			    "  function(e, p) return 'success' end)\n");
	if (!ok)
		FAIL("failed to register simple_node handler");

	ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('simple_node')\n"
		   "no_params_type = node.type\n"
		   "no_params_name = node.name\n"
		   "no_params_has_params = (node.params ~= nil)\n");
	if (!ok)
		FAIL("failed to create node without params");

	std::string type = getGlobalString(L, "no_params_type");
	std::string name = getGlobalString(L, "no_params_name");

	if (type != "luaTask")
		FAIL("expected type 'luaTask'");
	if (name != "simple_node")
		FAIL("expected name 'simple_node'");

	// params should be nil or empty when not provided
	lua_getglobal(L, "no_params_has_params");
	if (lua_toboolean(L, -1)) {
		// It's okay if params is set to empty string
		lua_getglobal(L, "node_params");
		const char *p = lua_tostring(L, -1);
		if (p && p[0] != '\0') {
			FAIL("expected no params, got '" + std::string(p) +
			     "'");
		}
		lua_pop(L, 1);
	}
	lua_pop(L, 1);

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 7: Create node and use in a behavior tree structure
// ---------------------------------------------------------------------------

static int testCreateNodeInTree(lua_State *L)
{
	TEST("create_node used in behavior tree structure");

	// Register Lua handlers first so create_node creates luaTask nodes
	bool ok = runLua(L,
			 "ecs.behavior_tree.register_node('say_hello', "
			 "  function(e, p) return 'success' end)\n"
			 "ecs.behavior_tree.register_node('wait_for_duration', "
			 "  function(e, p) return 'success' end)\n");
	if (!ok)
		FAIL("failed to register Lua handlers");

	ok = runLua(L, "local tree = {\n"
		       "  type = 'sequence',\n"
		       "  children = {\n"
		       "    ecs.behavior_tree.create_node('say_hello', "
		       "      'message=Hello'),\n"
		       "    ecs.behavior_tree.create_node('wait_for_duration', "
		       "      'duration=2.0,timer_key=test'),\n"
		       "    ecs.behavior_tree.create_node('say_hello', "
		       "      'message=Done')\n"
		       "  }\n"
		       "}\n"
		       "tree_type = tree.type\n"
		       "child_count = #tree.children\n"
		       "child0_type = tree.children[1].type\n"
		       "child0_name = tree.children[1].name\n"
		       "child0_params = tree.children[1].params\n"
		       "child1_type = tree.children[2].type\n"
		       "child1_name = tree.children[2].name\n"
		       "child1_params = tree.children[2].params\n");
	if (!ok)
		FAIL("failed to create tree with Lua nodes");

	std::string treeType = getGlobalString(L, "tree_type");
	int childCount = getGlobalInt(L, "child_count");

	if (treeType != "sequence")
		FAIL("expected tree type 'sequence'");
	if (childCount != 3)
		FAIL("expected 3 children");

	std::string c0type = getGlobalString(L, "child0_type");
	std::string c0name = getGlobalString(L, "child0_name");
	std::string c0params = getGlobalString(L, "child0_params");

	if (c0type != "luaTask")
		FAIL("child 0: expected type 'luaTask'");
	if (c0name != "say_hello")
		FAIL("child 0: expected name 'say_hello'");
	if (c0params != "message=Hello")
		FAIL("child 0: expected params 'message=Hello'");

	std::string c1type = getGlobalString(L, "child1_type");
	std::string c1name = getGlobalString(L, "child1_name");
	std::string c1params = getGlobalString(L, "child1_params");

	if (c1type != "luaTask")
		FAIL("child 1: expected type 'luaTask'");
	if (c1name != "wait_for_duration")
		FAIL("child 1: expected name 'wait_for_duration'");
	if (c1params != "duration=2.0,timer_key=test")
		FAIL("child 1: unexpected params");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 8: Re-register a node (replace existing handler)
// ---------------------------------------------------------------------------

static int testReregisterNode(lua_State *L)
{
	TEST("re-register a node handler (replace)");

	bool ok = runLua(
		L,
		"ecs.behavior_tree.register_node('replaceable', "
		"  function(e, p) return 'failure' end)\n"
		"ecs.behavior_tree.register_node('replaceable', "
		"  function(e, p) return 'success' end)\n"
		"local nodes = ecs.behavior_tree.list_nodes()\n"
		"found_count = 0\n"
		"for i, name in ipairs(nodes) do\n"
		"  if name == 'replaceable' then found_count = found_count + 1 end\n"
		"end\n");
	if (!ok)
		FAIL("failed to re-register node");

	int count = getGlobalInt(L, "found_count");
	if (count != 1)
		FAIL("expected exactly 1 'replaceable' after re-register, got " +
		     std::to_string(count));

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 9: Empty list when no nodes registered
// ---------------------------------------------------------------------------

static int testEmptyList(lua_State *L)
{
	TEST("list_nodes returns empty when no nodes");

	// Unregister all nodes we created (including those from tests 6 and 7)
	bool ok = runLua(
		L, "ecs.behavior_tree.unregister_node('test_node')\n"
		   "ecs.behavior_tree.unregister_node('node_b')\n"
		   "ecs.behavior_tree.unregister_node('node_c')\n"
		   "ecs.behavior_tree.unregister_node('replaceable')\n"
		   "ecs.behavior_tree.unregister_node('simple_node')\n"
		   "ecs.behavior_tree.unregister_node('say_hello')\n"
		   "ecs.behavior_tree.unregister_node('wait_for_duration')\n"
		   "local nodes = ecs.behavior_tree.list_nodes()\n"
		   "empty_count = #nodes\n");
	if (!ok)
		FAIL("failed to unregister all nodes");

	int count = getGlobalInt(L, "empty_count");
	if (count != 0)
		FAIL("expected empty list, got " + std::to_string(count) +
		     " nodes");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 10: Register node with invalid arguments
// ---------------------------------------------------------------------------

static int testRegisterInvalidArgs(lua_State *L)
{
	TEST("register_node with invalid args (no function)");

	// This should error - we just verify it doesn't crash
	bool ok = runLua(L, "local ok, err = pcall(function()\n"
			    "  ecs.behavior_tree.register_node('bad_node')\n"
			    "end)\n"
			    "pcall_ok = ok\n");
	if (!ok)
		FAIL("failed to run pcall");

	lua_getglobal(L, "pcall_ok");
	if (lua_toboolean(L, -1))
		FAIL("expected register_node without function to error");
	lua_pop(L, 1);

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 11: Full pipeline - register, create, and evaluate a node
// ---------------------------------------------------------------------------
// This test exercises the complete flow: register a Lua handler,
// create a node table, then call callLuaBehaviorTreeNode() to
// evaluate it and verify the return value.

static int testFullPipeline(lua_State *L)
{
	TEST("full pipeline: register, create, evaluate");

	// Register a node that returns "success"
	bool ok = runLua(L, "ecs.behavior_tree.register_node('pipeline_test', "
			    "  function(entity_id, params) "
			    "    return 'success' "
			    "  end"
			    ")");
	if (!ok)
		FAIL("failed to register pipeline_test node");

	// Call the C++ evaluation function
	int result = editScene::callLuaBehaviorTreeNode(L, "pipeline_test", 42,
							"msg=hello");

	if (result != 0)
		FAIL("expected 0 (success), got " + std::to_string(result));

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 12: Full pipeline - node returning "failure"
// ---------------------------------------------------------------------------

static int testFullPipelineFailure(lua_State *L)
{
	TEST("full pipeline: node returning failure");

	bool ok = runLua(L, "ecs.behavior_tree.register_node('fail_test', "
			    "  function(entity_id, params) "
			    "    return 'failure' "
			    "  end"
			    ")");
	if (!ok)
		FAIL("failed to register fail_test node");

	int result = editScene::callLuaBehaviorTreeNode(L, "fail_test", 42, "");

	if (result != 1)
		FAIL("expected 1 (failure), got " + std::to_string(result));

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 13: Full pipeline - node returning "running"
// ---------------------------------------------------------------------------

static int testFullPipelineRunning(lua_State *L)
{
	TEST("full pipeline: node returning running");

	bool ok = runLua(L, "ecs.behavior_tree.register_node('run_test', "
			    "  function(entity_id, params) "
			    "    return 'running' "
			    "  end"
			    ")");
	if (!ok)
		FAIL("failed to register run_test node");

	int result = editScene::callLuaBehaviorTreeNode(L, "run_test", 42, "");

	if (result != 2)
		FAIL("expected 2 (running), got " + std::to_string(result));

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 14: Full pipeline - node that reads params
// ---------------------------------------------------------------------------

static int testFullPipelineParams(lua_State *L)
{
	TEST("full pipeline: node reading params");

	// Register a node that stores params in a global for verification
	bool ok = runLua(L, "ecs.behavior_tree.register_node('param_test', "
			    "  function(entity_id, params) "
			    "    param_count = params.count "
			    "    param_msg = params.msg "
			    "    return 'success' "
			    "  end"
			    ")");
	if (!ok)
		FAIL("failed to register param_test node");

	int result = editScene::callLuaBehaviorTreeNode(L, "param_test", 42,
							"count=42,msg=hello");

	if (result != 0)
		FAIL("expected 0 (success), got " + std::to_string(result));

	// Verify params were passed correctly
	lua_getglobal(L, "param_count");
	if (!lua_isnumber(L, -1))
		FAIL("param_count should be a number");
	int count = (int)lua_tointeger(L, -1);
	lua_pop(L, 1);
	if (count != 42)
		FAIL("expected param_count=42, got " + std::to_string(count));

	lua_getglobal(L, "param_msg");
	const char *msg = lua_tostring(L, -1);
	lua_pop(L, 1);
	if (!msg || strcmp(msg, "hello") != 0)
		FAIL(std::string("expected param_msg='hello', got '") +
		     (msg ? msg : "nil") + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 15: Full pipeline - node that reads entity_id
// ---------------------------------------------------------------------------

static int testFullPipelineEntityId(lua_State *L)
{
	TEST("full pipeline: node reading entity_id");

	bool ok = runLua(L, "ecs.behavior_tree.register_node('eid_test', "
			    "  function(entity_id, params) "
			    "    eid_result = entity_id "
			    "    return 'success' "
			    "  end"
			    ")");
	if (!ok)
		FAIL("failed to register eid_test node");

	int result =
		editScene::callLuaBehaviorTreeNode(L, "eid_test", 12345, "");

	if (result != 0)
		FAIL("expected 0 (success), got " + std::to_string(result));

	lua_getglobal(L, "eid_result");
	int entityId = (int)lua_tointeger(L, -1);
	lua_pop(L, 1);
	if (entityId != 12345)
		FAIL("expected entity_id=12345, got " +
		     std::to_string(entityId));

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 16: Full pipeline - unregistered node returns failure
// ---------------------------------------------------------------------------

static int testFullPipelineUnregistered(lua_State *L)
{
	TEST("full pipeline: unregistered node returns failure");

	int result = editScene::callLuaBehaviorTreeNode(L, "nonexistent_node",
							42, "");

	if (result != -1)
		FAIL("expected -1 (not found) for unregistered node, got " +
		     std::to_string(result));

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 17: create_node with built-in node type (setAnimationState)
// ---------------------------------------------------------------------------

static int testCreateNodeBuiltinType(lua_State *L)
{
	TEST("create_node with built-in setAnimationState type");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('setAnimationState', "
		"  'locomotion/walk')\n"
		"anim_type = node.type\n"
		"anim_name = node.name\n"
		"anim_has_params = (node.params ~= nil)\n");
	if (!ok)
		FAIL("failed to create setAnimationState node");

	std::string type = getGlobalString(L, "anim_type");
	std::string name = getGlobalString(L, "anim_name");

	if (type != "setAnimationState")
		FAIL("expected type 'setAnimationState', got '" + type + "'");
	if (name != "locomotion/walk")
		FAIL("expected name 'locomotion/walk', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 18: create_node with built-in delay type and params
// ---------------------------------------------------------------------------

static int testCreateNodeDelay(lua_State *L)
{
	TEST("create_node with built-in delay type and params");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('delay', '', '2.0')\n"
		"delay_type = node.type\n"
		"delay_params = node.params\n"
		"delay_has_name = (node.name ~= nil and node.name ~= '')\n");
	if (!ok)
		FAIL("failed to create delay node");

	std::string type = getGlobalString(L, "delay_type");
	std::string params = getGlobalString(L, "delay_params");

	if (type != "delay")
		FAIL("expected type 'delay', got '" + type + "'");
	if (params != "2.0")
		FAIL("expected params '2.0', got '" + params + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 19: create_node with built-in isAnimationEnded type
// ---------------------------------------------------------------------------

static int testCreateNodeAnimEnded(lua_State *L)
{
	TEST("create_node with built-in isAnimationEnded type");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('isAnimationEnded', "
		"  'locomotion')\n"
		"anim_end_type = node.type\n"
		"anim_end_name = node.name\n");
	if (!ok)
		FAIL("failed to create isAnimationEnded node");

	std::string type = getGlobalString(L, "anim_end_type");
	std::string name = getGlobalString(L, "anim_end_name");

	if (type != "isAnimationEnded")
		FAIL("expected type 'isAnimationEnded', got '" + type + "'");
	if (name != "locomotion")
		FAIL("expected name 'locomotion', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 20: create_node with built-in debugPrint type
// ---------------------------------------------------------------------------

static int testCreateNodeDebugPrint(lua_State *L)
{
	TEST("create_node with built-in debugPrint type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('debugPrint', "
		   "  'Hello World')\n"
		   "dbg_type = node.type\n"
		   "dbg_name = node.name\n");
	if (!ok)
		FAIL("failed to create debugPrint node");

	std::string type = getGlobalString(L, "dbg_type");
	std::string name = getGlobalString(L, "dbg_name");

	if (type != "debugPrint")
		FAIL("expected type 'debugPrint', got '" + type + "'");
	if (name != "Hello World")
		FAIL("expected name 'Hello World', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 21: create_node with built-in sendEvent type
// ---------------------------------------------------------------------------

static int testCreateNodeSendEvent(lua_State *L)
{
	TEST("create_node with built-in sendEvent type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('sendEvent', "
		   "  'quest_started', 'quest_id=42')\n"
		   "ev_type = node.type\n"
		   "ev_name = node.name\n"
		   "ev_params = node.params\n");
	if (!ok)
		FAIL("failed to create sendEvent node");

	std::string type = getGlobalString(L, "ev_type");
	std::string name = getGlobalString(L, "ev_name");
	std::string params = getGlobalString(L, "ev_params");

	if (type != "sendEvent")
		FAIL("expected type 'sendEvent', got '" + type + "'");
	if (name != "quest_started")
		FAIL("expected name 'quest_started', got '" + name + "'");
	if (params != "quest_id=42")
		FAIL("expected params 'quest_id=42', got '" + params + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 22: create_node with built-in setBit type
// ---------------------------------------------------------------------------

static int testCreateNodeSetBit(lua_State *L)
{
	TEST("create_node with built-in setBit type");

	bool ok = runLua(L,
			 "local node = ecs.behavior_tree.create_node('setBit', "
			 "  'has_sword', '1')\n"
			 "bit_type = node.type\n"
			 "bit_name = node.name\n"
			 "bit_params = node.params\n");
	if (!ok)
		FAIL("failed to create setBit node");

	std::string type = getGlobalString(L, "bit_type");
	std::string name = getGlobalString(L, "bit_name");
	std::string params = getGlobalString(L, "bit_params");

	if (type != "setBit")
		FAIL("expected type 'setBit', got '" + type + "'");
	if (name != "has_sword")
		FAIL("expected name 'has_sword', got '" + name + "'");
	if (params != "1")
		FAIL("expected params '1', got '" + params + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 23: create_node with built-in checkBit type
// ---------------------------------------------------------------------------

static int testCreateNodeCheckBit(lua_State *L)
{
	TEST("create_node with built-in checkBit type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('checkBit', "
		   "  'has_sword')\n"
		   "cbit_type = node.type\n"
		   "cbit_name = node.name\n");
	if (!ok)
		FAIL("failed to create checkBit node");

	std::string type = getGlobalString(L, "cbit_type");
	std::string name = getGlobalString(L, "cbit_name");

	if (type != "checkBit")
		FAIL("expected type 'checkBit', got '" + type + "'");
	if (name != "has_sword")
		FAIL("expected name 'has_sword', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 24: create_node with built-in setValue type
// ---------------------------------------------------------------------------

static int testCreateNodeSetValue(lua_State *L)
{
	TEST("create_node with built-in setValue type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('setValue', "
		   "  'gold', '42')\n"
		   "sv_type = node.type\n"
		   "sv_name = node.name\n"
		   "sv_params = node.params\n");
	if (!ok)
		FAIL("failed to create setValue node");

	std::string type = getGlobalString(L, "sv_type");
	std::string name = getGlobalString(L, "sv_name");
	std::string params = getGlobalString(L, "sv_params");

	if (type != "setValue")
		FAIL("expected type 'setValue', got '" + type + "'");
	if (name != "gold")
		FAIL("expected name 'gold', got '" + name + "'");
	if (params != "42")
		FAIL("expected params '42', got '" + params + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 25: create_node with built-in checkValue type
// ---------------------------------------------------------------------------

static int testCreateNodeCheckValue(lua_State *L)
{
	TEST("create_node with built-in checkValue type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('checkValue', "
		   "  'gold', '> 10')\n"
		   "cv_type = node.type\n"
		   "cv_name = node.name\n"
		   "cv_params = node.params\n");
	if (!ok)
		FAIL("failed to create checkValue node");

	std::string type = getGlobalString(L, "cv_type");
	std::string name = getGlobalString(L, "cv_name");
	std::string params = getGlobalString(L, "cv_params");

	if (type != "checkValue")
		FAIL("expected type 'checkValue', got '" + type + "'");
	if (name != "gold")
		FAIL("expected name 'gold', got '" + name + "'");
	if (params != "> 10")
		FAIL("expected params '> 10', got '" + params + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 26: create_node with built-in teleportToChild type
// ---------------------------------------------------------------------------

static int testCreateNodeTeleport(lua_State *L)
{
	TEST("create_node with built-in teleportToChild type");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('teleportToChild', "
		"  'entrance')\n"
		"tel_type = node.type\n"
		"tel_name = node.name\n");
	if (!ok)
		FAIL("failed to create teleportToChild node");

	std::string type = getGlobalString(L, "tel_type");
	std::string name = getGlobalString(L, "tel_name");

	if (type != "teleportToChild")
		FAIL("expected type 'teleportToChild', got '" + type + "'");
	if (name != "entrance")
		FAIL("expected name 'entrance', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 27: create_node with built-in disablePhysics type
// ---------------------------------------------------------------------------

static int testCreateNodeDisablePhysics(lua_State *L)
{
	TEST("create_node with built-in disablePhysics type");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('disablePhysics')\n"
		"dphys_type = node.type\n");
	if (!ok)
		FAIL("failed to create disablePhysics node");

	std::string type = getGlobalString(L, "dphys_type");
	if (type != "disablePhysics")
		FAIL("expected type 'disablePhysics', got '" + type + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 28: create_node with built-in enablePhysics type
// ---------------------------------------------------------------------------

static int testCreateNodeEnablePhysics(lua_State *L)
{
	TEST("create_node with built-in enablePhysics type");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('enablePhysics')\n"
		"ephys_type = node.type\n");
	if (!ok)
		FAIL("failed to create enablePhysics node");

	std::string type = getGlobalString(L, "ephys_type");
	if (type != "enablePhysics")
		FAIL("expected type 'enablePhysics', got '" + type + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 29: create_node with built-in blackboardDump type
// ---------------------------------------------------------------------------

static int testCreateNodeBlackboardDump(lua_State *L)
{
	TEST("create_node with built-in blackboardDump type");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('blackboardDump', "
		"  'Debug')\n"
		"bbdump_type = node.type\n"
		"bbdump_name = node.name\n");
	if (!ok)
		FAIL("failed to create blackboardDump node");

	std::string type = getGlobalString(L, "bbdump_type");
	std::string name = getGlobalString(L, "bbdump_name");

	if (type != "blackboardDump")
		FAIL("expected type 'blackboardDump', got '" + type + "'");
	if (name != "Debug")
		FAIL("expected name 'Debug', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 30: create_node with built-in hasItem type
// ---------------------------------------------------------------------------

static int testCreateNodeHasItem(lua_State *L)
{
	TEST("create_node with built-in hasItem type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('hasItem', "
		   "  'sword_01')\n"
		   "hi_type = node.type\n"
		   "hi_name = node.name\n");
	if (!ok)
		FAIL("failed to create hasItem node");

	std::string type = getGlobalString(L, "hi_type");
	std::string name = getGlobalString(L, "hi_name");

	if (type != "hasItem")
		FAIL("expected type 'hasItem', got '" + type + "'");
	if (name != "sword_01")
		FAIL("expected name 'sword_01', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 31: create_node with built-in pickupItem type
// ---------------------------------------------------------------------------

static int testCreateNodePickupItem(lua_State *L)
{
	TEST("create_node with built-in pickupItem type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('pickupItem', "
		   "  'sword_01')\n"
		   "pi_type = node.type\n"
		   "pi_name = node.name\n");
	if (!ok)
		FAIL("failed to create pickupItem node");

	std::string type = getGlobalString(L, "pi_type");
	std::string name = getGlobalString(L, "pi_name");

	if (type != "pickupItem")
		FAIL("expected type 'pickupItem', got '" + type + "'");
	if (name != "sword_01")
		FAIL("expected name 'sword_01', got '" + name + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 32: create_node with built-in addItemToInventory type
// ---------------------------------------------------------------------------

static int testCreateNodeAddItem(lua_State *L)
{
	TEST("create_node with built-in addItemToInventory type");

	bool ok = runLua(
		L,
		"local node = ecs.behavior_tree.create_node('addItemToInventory', "
		"  'potion_01', 'Potion of Healing,misc,1,0.5,10')\n"
		"ai_type = node.type\n"
		"ai_name = node.name\n"
		"ai_params = node.params\n");
	if (!ok)
		FAIL("failed to create addItemToInventory node");

	std::string type = getGlobalString(L, "ai_type");
	std::string name = getGlobalString(L, "ai_name");
	std::string params = getGlobalString(L, "ai_params");

	if (type != "addItemToInventory")
		FAIL("expected type 'addItemToInventory', got '" + type + "'");
	if (name != "potion_01")
		FAIL("expected name 'potion_01', got '" + name + "'");
	if (params != "Potion of Healing,misc,1,0.5,10")
		FAIL("expected params 'Potion of Healing,misc,1,0.5,10', got '" +
		     params + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 33: create_node with built-in sequence type (composite node)
// ---------------------------------------------------------------------------

static int testCreateNodeSequence(lua_State *L)
{
	TEST("create_node with built-in sequence type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('sequence')\n"
		   "seq_type = node.type\n");
	if (!ok)
		FAIL("failed to create sequence node");

	std::string type = getGlobalString(L, "seq_type");
	if (type != "sequence")
		FAIL("expected type 'sequence', got '" + type + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 34: create_node with built-in selector type (composite node)
// ---------------------------------------------------------------------------

static int testCreateNodeSelector(lua_State *L)
{
	TEST("create_node with built-in selector type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('selector')\n"
		   "sel_type = node.type\n");
	if (!ok)
		FAIL("failed to create selector node");

	std::string type = getGlobalString(L, "sel_type");
	if (type != "selector")
		FAIL("expected type 'selector', got '" + type + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 35: create_node with built-in invert type (decorator node)
// ---------------------------------------------------------------------------

static int testCreateNodeInvert(lua_State *L)
{
	TEST("create_node with built-in invert type");

	bool ok = runLua(
		L, "local node = ecs.behavior_tree.create_node('invert')\n"
		   "inv_type = node.type\n");
	if (!ok)
		FAIL("failed to create invert node");

	std::string type = getGlobalString(L, "inv_type");
	if (type != "invert")
		FAIL("expected type 'invert', got '" + type + "'");

	PASS();
	return 0;
}

// ---------------------------------------------------------------------------
// Test 36: create_node with built-in nodes in a full behavior tree
// ---------------------------------------------------------------------------

static int testCreateNodeFullTree(lua_State *L)
{
	TEST("create_node with built-in nodes in a full behavior tree");

	bool ok = runLua(
		L,
		"local tree = {\n"
		"  type = 'selector',\n"
		"  children = {\n"
		"    {\n"
		"      type = 'sequence',\n"
		"      children = {\n"
		"        ecs.behavior_tree.create_node('checkBit', 'has_sword'),\n"
		"        ecs.behavior_tree.create_node('debugPrint', 'Already have sword'),\n"
		"        ecs.behavior_tree.create_node('setAnimationState', 'locomotion/idle')\n"
		"      }\n"
		"    },\n"
		"    {\n"
		"      type = 'sequence',\n"
		"      children = {\n"
		"        ecs.behavior_tree.create_node('setAnimationState', 'locomotion/walk'),\n"
		"        ecs.behavior_tree.create_node('delay', '', '2.0'),\n"
		"        ecs.behavior_tree.create_node('addItemToInventory', 'sword_01', "
		"'Iron Sword,weapon,1,2.5,50'),\n"
		"        ecs.behavior_tree.create_node('setAnimationState', 'locomotion/idle'),\n"
		"        ecs.behavior_tree.create_node('debugPrint', 'Got the sword!')\n"
		"      }\n"
		"    }\n"
		"  }\n"
		"}\n"
		"full_tree_type = tree.type\n"
		"full_tree_children = #tree.children\n"
		"full_c0_type = tree.children[1].type\n"
		"full_c0_children = #tree.children[1].children\n"
		"full_c0_c0_type = tree.children[1].children[1].type\n"
		"full_c0_c0_name = tree.children[1].children[1].name\n"
		"full_c1_type = tree.children[2].type\n"
		"full_c1_children = #tree.children[2].children\n"
		"full_c1_c2_type = tree.children[2].children[3].type\n"
		"full_c1_c2_name = tree.children[2].children[3].name\n");
	if (!ok)
		FAIL("failed to create full behavior tree");

	std::string treeType = getGlobalString(L, "full_tree_type");
	int childCount = getGlobalInt(L, "full_tree_children");

	if (treeType != "selector")
		FAIL("expected tree type 'selector'");
	if (childCount != 2)
		FAIL("expected 2 children");

	std::string c0type = getGlobalString(L, "full_c0_type");
	int c0children = getGlobalInt(L, "full_c0_children");
	if (c0type != "sequence")
		FAIL("expected child 0 type 'sequence'");
	if (c0children != 3)
		FAIL("expected child 0 to have 3 children");

	std::string c0c0type = getGlobalString(L, "full_c0_c0_type");
	std::string c0c0name = getGlobalString(L, "full_c0_c0_name");
	if (c0c0type != "checkBit")
		FAIL("expected child 0.0 type 'checkBit'");
	if (c0c0name != "has_sword")
		FAIL("expected child 0.0 name 'has_sword'");

	std::string c1type = getGlobalString(L, "full_c1_type");
	int c1children = getGlobalInt(L, "full_c1_children");
	if (c1type != "sequence")
		FAIL("expected child 1 type 'sequence'");
	if (c1children != 5)
		FAIL("expected child 1 to have 5 children");

	std::string c1c2type = getGlobalString(L, "full_c1_c2_type");
	std::string c1c2name = getGlobalString(L, "full_c1_c2_name");
	if (c1c2type != "addItemToInventory")
		FAIL("expected child 1.2 type 'addItemToInventory'");
	if (c1c2name != "sword_01")
		FAIL("expected child 1.2 name 'sword_01'");

	PASS();
	return 0;
}

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

int main()
{
	printf("Behavior Tree 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 behavior tree API
	editScene::registerLuaBehaviorTreeApi(L);

	// Run tests
	int failures = 0;
	failures += testRegisterNode(L);
	failures += testListNodes(L);
	failures += testUnregisterNode(L);
	failures += testUnregisterNonExistent(L);
	failures += testCreateNode(L);
	failures += testCreateNodeNoParams(L);
	failures += testCreateNodeInTree(L);
	failures += testReregisterNode(L);
	failures += testEmptyList(L);
	failures += testRegisterInvalidArgs(L);
	failures += testFullPipeline(L);
	failures += testFullPipelineFailure(L);
	failures += testFullPipelineRunning(L);
	failures += testFullPipelineParams(L);
	failures += testFullPipelineEntityId(L);
	failures += testFullPipelineUnregistered(L);
	failures += testCreateNodeBuiltinType(L);
	failures += testCreateNodeDelay(L);
	failures += testCreateNodeAnimEnded(L);
	failures += testCreateNodeDebugPrint(L);
	failures += testCreateNodeSendEvent(L);
	failures += testCreateNodeSetBit(L);
	failures += testCreateNodeCheckBit(L);
	failures += testCreateNodeSetValue(L);
	failures += testCreateNodeCheckValue(L);
	failures += testCreateNodeTeleport(L);
	failures += testCreateNodeDisablePhysics(L);
	failures += testCreateNodeEnablePhysics(L);
	failures += testCreateNodeBlackboardDump(L);
	failures += testCreateNodeHasItem(L);
	failures += testCreateNodePickupItem(L);
	failures += testCreateNodeAddItem(L);
	failures += testCreateNodeSequence(L);
	failures += testCreateNodeSelector(L);
	failures += testCreateNodeInvert(L);
	failures += testCreateNodeFullTree(L);

	// Cleanup
	lua_close(L);

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

	return failures > 0 ? 1 : 0;
}
