gc = game.translate_string

-- util stuff
local function pr(...)
    local debug = true
    if debug then
        printf("placeable_furniture: " .. ...)
    end
end

local function print_msg(string_id)
    local msg = gc(string_id)
    if ui_popup_messages then
        ui_popup_messages.GUI:AddMessage(msg)
    else
        actor_menu.set_msg(1, msg, 3)
    end
end

-- keybinds
local mcm_keybinds = ui_mcm and ui_mcm.key_hold
local key_place = bind_to_dik(key_bindings.kUSE)
local key_toggle_collision = DIK_keys.DIK_TAB
local key_toggle_align = DIK_keys.DIK_CAPITAL
local key_toggle_controls = DIK_keys.DIK_HOME


states = {
    IDLE=1,
    HOLDING=2,
    ADV_CTRL=3,
    PREVIEW=4
}
local state = states.IDLE

align_states = {
    ACTOR = 1,
    SURFACE = 2,
}
align_state = align_states.ACTOR

local check_collision = true

local place_coordinates = nil
local place_sound = xr_sound.get_safe_sound_object( "interface\\place_object" )
local item_id = nil
local phy_obj_section = nil

local location_offset = vector():set(0, 0, 0)
local rotation_offset = 0
local base_rot = 0
local base_loc = vector():set(0, 0, 0)

local player_pos = nil
local player_dir = nil
local player_dist = nil

local bbox = nil
local bbox_drawer = hf_bshape.bshape_renderer()

---------------------
-- State Management
---------------------
function reset_offsets()
    location_offset = utils_data.string_to_vector(ui_mcm.get("aol_hf/debug/location_offset"))
    rotation_offset = 0
end

transition_functors = {
    [states.HOLDING] = {
        [states.ADV_CTRL] = function ()
            player_pos = vector():set(device().cam_pos)
            player_dir = vector():set(device().cam_dir)
            player_dist = level.get_target_dist()
        end
    },
    [states.ADV_CTRL] = {
        [states.HOLDING] = reset_offsets
    },
    [states.PREVIEW] = {
        [states.HOLDING] = reset_offsets
    },
    [states.IDLE] = {
        [states.HOLDING] = reset_offsets
    }
}

-- Perform some function when a state transitions occurs
function on_state_transition(old_state, new_state)
    local func = transition_functors[old_state] and transition_functors[old_state][new_state]
    if func then
        func()
    end
end

function get_state()
    return state
end

function set_state(new_state)
    local old_state = state
    state = new_state
    on_state_transition(old_state, new_state)
end

function is_state(check_state)
    return state == check_state
end

function in_placing_state()
    return get_state() ~= states.IDLE
end
---------------------
-- Objects and their data
---------------------

---Creates and places an object in the world.
---Also initialises additional data depending on their placeable_type as defined in their section
---@param placeable_section string
---@param location vector
---@param rotation aol_rotation.Quaternion|vector
---@param lvid integer? Level Vertex ID
---@param gvid integer? Game Vertex ID
---@return integer|nil obj.id
function create_object(placeable_section, location, rotation, lvid, gvid)
    -- Exit early if placeable_section is not supplied
    if not placeable_section then
        pr("Could not find associated object with item")
        return
    end

    location = location or vector():set(0,0,0)
    rotation = rotation or vector():set(0,0,0)

    local obj = alife_create(placeable_section, location, lvid or db.actor:level_vertex_id(), gvid or db.actor:game_vertex_id())
    if not obj then return end

    -- Rotate object with quaternion or vector
    if rotation.w then
        obj.angle = rotation:to_euler_angles()
    else
        obj.angle = vector():set(rotation.x, rotation.y, rotation.z)
    end

    -- Initialise data store
    hf_obj_manager.set_data(obj.id, {})
    
    -- Initialise additional data
    local placeable_type = ini_sys:r_string_ex(placeable_section, "placeable_type") or "prop"
    local type_functor = hf_furniture_types.get_func(placeable_type)
    if type_functor then
        type_functor(obj.id)
    end

    -- Remove flags
    local data = utils_stpk.get_physic_data(obj)
    local remove_flags = 128
    local flag_mask = bit_not(remove_flags)
    data.object_flags = bit_and(data.object_flags, flag_mask)

    utils_stpk.set_physic_data(data, obj)

    SendScriptCallback("hf_on_furniture_spawn", obj.id)

    return obj.id
end

function transfer_item_data(item_id, obj_id)
    if not item_id or not obj_id then return end

    -- Copy data from item to obj and delete item data
    local hf_data = hf_obj_manager.get_data(item_id)
    if hf_data then
        hf_obj_manager.update_data(obj_id, hf_data)
        hf_obj_manager.delete_data(item_id)
    end

    -- Update condition if item is online
    local item = get_object_by_id(item_id)
    if not item then return end
    hf_obj_manager.update_data(obj_id, {condition=item:condition()})
end

---------------------
-- Placement System
---------------------
local function actor_on_update()
    if not in_placing_state() then return end

    local cam_pos = nil
    local cam_dir = nil
    local dist = nil
    if is_state(states.HOLDING) then
        cam_pos = device().cam_pos
        cam_dir = device().cam_dir
        dist = level.get_target_dist()
    else
        cam_pos = player_pos
        cam_dir = player_dir
        dist = player_dist
    end

    -- get position of point that the player is looking at
    local pos = vector()
    pos:mad(cam_pos, cam_dir,dist)
    
    -- rotate to point towards player + offset
    if align_state == align_states.ACTOR then
        local rot = cam_dir:getH() + (((base_rot+rotation_offset) * math.pi) / 180)
        local q0 = aol_rotation.get_rotation_around(vector():set(0, 1, 0), rot)
        
        bbox.rotation = q0

        local angle = cam_dir:getH()
    
        -- Rotate vector about y
        local c = math.cos (angle)
        local s = math.sin (angle)
        local rotated_x = location_offset.x * c - location_offset.z * s
        local rotated_z = location_offset.x * s + location_offset.z * c
        pos:add(vector():set(rotated_x,
                                location_offset.y,
                                rotated_z))
        
        -- automatic alignment to surface
    elseif align_state == align_states.SURFACE then
        -- Rotation
        local rot_z = ((base_rot+rotation_offset) * math.pi) / 180
        local u = vector():set(0, 1, 0)
        local v = demonized_geometry_ray.get_surface_normal(cam_pos, cam_dir)
        if not v then return end
        
        -- rotate from upwards vector to normal vector on surface
        local q0 = aol_rotation.get_rotation_between(u, v)
        
        -- rotate to point downwards + offset
        local angle_to_downwards = 0
        local similarity_to_upwards_axis = v:dotproduct(vector():set(0,1,0))
        if similarity_to_upwards_axis > 0.9999 then -- pointing upwards
            angle_to_downwards = cam_dir:getH()
        elseif similarity_to_upwards_axis < -0.9999 then -- pointing downwards
            angle_to_downwards = math.pi - cam_dir:getH()
        else
            angle_to_downwards = angle_to_downwards + v:getH() + math.pi
        end
        local q1 = aol_rotation.get_rotation_around(v, angle_to_downwards + rot_z)
        
        -- perform each rotation in succession
        local q = aol_rotation.Quaternion():multiply(q0):multiply(q1)
        
        -- Location
        local q1_2 = aol_rotation.get_rotation_around(v, angle_to_downwards)
        local q_loc = aol_rotation.Quaternion():multiply(q0):multiply(q1_2)
        local rotated_loc_offset = q_loc:rotate_vector(location_offset)
        
        bbox.rotation = q
        pos:add(rotated_loc_offset)
    end
    bbox:SetWorldOrigin(vector():set(pos))

    -- Update position of vertices in bounding box
    bbox:UpdateBBox()
    if check_collision then
        bbox:CheckForCollisions()
    else
        bbox.is_colliding = false
    end
    bbox_drawer:DrawBShapeCollider(bbox)
    place_coordinates = pos
end

local direction_keys = {
	[key_bindings.kFWD] = true,
	[key_bindings.kBACK] = true,
	[key_bindings.kL_STRAFE] = true,
	[key_bindings.kR_STRAFE] = true,
	[key_bindings.kCROUCH] = true,
	[key_bindings.kACCEL] = true,
    [key_bindings.kWPN_FIRE] = true,
    [key_bindings.kWPN_ZOOM] = true,
    [key_bindings.kL_LOOKOUT] = true,
    [key_bindings.kR_LOOKOUT] = true,
    [key_bindings.kJUMP] = true,
    [key_bindings.kQUIT] = true,
	[7] = true,
    [key_bindings.kCONSOLE] = true,
}

---@return game_object|nil
function get_obj_at_crosshair()
    if level.get_target_dist() > 5 then return end
    local obj = level.get_target_obj()
    if not obj then return end
    return obj
end

---@return bind_hf_base.hf_binder_wrapper|nil
function get_wrapper_at_crosshair()
    local obj = get_obj_at_crosshair()
    if not obj then return end
    local binder = obj:binded_object()
    if not binder then return end
    
    local wrapper = binder.wrapper
    if not wrapper then return end
    return wrapper
end

local function on_key_hold(dik)
    if dik_to_bind(dik) ~= key_bindings.kUSE then return end
    if Check_UI("UIRadialMenu") then return end
    if not in_placing_state() then
        if ui_mcm.key_hold("hf_interact_adv", dik) then
            local wrapper = get_wrapper_at_crosshair()
            if not wrapper then return end
            if Check_UI() then return end
            open_interact_gui(wrapper)
        end
    end
end

local function on_key_press(dik)
    if dik_to_bind(dik) == key_bindings.kUSE then
        if Check_UI("UIRadialMenu") then return end
        if not in_placing_state() then
            ui_mcm.simple_press("hf_interact_simple", dik, function ()
                local wrapper = get_wrapper_at_crosshair()
                if not wrapper then return end
                wrapper:use_callback_simple()
            end)
        end
    end
    if dik == key_place then
        if in_placing_state() then
            -- Prevent placement if target pos is too far away
            if level.get_target_dist() > 5 and not DEV_DEBUG then
                print_msg("st_far_popup")
                pr("Cannot place object that far away - max range of 5 units")
                return
            end

            -- Prevent placement if bounding box is colliding with something
            if bbox.is_colliding then
                print_msg("st_collision_warning")
                return
            end

            local obj_id = create_object(phy_obj_section, place_coordinates, bbox.rotation)
            if obj_id then transfer_item_data(item_id, obj_id) end

            SendScriptCallback("hf_on_furniture_place", obj_id)

            place_sound:play_no_feedback(db.actor, sound_object.s3d, 0, place_coordinates, 1.0, 1.0)
            bbox_drawer:Stop()

            -- Reset variables
            base_rot = 0
            phy_obj_section = nil

            -- Delete item
            if item_id then
                alife():release(alife():object(item_id), true)
                item_id = nil
            end

            set_state(states.IDLE)
            pr("state=PLACING")
        end
    elseif dik == key_toggle_collision then
        if in_placing_state() then
            check_collision = not check_collision
            if check_collision then
                print_msg("st_enable_collision")
            else
                print_msg("st_disable_collision")
            end
        end
    elseif dik == key_toggle_align then
        if in_placing_state() then
            if align_state == align_states.ACTOR then
                align_state = align_states.SURFACE
                print_msg("st_align_surface")
            elseif align_state == align_states.SURFACE then
                align_state = align_states.ACTOR
                print_msg("st_align_actor")
            end
        end
    elseif dik == key_toggle_controls then
        if state == states.IDLE then
            return
        elseif state == states.HOLDING or
               state == states.PREVIEW then
            set_state(states.ADV_CTRL)
        end
        open_gui()
    else
        local bind = dik_to_bind(dik)
        if state ~= states.IDLE and not direction_keys[bind] then
            pr("Interrupting placement")
            set_state(states.IDLE)
            bbox_drawer:Stop()
        end
    end
end

-- Cancel placement state with ESCAPE key
-- turns out this fires after on_key_press
local function on_before_key_press(key, bind, dis, flags)
    if bind == key_bindings.kQUIT and
      (is_state(states.HOLDING) or is_state(states.PREVIEW)) then
        flags.ret_value = false
        set_state(states.IDLE)
        bbox_drawer:Stop()
    end
end

------

function start_placing_item(section)
    if state ~= states.IDLE then
        item_id = nil
        return
    end
    print_msg("st_place_popup")
    hide_hud_inventory()
    db.actor:activate_slot(0)
    set_state(states.HOLDING)

    local bbox_size_str = ini_sys:r_string_ex(section, "bounding_box_size", "0.5,0.5,0.5")
    local bbox_size = str_explode(bbox_size_str,",")
    local bbox_origin_str = ini_sys:r_string_ex(section, "bounding_box_origin", "0,0.25,0")
    local bbox_origin = str_explode(bbox_origin_str,",")
    base_rot = ini_sys:r_float_ex(section, "base_rotation", 0) or 0
    base_loc = vector():set(tonumber(bbox_origin[1]), tonumber(bbox_origin[2]), tonumber(bbox_origin[3]))

    bbox = hf_bbox.bbox_collider(tonumber(bbox_size[1]), tonumber(bbox_size[2]), tonumber(bbox_size[3]))

    bbox:OffsetVertices(base_loc)

    local crosshair_vertex = bbox:GetCrosshairVertex()
    crosshair_vertex:sub(base_loc)
    bbox:SetCrosshairVertex(crosshair_vertex)

    phy_obj_section = section
end

function place_item(obj)
    return gc("st_place_furniture")
end

function func_place_item(obj)
    item_id = obj:id()

    print_msg("st_place_popup")
    physic_section = ini_sys:r_string_ex(obj:section(), "placeable_section")
    if physic_section == nil then
        pr("No section of physical object to place")
        return
    end

    start_placing_item(physic_section)
end

------
-- Helper to determine if item is a placeable furniture that requires fuel
-- function is_fueled_furniture(obj)
--     local section = obj and obj:section() or false
--     if section then
--         return SYS_GetParam(0, section, "placeable_type") and SYS_GetParam(0, section, "use_condition")
--     end
--     return false
-- end

-- Monkey patch for adding fuel amount on items // DEPRECATED: using item_device binder instead, which makes these items devices
-- local clr_r  = utils_xml.get_color("d_red")
-- local clr_g  = utils_xml.get_color("d_green")
-- local clr_y  = utils_xml.get_color("yellow")

-- local clr_2  = utils_xml.get_color("ui_gray_1")
-- original_build_desc_header = ui_item.build_desc_header
-- function ui_item.build_desc_header(obj, sec, str)
-- 	local _str = ""
-- 	local _str2 = original_build_desc_header(obj, sec, str)

--     -- display power + psu
-- 	if obj and is_fueled_furniture(obj) then
--         local fuel = math.ceil(obj:condition() * 100)
--         local clr = utils_xml.get_color_con(fuel)
--         _str = clr .. " � " .. clr_2 .. gc("st_fuel") .. ": " .. clr .. tostring(fuel) .. "%" .. "\\n \\n" .. clr_2
--     end
-- 	_str = _str .. _str2

-- 	return _str
-- end

-------
-- UI
-------
-- Advanced Controls
---@class placeable_furniture.UIAdvancedControls
GUI = nil -- instance, don't touch
function open_gui()
	hide_hud_inventory()

	if (not GUI) then
		GUI = UIAdvancedControls()
	end
	if (GUI) and (not GUI:IsShown()) then
        GUI:Reset()
		GUI:ShowDialog(true)
		Register_UI("UIAdvancedControls","hf_advanced_controls")
	end
end

class "UIAdvancedControls" (CUIScriptWnd)

function UIAdvancedControls:__init() super()
	self:InitControls()
	self:InitCallBacks()

    self.states = {
        IDLE = 1, -- idle
        XZ_DRAG_LOC = 2, -- Horizontal Plane
        Y_DRAG_LOC = 3, -- Up/Down
        Y_DRAG_ROT = 4, -- Rotate about upward axis
    }
    self.state = self.states.IDLE
    self.snap = false
    self.shift = false
    
    self.pos = {x=0, y=0}
    self.prev_pos = nil
    self.prev_offset = vector():set(location_offset)

    self.init = false
end

function UIAdvancedControls:__finalize()
end

function UIAdvancedControls:KeyPress(dik, kb_action)
    local funcs = self.key_to_functor[dik]
    if funcs then
        local func = funcs[kb_action]
        if func then func() end
    end
end

function UIAdvancedControls:InitControls()
	self:SetWndRect(Frect():set(0,0,1024,768))
	self:SetAutoDelete(true)

    -- Questionable table, but i got tired of if-elses
    self.key_to_functor = {
        -- XZ Displacement
        [DIK_keys.MOUSE_1] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                if self.shift then
                    self.state = self.state == self.states.IDLE and self.states.Y_DRAG_LOC or self.state
                else
                    self.state = self.state == self.states.IDLE and self.states.XZ_DRAG_LOC or self.state
                end
            end,
            [ui_events.WINDOW_KEY_RELEASED] = function()
                self.prev_offset = vector():set(location_offset)
                self.state = self.states.IDLE
                self.prev_pos = nil
            end
        },
        -- Y Displacement
        [DIK_keys.DIK_LSHIFT] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                self.shift = true
            end,
            [ui_events.WINDOW_KEY_RELEASED] = function()
                self.shift = false
            end
        },
        -- Y Rotation
        [DIK_keys.MOUSE_2] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                self.state = self.state == self.states.IDLE and self.states.Y_DRAG_ROT or self.state
            end,
            [ui_events.WINDOW_KEY_RELEASED] = function()
                self.prev_rot = rotation_offset
                self.state = self.states.IDLE
                self.prev_pos = nil
            end
        },
        -- Finer control, adjust at 1/10th speed
        [DIK_keys.DIK_LCONTROL] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                self.fine_control = true
            end,
            [ui_events.WINDOW_KEY_RELEASED] = function()
                self.fine_control = false
            end
        },
        -- SNAP
        [DIK_keys.DIK_LMENU] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                self.snap = true
            end,
            [ui_events.WINDOW_KEY_RELEASED] = function()
                self.snap = false
            end
        },
        -- Place
        [key_place] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                on_key_press(key_place)
                self:Close()
            end
        },
        -- Toggle Collision
        [key_toggle_collision] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                on_key_press(key_toggle_collision)
            end
        },
        -- Toggle Alignment mode
        [key_toggle_align] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                on_key_press(key_toggle_align)
            end
        },
        -- Swap to PREVIEW mode
        [key_toggle_controls] = {
            [ui_events.WINDOW_KEY_PRESSED] = function()
                set_state(states.PREVIEW)
                self:Close()
            end
        }
    }
end

function UIAdvancedControls:InitCallBacks()
end

function UIAdvancedControls:Reset()
    self.state = self.states.IDLE
    self.snap = false
    
    self.pos = {x=0, y=0}
    self.prev_pos = nil

    self.prev_offset = vector():set(location_offset)
    self.location_offset_copy = vector():set(location_offset)

    self.prev_rot = rotation_offset
    self.prev_rot_copy = rotation_offset

    self.init = false
end

function UIAdvancedControls:Update()
	CUIScriptWnd.Update(self)
    if self.state == self.states.IDLE then return end
    
    if self.prev_pos == nil then
        self.prev_pos = GetCursorPosition()
    end
    
    local mouse_pos = GetCursorPosition()
    local diff_x = (mouse_pos.x - self.prev_pos.x)
    local diff_y = -(mouse_pos.y - self.prev_pos.y)

    if self.fine_control then
        diff_x = diff_x / 10
        diff_y = diff_y / 10
    end

    if self.state == self.states.XZ_DRAG_LOC then
        diff_x = diff_x / 200
        diff_y = diff_y / 200

        if self.snap then
            if math.abs(diff_x) > math.abs(diff_y) then
                diff_y = 0
            else
                diff_x = 0
            end
        end
		location_offset = vector():set(self.prev_offset.x+diff_x, self.prev_offset.y, self.prev_offset.z+diff_y)
    elseif self.state == self.states.Y_DRAG_LOC then
        diff_y = diff_y / 200

		location_offset = vector():set(self.prev_offset.x, self.prev_offset.y+diff_y, self.prev_offset.z)
    elseif self.state == self.states.Y_DRAG_ROT then
        diff_x = diff_x/2
        rotation_offset = self.prev_rot + diff_x
        
        if self.snap then
            local n = 15
            rotation_offset = (rotation_offset % n) > n/2 and rotation_offset + n - rotation_offset%n or rotation_offset - rotation_offset%n
        end
    end
end

function UIAdvancedControls:OnKeyboard(dik, keyboard_action)
	local res = CUIScriptWnd.OnKeyboard(self,dik,keyboard_action)
    if not self.init then
        self.init = true
        return
    end
	if (res == false) then
        self:KeyPress(dik, keyboard_action)
		if keyboard_action == ui_events.WINDOW_KEY_PRESSED then
			if dik == DIK_keys.DIK_ESCAPE then
                if self.state == self.states.IDLE then
                    location_offset = vector():set(self.location_offset_copy)
                    rotation_offset = self.prev_rot_copy
                    set_state(states.HOLDING)
                    self:Close()
                else
                    location_offset = vector():set(self.prev_offset)
                    rotation_offset = self.prev_rot
                    self.state = self.states.IDLE
                end
            end
        end
	end
	return res
end

function UIAdvancedControls:Close()
	self:HideDialog()
	Unregister_UI("UIAdvancedControls")
end

-- Advanced Interaction Menu
---@class arm.UIRadialMenu
RadialGUI = nil

function create_interact_gui()
    local RadialGUI = arm.UIRadialMenu()

    -- Pickup
    local opt_pickup = arm.OptionData("opt_pickup", function (state) 
        return arm.get_stateful_texture("ui_hf_radial_icon_pickup", state)
    end)
    opt_pickup:SetColour(function (state)
        return arm.get_stateful_colour(state) -- stateful colour
    end) -- stateful colour

    -- Text
    opt_pickup:SetText({title = gc("st_pickup")})
    
    -- Open UI
    local opt_ui = arm.OptionData("opt_use", function (state) 
        return arm.get_stateful_texture("ui_hf_radial_icon_use", state)
    end)
    opt_ui:SetColour(function (state)
        return arm.get_stateful_colour(state) -- stateful colour
    end) -- stateful colour

    -- Text
    opt_ui:SetText({title = gc("st_open_adv_ui")})
    
    -- Freeze/Unfreeze
    local opt_freeze = arm.OptionData("opt_freeze", function(state)
        if state == arm.States.HIGHLIGHTED then
            return "ui_hf_radial_icon_frozen"
        else
            return "ui_hf_radial_icon_unfrozen"
        end
    end)
    opt_freeze:SetColour(function (state)
        return arm.get_stateful_colour(state) -- stateful colour
    end) -- stateful colour

    -- Text
    opt_freeze:SetText({title = gc("st_freeze")})
    
    -- Upgrade
    local opt_upgrade = arm.OptionData("opt_upgrade", "ui_hf_radial_icon_upgrade")
    opt_upgrade:SetColour(function (state)
        return arm.get_stateful_colour(state) -- stateful colour
    end) -- stateful colour

    -- Text
    opt_upgrade:SetText({title = gc("st_cap_upgrades"),
                     description = gc("st_not_implemented")})
    opt_upgrade:SetState(arm.States.DISABLED)
    
    -- Wire Connections
    local opt_connections = arm.OptionData("opt_connections", "ui_hf_radial_icon_connections")
    opt_connections:SetColour(function (state)
        return arm.get_stateful_colour(state) -- stateful colour
    end)
    -- Text
    opt_connections:SetText({title = gc("st_open_connections"),
                     description = gc("st_not_implemented")})
    opt_connections:SetState(arm.States.DISABLED)

    RadialGUI:RegisterCallback("opt_pickup", function ()
        local obj = get_obj_at_crosshair()
        if not obj then return end
        pickup_obj(obj)
    end)
    RadialGUI:RegisterCallback("opt_use", function ()
        local wrapper = get_wrapper_at_crosshair()
        if not wrapper then return end
        wrapper:use_callback()
    end)
    RadialGUI:RegisterCallback("opt_freeze", function ()
        local wrapper = get_wrapper_at_crosshair()
        if not wrapper then return end
        
        wrapper:set_frozen(not wrapper.is_frozen)
        if wrapper.is_frozen then
            opt_freeze:SetState(arm.States.HIGHLIGHTED)
            opt_freeze:SetText({title = gc("st_unfreeze")})
        else
            opt_freeze:SetState(arm.States.ENABLED)
            opt_freeze:SetText({title = gc("st_freeze")})
        end
    end)

    RadialGUI:AddOption(opt_ui)
    RadialGUI:AddOption(opt_pickup)
    RadialGUI:AddOption(opt_freeze)
    RadialGUI:AddOption(opt_upgrade)
    RadialGUI:AddOption(opt_connections)

    RadialGUI:DrawOptions()

    return RadialGUI
end

---@param wrapper bind_hf_base.hf_binder_wrapper
function update_radial_gui(wrapper)
    local is_pickupable = wrapper:is_pickupable()
    if is_pickupable then
        RadialGUI:GetOption("opt_pickup"):SetState(arm.States.ENABLED)
    else
        RadialGUI:GetOption("opt_pickup"):SetState(arm.States.DISABLED)
    end
    
    local type = hf_furniture_types.get_type(wrapper.object)
    if type == "prop" or type == nil then
        RadialGUI:GetOption("opt_use"):SetState(arm.States.DISABLED)
    else
        RadialGUI:GetOption("opt_use"):SetState(arm.States.ENABLED)
    end

    if wrapper.is_frozen then
        RadialGUI:GetOption("opt_freeze"):SetState(arm.States.HIGHLIGHTED)
        RadialGUI:GetOption("opt_freeze"):SetText({title = gc("st_unfreeze")})
    else
        RadialGUI:GetOption("opt_freeze"):SetState(arm.States.ENABLED)
        RadialGUI:GetOption("opt_freeze"):SetText({title = gc("st_freeze")})
    end
end

---@param wrapper bind_hf_base.hf_binder_wrapper
function open_interact_gui(wrapper)
	hide_hud_inventory()

	if (not RadialGUI) then
		RadialGUI = create_interact_gui()
	end
	if (RadialGUI) and (not RadialGUI:IsShown()) then
        update_radial_gui(wrapper)
		RadialGUI:ShowDialog(true)

        _GUIs_keyfree["UIRadialMenu"] = true
		Register_UI("UIRadialMenu","placeable_furniture")
	end
end

-------

function on_option_change(mcm) --new in mcm 1.6.0 mcm passes true to the on_option_change callback
	if mcm then
        key_toggle_collision = ui_mcm.get("aol_hf/controls/bind_collision") or key_toggle_collision
        key_toggle_align = ui_mcm.get("aol_hf/controls/bind_alignment") or key_toggle_align
        key_toggle_controls = ui_mcm.get("aol_hf/controls/bind_place_mode") or key_toggle_controls
	end
    key_place = bind_to_dik(key_bindings.kUSE)
end

-- Allow drag n drop of fuel onto furniture items
local function on_item_drag_dropped(obj_b, obj_d, slot_from, slot_to)

	-- Check capability
	if not (slot_from == EDDListType.iActorBag and (slot_to == EDDListType.iActorBag or slot_to == EDDListType.iActorSlot)) then
        return
    end

	local sec_b = obj_b:section() -- fuel
	local sec_d = obj_d:section() -- light source

    local req_fuel = ini_sys:r_string_ex(sec_d, "fuel_section")
	if req_fuel and (sec_b == req_fuel) then
        if sec_b == "batteries_dead" then
            alife_create_item("batteries_dead", db.actor, {cond=obj_d:condition()})
            obj_d:set_condition(obj_b:condition())
        else
            utils_item.discharge(obj_b, 1)
            obj_d:set_condition(1.0)
        end

		utils_obj.play_sound("interface\\items\\inv_items_generic_1")
	end
end

---@param obj game_object
function pickup_obj(obj)
	local wrapper = bind_hf_base.get_wrapper(obj:id())
    if not wrapper then return end
    if wrapper:is_pickupable() then
        wrapper:pickup()
        print_msg("Picked up object")
    end
end


-------

function on_game_start()
    RegisterScriptCallback("on_option_change",on_option_change)
    on_option_change(mcm_keybinds)

	RegisterScriptCallback("on_key_press", on_key_press)
    RegisterScriptCallback("on_key_hold", on_key_hold)
    RegisterScriptCallback("on_before_key_press", on_before_key_press)
    RegisterScriptCallback("actor_on_update", actor_on_update)
    RegisterScriptCallback("ActorMenu_on_item_drag_drop", on_item_drag_dropped)
end