Utility = Utility or Require("lib/utility/client/utility.lua")
Ids = Ids or Require("lib/utility/shared/ids.lua")
Point = Point or Require("lib/points/client/points.lua")
Behaviors = Behaviors or Require("lib/entities/shared/behaviors.lua")
local Entities = {} -- Stores entity data received from server
ClientEntity = {
    Invoked = {}
} -- Renamed from BaseEntity
ClientEntity.Behaviors = Behaviors

--------------------

local function SpawnEntity(entityData)
    if entityData.spawned and DoesEntityExist(entityData.spawned) then return end -- Already spawned
    if entityData.model then 
        local loaded, model = Utility.LoadModel(entityData.model)
        if not loaded then
            print(string.format("[ClientEntity] Failed to load model %s for entity %s", entityData.model, entityData.id))
            return
        end

        local entity = nil
        local coords = entityData.coords
        local rotation = entityData.rotation or vector3(0.0, 0.0, entityData.heading or 0.0) -- Default rotation if not provided

        if entityData.entityType == 'object' then
            entity = CreateObject(model, coords.x, coords.y, coords.z, false, false, false)
            SetEntityRotation(entity, rotation.x, rotation.y, rotation.z, 2, true)
        elseif entityData.entityType == 'ped' then
            entity = CreatePed(4, model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
        elseif entityData.entityType == 'vehicle' then
            entity = CreateVehicle(model, coords.x, coords.y, coords.z, type(rotation) == 'number' and rotation or rotation.z, false, false)
        else
            print(string.format("[ClientEntity] Unknown entity type '%s' for entity %s", entityData.entityType, entityData.id))
        end
        if entity and model then
            entityData.spawned = entity
            SetModelAsNoLongerNeeded(model)
            SetEntityAsMissionEntity(entity, true, true)
            FreezeEntityPosition(entity, true)
        else
            SetModelAsNoLongerNeeded(model)
        end
    end
    if entityData.OnSpawn then
        pcall(function (...)
            return entityData.OnSpawn(entityData)
        end)        
    end
    Behaviors.Trigger("OnSpawn", entityData)
end

local function RemoveEntity(entityData)
    entityData = entityData and entityData.args or entityData
    if not entityData then return end
    Behaviors.Trigger("OnRemove", entityData)
    if entityData.OnRemove then
        pcall(function (...)
            return entityData.OnRemove(entityData)
        end)
    end
    if entityData.spawned and DoesEntityExist(entityData.spawned) then
        local entityHandle = entityData.spawned
        entityData.spawned = nil
        SetEntityAsMissionEntity(entityHandle, true, true)
        DeleteEntity(entityHandle)
    end
end

local function UpdateEntity(_entityData)
    local entityData = ClientEntity.Get(_entityData.id)
    if not entityData then return end
    if entityData.OnUpdate then
        pcall(function (...)
            return entityData.OnUpdate(entityData)
        end)
    end
    Behaviors.Trigger("OnUpdate", entityData)
    local coords = vector3(entityData.coords.x, entityData.coords.y, entityData.coords.z)
    entityData.oldCoords = entityData.oldCoords and vector3(entityData.oldCoords.x, entityData.oldCoords.y, entityData.oldCoords.z) or coords
    local dist = #(coords - entityData.oldCoords)
    if entityData.oldCoords and dist > 0.5 then
        if entityData.spawned then 
            SetEntityCoords(entityData.spawned, coords.x, coords.y, coords.z, false, false, false, true)
        end    
        -- ClientEntity.UpdateCoords(entityData.id, entityData.coords)       
        if entityData.OnMove then
            pcall(function (...)
                return entityData.OnMove(entityData)
            end)
        end
        Behaviors.Trigger("OnMove", entityData, entityData.oldCoords, entityData.coords)
    end

    if not entityData.oldRotation or entityData.oldRotation ~= entityData.rotation then
        if entityData.spawned and DoesEntityExist(entityData.spawned) then
            if entityData.entityType == 'object' then
                SetEntityRotation(entityData.spawned, entityData.rotation.x, entityData.rotation.y, entityData.rotation.z, 2, true)
            else -- Ped/Vehicle heading
                SetEntityHeading(entityData.spawned, type(entityData.rotation) == 'number' and entityData.rotation or entityData.rotation.z)
            end
        end
        entityData.oldRotation = entityData.rotation
    end

    if entityData.freeze ~= nil and entityData.spawned and IsEntityPositionFrozen(entityData.spawned) then
        FreezeEntityPosition(entityData.spawned, entityData.freeze)
    end
end

--- Registers an entity received from the server and sets up proximity spawning.
-- @param entityData table Data received from the server via 'community_bridge:client:CreateEntity'
function ClientEntity.Create(entityData)
    entityData.id = entityData.id or Ids.CreateUniqueId(Entities)
    if Entities[entityData.id] then return Entities[entityData.id] end
    entityData.rotation = entityData.rotation or vector3(0.0, 0.0, entityData.heading or 0.0)
    entityData.oldCoords = entityData.oldCoords or vector3(0.0, 0.0, 0.0)
    entityData.oldRotation = entityData.rotation
    entityData.invoked =  entityData.invoked or GetInvokingResource() or "community_bridge"
    local entityPoint = Point.Register(entityData.id, entityData.coords, entityData.spawnDistance or 50.0, entityData, SpawnEntity, RemoveEntity, UpdateEntity)
    Entities[entityData.id] = entityPoint
   
    ClientEntity.Invoked[entityData.invoked] = ClientEntity.Invoked[entityData.invoked] or {}
    ClientEntity.Invoked[entityData.invoked][entityData.id] = entityData
    Behaviors.Trigger("OnCreate", entityPoint)
    return entityPoint
end

--Depricated use ClientEntity.Create instead
--- Registers an entity and spawns it in the world if not already spawned.
ClientEntity.Register = ClientEntity.Create


function ClientEntity.CreateBulk(entities)
    local registeredEntities = {}
    for _, entityData in pairs(entities) do
        local entity = ClientEntity.Create(entityData)
        registeredEntities[entity.id] = entity
    end
    return registeredEntities
end

-- Depricated use ClientEntity.CreateBulk instead
ClientEntity.RegisterBulk = ClientEntity.CreateBulk

--- Unregisters an entity and removes it from the world if spawned.
-- @param id string|number The ID of the entity to unregister.
function ClientEntity.Destroy(id)
    local entityData = Entities[id]
    if not entityData then return end
    Behaviors.Trigger("OnDestroy", entityData)
    Point.Remove(id)
    RemoveEntity(entityData)    
    Entities[id] = nil
end
ClientEntity.Unregister = ClientEntity.Destroy

function ClientEntity.RegisterBehavior(id, behavior)
    Behaviors.Create(id, behavior)
end

--- Sets a key-value pair for a client entity.
---@param id any
---@param key any
---@param value any
---@return nil
function ClientEntity.Set(id, key, value)
    local entityData = ClientEntity.Get(id)
    if not entityData then return print(string.format("[ClientEntity] SetKey: Entity %s does not exist", id)) end
    entityData[key] = value
end

function ClientEntity.Get(id)
    return Entities[id]
end

function ClientEntity.GetAll()
    return Entities
end

function ClientEntity.OnCreate(_type, func)
    ClientEntity.OnCreates = ClientEntity.OnCreates or {}
    if not ClientEntity.OnCreates[_type] then
        ClientEntity.OnCreates[_type] = {}
    end
    table.insert(ClientEntity.OnCreates[_type], func)
end

function ClientEntity.SetOnSpawn(id, func)
    local entityData = Entities[id]
    if not entityData then
        print(string.format("[ClientEntity] SetOnSpawn: Entity %s does not exist", id))
        return
    end
    entityData.OnSpawn = func
end

function ClientEntity.SetOnRemove(id, func)
    local entityData = Entities[id]
    if not entityData then
        print(string.format("[ClientEntity] SetOnRemove: Entity %s does not exist", id))
        return
    end
    entityData.OnRemove = func
end

function ClientEntity.UpdateCoords(id, coords)
    local entityData = Entities[id]
    if not entityData then
        print(string.format("[ClientEntity] UpdateCoords: Entity %s does not exist", id))
        return
    end
    entityData.coords = coords
    Point.UpdateCoords(id, coords) 
end

function ClientEntity.ChangeModel(id, model)
    local entityData = Entities[id]
    if not entityData then return print(string.format("[ClientEntity] ChangeModel: Entity %s does not exist", id)) end

    entityData.model = model
    if not entityData.spawned or not DoesEntityExist(entityData.spawned) then return end

    RemoveEntity(entityData)
    SpawnEntity(entityData)
end


-- Network Event Handlers
RegisterNetEvent("community_bridge:client:CreateEntity", function(entityData)
    ClientEntity.Create(entityData)
end)

RegisterNetEvent("community_bridge:client:CreateEntities", function(entities)
    ClientEntity.CreateBulk(entities)
end)

RegisterNetEvent("community_bridge:client:DeleteEntity", function(id)
    ClientEntity.Unregister(id)
end)

RegisterNetEvent("community_bridge:client:UpdateEntity", function(id, data)
    for k, v in pairs(data) do
        ClientEntity.Set(id, k, v)
    end
end)

local Loaded = false
AddEventHandler('community_bridge:Client:OnPlayerLoaded', function(resourceName)
    if Loaded then return end
    Loaded = true
end)

AddEventHandler('onResourceStart', function(resourceName)
    if resourceName ~= GetCurrentResourceName() then return end
    if Loaded then return end
    Loaded = true
end)

-- Resource Stop Cleanup
AddEventHandler('onResourceStop', function(resourceName)
    if resourceName == GetCurrentResourceName() then
        for id, entityData in pairs(Entities) do
            Point.Remove(id) -- Clean up point registration
            RemoveEntity(entityData) -- Clean up spawned game entity
        end
        Entities = {} -- Clear local cache
    else
        for id, entityData in pairs(ClientEntity.Invoked[resourceName] or {}) do
            Point.Remove(id) -- Clean up point registration
            RemoveEntity(entityData) -- Clean up spawned game entity
            Entities[id] = nil
        end
    end
end)

return ClientEntity