---@diagnostic disable
--[[
    X Enchanting. Adds Enchanting Mechanics and API.
    Copyright (C) 2023 SaKeL <juraj.vajda@gmail.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to juraj.vajda@gmail.com
--]]

local S = minetest.get_translator(minetest.get_current_modname())

----
--- Grindstone Node
----

minetest.register_node('x_enchanting:grindstone', {
    description = S('Grindstone'),
    short_description = S('Grindstone'),
    drawtype = 'mesh',
    mesh = 'x_enchanting_grindstone.obj',
    tiles = { 'x_enchanting_grindstone_mesh.png' },
    inventory_image = 'x_enchanting_grindstone_item.png',
    use_texture_alpha = 'clip',
    paramtype = 'light',
    paramtype2 = 'facedir',
    walkable = true,
    wield_scale = { x = 2, y = 2, z = 2 },
    selection_box = {
        type = 'fixed',
        fixed = { -1 / 2, -1 / 2, -1 / 2, 1 / 2, 1 / 2 - 1 / 16, 1 / 2 }
    },
    collision_box = {
        type = 'fixed',
        fixed = { -1 / 2, -1 / 2, -1 / 2, 1 / 2, 1 / 2 - 1 / 16, 1 / 2 }
    },
    sounds = XEnchanting.node_sound_wood_defaults(),
    is_ground_content = false,
    groups = { choppy = 2, oddly_breakable_by_hand = 2 },
    stack_max = 1,
    mod_origin = 'x_enchanting',
    ---@param pos Vector
    on_construct = function(pos)
        local meta = minetest.get_meta(pos)
        local inv = meta:get_inventory()

        meta:set_string('infotext', S('Grindstone'))
        meta:set_string('owner', '')
        inv:set_size('item', 1)
        inv:set_size('result', 1)
    end,
    ---@param pos Vector
    ---@param placer ObjectRef | nil
    ---@param itemstack ItemStack
    ---@param pointed_thing PointedThingDef
    ---@diagnostic disable-next-line: unused-local
    after_place_node = function(pos, placer, itemstack, pointed_thing)
        local meta = minetest.get_meta(pos)

        if not placer then
            return
        end

        local player_name = placer:get_player_name()

        meta:set_string('owner', player_name)
        meta:set_string('infotext', S('Grindstone') .. ' (' .. S('owned by') .. ' ' .. player_name .. ')')

        local formspec = XEnchanting:get_grindstone_formspec(pos, player_name)
        meta:set_string('formspec', formspec)
    end,
    ---@param pos Vector
    ---@param node NodeDef
    ---@param clicker ObjectRef
    ---@param itemstack ItemStack
    ---@param pointed_thing? PointedThingDef
    ---@diagnostic disable-next-line: unused-local
    on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
        local meta = minetest.get_meta(pos)
        local p_name = clicker:get_player_name()
        local inv = meta:get_inventory()
        local item_stack = inv:get_stack('item', 1)
        local has_all_cursed_ench = XEnchanting:has_all_cursed_ench(item_stack)

        if minetest.is_protected(pos, p_name) then
            return itemstack
        end

        minetest.sound_play('x_enchanting_wood_hit', {
            gain = 0.5,
            pos = pos,
            max_hear_distance = 10
        }, true)

        local formspec = XEnchanting:get_grindstone_formspec(pos, p_name, { result_disabled = has_all_cursed_ench })
        meta:set_string('formspec', formspec)

        return itemstack
    end,
    ---@param pos Vector
    ---@param intensity? number
    ---@return table | nil
    ---@diagnostic disable-next-line: unused-local
    on_blast = function(pos, intensity)
        if minetest.is_protected(pos, '') then
            return
        end

        local drops = {}
        local inv = minetest.get_meta(pos):get_inventory()
        local stack_item = inv:get_stack('item', 1)

        if not stack_item:is_empty() then
            drops[#drops + 1] = stack_item:to_table()
        end

        drops[#drops + 1] = 'x_enchanting:grindstone'
        minetest.remove_node(pos)

        return drops
    end,
    ---@param pos Vector
    ---@param player? ObjectRef
    can_dig = function(pos, player)
        if not player then
            return false
        end

        local inv = minetest.get_meta(pos):get_inventory()

        return inv:is_empty('item')
            and inv:is_empty('result')
            and not minetest.is_protected(pos, player:get_player_name())
    end,
    ---@diagnostic disable-next-line: unused-local
    on_rotate = function(pos, node, user, mode, new_param2)
        return false
    end,
    ---@param pos Vector
    ---@param listname string
    ---@param index number
    ---@param stack ItemStack
    ---@param player ObjectRef
    ---@diagnostic disable-next-line: unused-local
    allow_metadata_inventory_put = function(pos, listname, index, stack, player)
        local st_meta = stack:get_meta()
        local is_enchanted = st_meta:get_int('is_enchanted')

        if listname == 'item' and is_enchanted == 1 then
            return stack:get_count()
        end

        return 0
    end,
    ---@param pos Vector
    ---@param listname string
    ---@param index number
    ---@param stack ItemStack
    ---@param player ObjectRef
    ---@diagnostic disable-next-line: unused-local
    allow_metadata_inventory_take = function(pos, listname, index, stack, player)
        return stack:get_count()
    end,
    ---@param pos Vector
    ---@param from_list string
    ---@param from_index number
    ---@param to_list string
    ---@param to_index number
    ---@param count number
    ---@param player ObjectRef
    ---@diagnostic disable-next-line: unused-local
    allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
        return 0
    end,
    ---@param pos Vector
    ---@param listname string
    ---@param index number
    ---@param stack ItemStack
    ---@param player ObjectRef
    ---@diagnostic disable-next-line: unused-local
    on_metadata_inventory_put = function(pos, listname, index, stack, player)
        --
        -- Create new itemstack with removed enchantments (excl. curses) and populate the result slot with this new item
        --
        local meta = minetest.get_meta(pos)
        local p_name = player:get_player_name()
        local inv = meta:get_inventory()
        local item_stack = inv:get_stack('item', 1)
        local item_stack_meta = item_stack:get_meta()
        local is_enchanted = item_stack_meta:get_int('is_enchanted')
        local stack_enchantment_data = minetest.deserialize(item_stack_meta:get_string('x_enchanting')) or {}
        local has_all_cursed_ench = XEnchanting:has_all_cursed_ench(item_stack)

        if not inv:is_empty('item') and is_enchanted == 1 and not has_all_cursed_ench then
            -- Discenchanted item
            local has_curse = false
            local current_enchantments = {}

            for id, value in pairs(stack_enchantment_data) do
                -- Remove enchantment meta data (excl. cursed)
                local ench_def = XEnchanting.enchantment_defs[id]

                if not ench_def.cursed then
                    item_stack_meta:set_float('is_' .. id, 0)
                    stack_enchantment_data[id] = nil
                else
                    has_curse = true
                end

                -- Get descriptions
                if stack_enchantment_data[id] then
                    local level

                    -- Find level
                    for i, v in ipairs(ench_def.level_def) do
                        if v == value.value then
                            level = i
                            break
                        end
                    end

                    table.insert(current_enchantments, {
                        id = id,
                        value = value.value,
                        level = level,
                        secondary = ench_def.secondary,
                        incompatible = ench_def.incompatible
                    })
                end
            end

            if not has_curse then
                -- Reset meta data if not cursed
                item_stack_meta:set_string('inventory_image', '')
                item_stack_meta:set_string('inventory_overlay', '')
                item_stack_meta:set_string('wield_image', '')
                item_stack_meta:set_string('wield_overlay', '')
                item_stack_meta:set_string('description', '')
                item_stack_meta:set_string('short_description', '')
                item_stack_meta:set_int('is_enchanted', 0)
            end

            local descriptions = XEnchanting:get_enchanted_descriptions(current_enchantments)

            -- Upgrade description with remaining enchantments
            if #current_enchantments > 0 then
                local item_stack_def = minetest.registered_tools[item_stack:get_name()]
                item_stack_meta:set_string('description', (item_stack_def and item_stack_def.description or '') .. '\n' .. descriptions.enchantments_desc)
            end

            item_stack_meta:set_tool_capabilities(nil)
            item_stack_meta:set_string('x_enchanting', minetest.serialize(stack_enchantment_data))

            inv:set_stack('result', 1, item_stack)
        end

        local formspec = XEnchanting:get_grindstone_formspec(pos, p_name, { result_disabled = has_all_cursed_ench })

        meta:set_string('formspec', formspec)
    end,
    ---@param pos Vector
    ---@param listname string
    ---@param index number
    ---@param stack ItemStack
    ---@param player ObjectRef
    ---@diagnostic disable-next-line: unused-local
    on_metadata_inventory_take = function(pos, listname, index, stack, player)
        local meta = minetest.get_meta(pos)
        local p_name = player:get_player_name()
        local inv = meta:get_inventory()
        local result_stack = inv:get_stack('result', 1)
        local item_stack = inv:get_stack('item', 1)
        local item_stack_meta = item_stack:get_meta()
        local has_all_cursed_ench = XEnchanting:has_all_cursed_ench(item_stack)
        local stack_enchantment_data = minetest.deserialize(item_stack_meta:get_string('x_enchanting')) or {}
        local result_payment_stack = ItemStack({ name = 'default:mese_crystal_fragment' })

        if item_stack:is_empty() or has_all_cursed_ench then
            -- Remove result item
            inv:remove_item('result', result_stack)
        end

        -- Collect total result
        local result_total = 0

        if result_total == 0 then
            -- get payback result (excl. cursed enchantments) from the original item
            for id, value in pairs(stack_enchantment_data) do
                local ench_def = XEnchanting.enchantment_defs[id]
                local lvl_index

                if not ench_def.cursed then
                    -- find level index
                    for i, v in ipairs(ench_def.level_def) do
                        if v == stack_enchantment_data[id].value then
                            lvl_index = i
                        end
                    end
                end

                if lvl_index then
                    local final_level_range = ench_def.final_level_range[lvl_index]
                    local range_max = final_level_range[1] + final_level_range[2]
                    local range_min = math.floor(range_max / 2)
                    local result = math.random(range_min, range_max)

                    result_total = result_total + result
                end
            end

            if result_total > result_payment_stack:get_stack_max() then
                result_total = result_payment_stack:get_stack_max()
            elseif result_total == 0 then
                result_total = 1
            end
        end

        result_total = math.floor(result_total / 10)

        result_payment_stack:set_count(result_total)

        if listname == 'result' and result_stack:is_empty() then
            -- Drop payment result
            inv:set_stack('item', 1, ItemStack(''))
            minetest.item_drop(result_payment_stack, player, player:get_pos())

            minetest.sound_play('x_enchanting_disenchant', {
                gain = 0.3,
                pos = pos,
                max_hear_distance = 10
            }, true)

            -- particles
            local particlespawner_def = {
                amount = 50,
                time = 0.5,
                minpos = { x = pos.x - 0.5, y = pos.y + 0.5, z = pos.z - 0.5 },
                maxpos = { x = pos.x + 0.5, y = pos.y + 0.5, z = pos.z + 0.5 },
                minvel = { x = -1.5, y = -0.5, z = -1.5 },
                maxvel = { x = 1.5, y = -1.5, z = 1.5 },
                minacc = { x = -3.5, y = -6.5, z = -3.5 },
                maxacc = { x = 5.5, y = -7.5, z = 5.5 },
                minexptime = 0.5,
                maxexptime = 1,
                minsize = 0.5,
                maxsize = 1,
                texture = 'x_enchanting_scroll_particle.png^[colorize:#FFE5C2:256',
                glow = 1
            }

            minetest.add_particlespawner(particlespawner_def)
        end


        local formspec = XEnchanting:get_grindstone_formspec(pos, p_name)

        meta:set_string('formspec', formspec)
    end
})

----
-- Recipe
---

minetest.register_craft({
    output = 'x_enchanting:grindstone',
    recipe = {
        { 'group:stick', 'stairs:slab_stone', 'group:stick' },
        { 'group:wood', '', 'group:wood' }
    }
})