gc = game.translate_string

---@param id number
---@return bind_hf_base.hf_binder_wrapper|nil
function get_wrapper(id)
    local obj = get_object_by_id(id)
    if not obj then return end

    local binder = obj:binded_object()
    if not binder then return end
    
    return binder.wrapper
end

function freeze_obj(id)
    local obj = level.object_by_id(id)
    if not obj then return end

    local phys = obj:get_physics_shell()
    if not phys then return end

    phys:freeze()

    local wrapper = get_wrapper(id)
    if wrapper then wrapper.is_frozen = true end

    return true
end

function init(obj)
    obj:bind_object(hf_binder_wrapper(obj).binder)
end

---@param obj game_object
---@param field string
---@return function
function get_function_from_field(obj, field)
    local func_string = ini_sys:r_string_ex(obj:section(), field)
    if not func_string then return end
    
    local func_split = {}
    for s in string.gmatch(func_string, "([^.]+)") do
        table.insert(func_split, s)
    end

    return _G[func_split[1]][func_split[2]]
end
--------------------------------------------------------------------------------
-- Class "hf_binder_wrapper"
--------------------------------------------------------------------------------
class "hf_binder_wrapper"
-- Class constructor
function hf_binder_wrapper:__init(obj)
    local binder = base_binder(obj)
    binder.wrapper = self

    obj:bind_object(binder)

    self.binder = binder
    
    ---@type game_object
    self.object = obj

    -- *hopefully* source of truth of frozenness since objects are not frozen by default
    self.is_frozen = false
end

function hf_binder_wrapper:update(delta)
end

function hf_binder_wrapper:net_spawn()
    CreateTimeEvent("hf_freeze", self.object:id(), 0, freeze_obj, self.object:id())
end

function hf_binder_wrapper:net_destroy()
    -- Remove time event just in case object is released before freezing
	RemoveTimeEvent("hf_freeze", self.object:id())

    -- Clean up references
    self.binder.wrapper = nil
    self.binder = nil
    self.object = nil
end

function hf_binder_wrapper:use_callback()
    local callback = get_function_from_field(self.object, "ui_on_interaction")
    if callback then callback(self.object:id()) end
end

function hf_binder_wrapper:use_callback_simple()
    local callback = get_function_from_field(self.object, "ui_on_simple_interaction")
    if callback then callback(self.object:id()) end
end

function hf_binder_wrapper:is_pickupable()
    local data = hf_obj_manager.get_data(self.object:id())
    if data.is_world_obj then
        return false
    end
    return true
end

function hf_binder_wrapper:pickup()
	local item_section = ini_sys:r_string_ex(self.object:section(), "item_section")
	local condition = self.fuel
	alife_create_item(item_section, db.actor, {cond=condition})
	alife_release(self.object)
    
    return true
end

---@param bool boolean
function hf_binder_wrapper:set_frozen(bool)
    local data = hf_obj_manager.get_data(self.object:id())
    if data.is_world_obj then return end

    local phys = self.object:get_physics_shell()
    if not phys then return end -- idk how an object wouldn't have a physics shell

    if bool then
        phys:freeze()
    else
        phys:unfreeze()
    end

    self.is_frozen = bool
end

--------------------------------------------------------------------------------
-- Class "base_binder"
--------------------------------------------------------------------------------
class "base_binder" (object_binder)
-- Class constructor
function base_binder:__init(obj) super(obj)
    self.object:set_tip_text(gc("st_interact"))
    ---@type bind_hf_base.hf_binder_wrapper
    self.wrapper = nil
end
-- Class update
function base_binder:update(delta)
    object_binder.update(self, delta)
    -- self.object:set_callback(callback.use_object, self.use_callback, self)

    if self.wrapper and self.wrapper.update then
        self.wrapper:update(delta)
    end
end

-- Reload object
function base_binder:reload(section)
    object_binder.reload(self, section)

    if self.wrapper and self.wrapper.reload then
        self.wrapper:reload(section)
    end
end
-- Reinitialize object
function base_binder:reinit()
    object_binder.reinit(self)

    if self.wrapper and self.wrapper.reinit then
        self.wrapper:reinit()
    end
end
-- Net spawn
function base_binder:net_spawn(se_abstract)
    if not(object_binder.net_spawn(self, se_abstract)) then
        return false
    end

    if self.wrapper and self.wrapper.net_spawn then
        self.wrapper:net_spawn()
    end

    return true
end
-- Net destroy
function base_binder:net_destroy()
    self:save_data()
	object_binder.net_destroy(self)

    if self.wrapper and self.wrapper.net_destroy then
        self.wrapper:net_destroy()
    end
end
-- Standart function for save
function base_binder:net_save_relevant()
	return true
end
-- Saving container
function base_binder:save(stpk)
	object_binder.save(self, stpk)
    if self.wrapper and self.wrapper.save then
        self.wrapper:save(stpk)
    end
end
-- Loading container
function base_binder:load(stpk)
	object_binder.load(self, stpk)
end
-- Save to mdata by calling hf_obj_manager.update_data
function base_binder:save_data()
    if self.wrapper and self.wrapper.save_data then
        self.wrapper:save_data()
    end
end