
aom_shape_selector = {}

local pl = {}
aom_shape_selector.pl = pl

-- gets a player info table
function aom_shape_selector.check_player(player)
    local pi = pl[player]
    if pi == nil then pi = {}; pl[player] = pi end
    return pi
end

-- tests if this node has shapes defined and should be used by this system
local function is_shape_item(stack)
    if stack:get_count() <= 0 then return false end
    local def = stack:get_definition()
    if not def then return false end
    return def._full_node_name ~= nil
end

-- used to get controls
local function is_using_radial(player)
    local ctrl = player:get_player_control()
    local movement = (ctrl.up or ctrl.down or ctrl.left or ctrl.right or ctrl.sneak)
    if (ctrl.aux1) and not movement then
        return true, movement
    elseif not ctrl.aux1 then
        return false, movement
    else
        return nil, movement
    end
end

-- definition of "items" for the radial menu
local shapes_default = {
    {name = "full", image = "aom_icon_full.png", text = nil, scale = 0.75},
    {name = "stair", image = "aom_icon_stair.png", text = nil, scale = 0.75},
    {name = "slab", image = "aom_icon_slab.png", text = nil, scale = 0.75},
    {name = "beam", image = "aom_icon_beam.png", text = nil, scale = 0.75},
    {name = "post", image = "aom_icon_post.png", text = nil, scale = 0.75},
    {name = "quarter", image = "aom_icon_quarter.png", text = nil, scale = 0.75},
}
local item_disabled = {name = "IGNORE", image = "blank.png", text = nil, scale = 0.75}

local shape_selector = {
    name = "shape_selector",
    -- signals that something was selected
    on_select = function(self, player, selection_name, pi)
        if selection_name == nil then return end
        local stack = player:get_wielded_item()
        local def = stack:get_definition()
        local full = def._full_node_name
        if not full then return end
        if selection_name == "full" then
            stack:set_name(full)
        else
            stack:set_name(full .. "_" .. selection_name)
        end
        -- if it's not a real item, don't set it to "unknown node"
        if stack:is_known() then
            player:set_wielded_item(stack)
            minetest.sound_play("aom_wrench_plip", {
                gain = 0.1,
                to_player = player:get_player_name(),
            })
        end
    end,
    -- for after all segments added
    after_init_huds = function(self, player, pi)
    end,
    -- for each segment added
    on_segment_added = function(self, player, pi, i, dir, pos)
    end,
    -- return true to allow selection to finish
    on_step = function(self, player, dtime, selection_name, pi)
        local stack = player:get_wielded_item()
        if pi.meta.start_node_name == nil then pi.meta.start_node_name = stack:get_name() end
        local is_shape_node = is_shape_item(stack)
        if stack:get_name() ~= pi.meta.start_node_name then
            aom_radial.close_radial_menu(player, is_shape_node)
            return false
        end
        if not is_shape_node then return false end
        local input = is_using_radial(player)
        if input == false then
            return true
        elseif input == nil then
            return false
        end
    end,
    items = shapes_default
}

-- global on_step for all players to show and hide the radial menu
local function system_on_step_player(player, dtime)
    local stack = player:get_wielded_item()
    local is_shape_node = is_shape_item(stack)
    if not is_shape_node then return end
    local pi = aom_shape_selector.check_player(player)
    local is_ctrl, movement = is_using_radial(player)

    -- leave some time after moving where the radial menu won't open
    if not pi.move_grace then pi.move_grace = 0 end
    if movement then
        -- if they have tons of lag, the lag is the grace period, so remove lag from grace
        pi.move_grace = 0.2 - (minetest.get_player_information(player:get_player_name()).avg_rtt or 0)
    elseif pi.move_grace > 0 then
        pi.move_grace = pi.move_grace - dtime
    end

    if (pi.move_grace <= 0) and (is_ctrl == true) and not aom_radial.is_radial_open(player, "shape_selector") then
        -- use a new list so you can skip some if the node doesn't have that shape available
        local new_shape_selector = setmetatable({items={}}, {__index = shape_selector})
        local tmpstack = ItemStack(stack:get_name())
        local def = stack:get_definition()
        local full = def._full_node_name
        -- go through each shape radial element
        for i, item in ipairs(shapes_default) do
            if item.name == "full" then
                tmpstack:set_name(full)
            else
                tmpstack:set_name(full .. "_" .. item.name)
            end
            -- if there is this shape available, show the shape else a blank one
            if tmpstack:is_known() then
                table.insert(new_shape_selector.items, item)
            else
                table.insert(new_shape_selector.items, item_disabled)
            end
        end
        aom_radial.show_radial_menu(player, new_shape_selector)
    end
end

minetest.register_globalstep(function(dtime)
    for i, player in ipairs(minetest.get_connected_players()) do
        system_on_step_player(player, dtime)
    end
end)

-- when a player picks up a stair, it should convert into a slab if they have slabs in their inventory
local function on_pickup_shape_convert(itemstack, player, pointed_thing, time_from_last_punch, ...)
    local idef = itemstack:get_definition()
    local full_node_name = idef and idef._full_node_name
    -- if it's not a shape node, skip it
    if (not full_node_name) or (not minetest.is_player(player)) then
        return minetest.item_pickup(itemstack, player, pointed_thing, time_from_last_punch, ...)
    end
    local to_stack
    -- look for a stack which is a shape variant of this item
    for i, stack in ipairs(player:get_inventory():get_list("main")) do
        local sdef = stack and stack:get_definition()
        if full_node_name == (sdef and sdef._full_node_name) then
            to_stack = stack
            break
        end
    end
    -- if there's no stack, just pick up the item normally
    if not to_stack then
        return minetest.item_pickup(itemstack, player, pointed_thing, time_from_last_punch, ...)
    else
    -- if it's a shape node, set the name so it stacks with the existing items
        itemstack:set_name(to_stack:get_name())
        return minetest.item_pickup(itemstack, player, pointed_thing, time_from_last_punch, ...)
    end
end

-- for aom_item_entity callbacks, set the node to the full node when it gets dropped
local _on_item_drop = function(self, itemstack, dropper)
    local idef = itemstack:get_definition()
    local full_node_name = idef and idef._full_node_name
    if full_node_name then
        itemstack:set_name(full_node_name)
        return itemstack
    end
end

if minetest.get_modpath("aom_wrench") then
    local old_take_item = aom_wrench.take_item
    -- count==nil for dry test
    ---@diagnostic disable-next-line: duplicate-set-field
    function aom_wrench.take_item(player, itemname, count)
        local idef = minetest.registered_items[itemname]
        if idef and idef._full_node_name then
            local inv = player:get_inventory()
            for i, stack in ipairs(inv:get_list("main")) do
                local sdef = stack:get_definition()
                if sdef and sdef._full_node_name == idef._full_node_name then
                    if (count == nil) or aom_wrench.pl[player].creative then return true end -- don't use items if in creative
                    stack:take_item(count)
                    inv:set_stack("main", i, stack)
                    return true
                end
            end
        else
            return old_take_item(player, itemname, count)
        end
    end
end

if minetest.get_modpath("aom_hammer") then
    local old_func = aom_hammer.find_item_index_in_inv
    -- count==nil for dry test
    ---@diagnostic disable-next-line: duplicate-set-field
    function aom_hammer.find_item_index_in_inv(player, itemname)
        local idef = minetest.registered_items[itemname]
        if idef and idef._full_node_name then
            local inv = player:get_inventory()
            for i, stack in ipairs(inv:get_list("main")) do
                local sdef = stack:get_definition()
                if sdef and sdef._full_node_name == idef._full_node_name then
                    return i
                end
            end
        else
            return old_func(player, itemname)
        end
    end
end

-- override all the shape related items to use the functions used here
minetest.register_on_mods_loaded(function()
    for iname, ndef in pairs(minetest.registered_nodes) do
        if ndef._full_node_name ~= nil then
            minetest.override_item(iname, {
                _old_on_pickup = ndef.on_pickup,
                on_pickup = on_pickup_shape_convert,
                _on_item_drop = _on_item_drop,
            })
        end
    end
end)
