/**
 * @file Script.cpp
 * @brief 脚本组件存储、脚本注册表与逐帧更新实现。
 * @details
 * 该文件负责脚本系统运行时核心流程：
 * - 维护脚本实例数组与 script_id 到实例下标映射；
 * - 维护脚本工厂注册表，支持按哈希查询创建函数；
 * - 在脚本回调中缓存变换修改，并在帧末批量提交到 Transform 系统。
 */
#include "Script.h"
#include "Entity.h"
#include "Transform.h"

#define USE_TRANSFORM_CACHE_MAP 1


namespace XEngine::script {
namespace {
/**
 * @brief 活跃脚本实例数组。
 */
utl::vector<detail::script_ptr>					entity_scripts;
/**
 * @brief script_id 索引到实例数组下标的映射。
 */
utl::vector<id::id_type>						id_mapping;

/**
 * @brief script_id 代数数组。
 */
utl::vector<id::generation_type>				generations;
/**
 * @brief 可复用 script_id 队列。
 */
utl::deque<script_id>							free_ids;

/**
 * @brief 脚本对 Transform 的延迟修改缓存。
 */
utl::vector<transform::component_cache>			transform_cache;

#if USE_TRANSFORM_CACHE_MAP
std::unordered_map<id::id_type, u32>			cache_map;
#endif


using script_registry = std::unordered_map<size_t, detail::script_creator>;


/**
 * @brief 获取全局脚本工厂注册表。
 * @return 注册表引用。
 */
script_registry&
registery()
{
	static script_registry reg;
	return reg;
}
#ifdef USE_WITH_EDITOR
/**
 * @brief 获取编辑器脚本名集合。
 * @return 脚本名数组引用。
 */
utl::vector<std::string>&
script_names()
{
	static utl::vector<std::string> names;
	return names;
}

#endif

/**
 * @brief 判断脚本组件是否存在且有效。
 * @param id 脚本组件 ID。
 * @return 存在返回 true。
 */
bool
exists(script_id id)
{
	assert(id::is_valid(id));
	const id::id_type index{ id::index(id) };
	assert(index < generations.size() && id_mapping[index] < entity_scripts.size());
	assert(generations[index] == id::generation(id));
	return (generations[index] == id::generation(id) &&
		entity_scripts[id_mapping[index]] &&
		entity_scripts[id_mapping[index]]->is_valid());
}

#if USE_TRANSFORM_CACHE_MAP
/**
 * @brief 获取实体对应的 Transform 缓存项。
 * @param entity 目标实体。
 * @return 缓存项指针。
 */
transform::component_cache *const
get_cache_ptr(const game_entity::entity *const entity)
{
	assert(game_entity::is_alive((*entity).get_id()));
	const transform::transform_id id{ (*entity).transform().get_id() };

	u32 index{ u32_invalid_id };
	auto pair = cache_map.try_emplace(id, id::invalid_id);

	if (pair.second)
	{
		index = (u32)transform_cache.size();
		transform_cache.emplace_back();
		transform_cache.back().id = id;
		cache_map[id] = index;
	}
	else
	{
		index = cache_map[id];
	}

	assert(index < transform_cache.size());
	return &transform_cache[index];
}
#else
/**
 * @brief 线性查找方式获取 Transform 缓存项。
 * @param entity 目标实体。
 * @return 缓存项指针。
 */
transform::component_cache *const
get_cache_ptr(const game_entity::entity *const entity)
{
	assert(game_entity::is_alive((*entity).get_id()));
	const  transform::transform_id id{ (*entity).transform().get_id() };

	for (auto& cache : transform_cache)
	{
		if (cache.id == id)
		{
			return &cache;
		}
	}

	transform_cache.emplace_back();
	transform_cache.back().id = id;

	return &transform_cache.back();
}
#endif
}// namespace


namespace detail {

/**
 * @brief 注册脚本工厂函数。
 * @param tag 脚本名哈希。
 * @param func 工厂函数。
 * @return 注册成功返回 1。
 */
u8
register_script(size_t tag, script_creator func)
{
	bool result{ registery().insert(script_registry::value_type{tag, func}).second };
	assert(result);
	return result;
}

/**
 * @brief 按哈希获取脚本工厂函数。
 * @param tag 脚本名哈希。
 * @return 脚本工厂函数。
 */
script_creator
get_script_creator(size_t tag)
{
	auto script = XEngine::script::registery().find(tag);
	assert(script != XEngine::script::registery().end() && script->first == tag);
	return script->second;
}

#ifdef USE_WITH_EDITOR
/**
 * @brief 记录脚本名称供编辑器使用。
 * @param name 脚本类名。
 * @return 写入成功返回 1。
 */
u8
add_script_name(const char* name)
{
	script_names().emplace_back(name);
	return true;
}

#endif

}// namespace detail


/**
 * @brief 创建脚本组件并实例化脚本对象。
 * @param info 脚本初始化参数。
 * @param entity 绑定实体。
 * @return 脚本组件句柄。
 */
component
create(init_info info, game_entity::entity entity)
{
	assert(entity.is_valid());
	assert(info.script_creator);

	script_id id{};
	if (free_ids.size() > id::min_deleted_elements)
	{
		id = free_ids.front();
		assert(!exists(id));
		free_ids.pop_front();
		id = script_id{ id::new_generation(id) };
		++generations[id::index(id)];

	}
	else
	{
		id = script_id{ (id::id_type)id_mapping.size() };
		id_mapping.emplace_back();
		generations.push_back(0);
	}
	assert(id::is_valid(id));
	const id::id_type index{ (id::id_type)entity_scripts.size() };
	entity_scripts.emplace_back(info.script_creator(entity));
	assert(entity_scripts.back()->get_id() == entity.get_id());
	id_mapping[id::index(id)] = index;

	return component{ id };
}


/**
 * @brief 删除脚本组件并更新映射关系。
 * @param c 脚本组件句柄。
 */
void
remove(component c)
{
	assert(c.is_valid() && exists(c.get_id()));
	const script_id id{ c.get_id() };
	const id::id_type index{ id::index(id) };
	const script_id last_id{ entity_scripts.back()->script().get_id() };
	utl::erase_unordered(entity_scripts, index);
	id_mapping[id::index(last_id)] = index;
	id_mapping[id::index(id)] = id::invalid_id;
}

/**
 * @brief 执行脚本逐帧更新并提交变换缓存。
 * @param dt 帧时间步长。
 */
void
update(float dt) {
	for (auto& ptr : entity_scripts)
	{
		ptr->update(dt);
	}

	if (transform_cache.size())
	{
		transform::update(transform_cache.data(), (u32)transform_cache.size());
		transform_cache.clear();

#if USE_TRANSFORM_CACHE_MAP
		cache_map.clear();
#endif
	}
}

/**
 * @brief 缓存脚本写入的旋转变更。
 * @param entity 目标实体。
 * @param rotation_quaternion 旋转四元数。
 */
void
entity_script::set_rotation(const game_entity::entity *const entity, math::v4 rotation_quaternion)
{
	transform::component_cache& cache{ *get_cache_ptr(entity) };
	cache.flags |= transform::component_flags::rotation;
	cache.rotation = rotation_quaternion;
}
/**
 * @brief 缓存脚本写入的朝向变更。
 * @param entity 目标实体。
 * @param orientation_vector 朝向向量。
 */
void
entity_script::set_orientation(const game_entity::entity *const entity, math::v3 orientation_vector)
{
	transform::component_cache& cache{ *get_cache_ptr(entity) };
	cache.flags |= transform::component_flags::orientation;
	cache.orientation = orientation_vector;
}
/**
 * @brief 缓存脚本写入的位置变更。
 * @param entity 目标实体。
 * @param position 位置向量。
 */
void
entity_script::set_position(const game_entity::entity *const entity, math::v3 position)
{
	transform::component_cache& cache{ *get_cache_ptr(entity) };
	cache.flags |= transform::component_flags::position;
	cache.position = position;
}
/**
 * @brief 缓存脚本写入的缩放变更。
 * @param entity 目标实体。
 * @param scale 缩放向量。
 */
void
entity_script::set_scale(const game_entity::entity *const entity, math::v3 scale)
{
	transform::component_cache& cache{ *get_cache_ptr(entity) };
	cache.flags |= transform::component_flags::scale;
	cache.scale = scale;
}

} // namespace script

#ifdef USE_WITH_EDITOR 

/**
 * @brief ATL 的 SAFEARRAY 封装头。
 * @details
 * 该头文件提供 CComSafeArray，用于在编辑器导出路径中构造 COM 安全数组，
 * 以便把脚本名列表跨 DLL 边界返回给外部调用方。
 */
#include <atlsafe.h>

/**
 * @brief 导出脚本名列表给编辑器端。
 * @details
 * 返回值为 LPSAFEARRAY（SAFEARRAY*），元素类型为 BSTR。
 * 内部流程：
 * - 从 script_names() 读取脚本名数量；
 * - 为空时返回 nullptr；
 * - 使用 CComSafeArray<BSTR> 分配数组；
 * - 逐项把 std::string 转成 BSTR 填入数组；
 * - 通过 Detach 转移 SAFEARRAY 所有权给调用方。
 * 调用方在不再使用后应负责释放 SAFEARRAY。
 */
extern "C" __declspec(dllexport)
LPSAFEARRAY
get_script_names()
{
	// 读取当前已注册脚本名数量。
	const u32 size{ (u32)XEngine::script::script_names().size() };

	// 没有脚本时返回空指针，避免创建空 SAFEARRAY。
	if (!size) return nullptr;

	// 创建固定长度的 BSTR 安全数组。
	CComSafeArray<BSTR> names(size);

	// 将脚本名逐项转换为 BSTR 并写入数组。
	for (u32 i{ 0 }; i < size; ++i)
	{
		names.SetAt(i, A2BSTR_EX(XEngine::script::script_names()[i].c_str()), false);
	}
	// 释放 CComSafeArray 对底层 SAFEARRAY 的管理并返回给调用方。
	return names.Detach();
}
#endif
