local internal_api = ...

local S = internal_api.translate_single

local function dir_to_rotation(dir)
	local ax, az = math.abs(dir.x), math.abs(dir.z)
	if ax > az then
		if dir.x < 0 then return "270" end
		return "90"
	end
	if dir.z < 0 then return "180" end
	return "0"
end

function area(player, pos, tool_master_level, square)
	local rotation = dir_to_rotation(core.yaw_to_dir(player:get_look_horizontal()))

	local y_mod, long_mod, short_mod
	if square then
		y_mod = 0
		long_mod = (tool_master_level * 3) - 1
		short_mod = tool_master_level + 1
	else
		y_mod = tool_master_level
		long_mod = tool_master_level * 2
		short_mod = tool_master_level
	end

	local min_x, min_z, max_x, max_z

	if rotation == "270" then
		min_z = -short_mod
		min_x = 0
		max_z = short_mod
		max_x = -long_mod
	elseif rotation == "90" then
		min_z = -short_mod
		min_x = 0
		max_z = short_mod
		max_x = long_mod
	elseif rotation == "180" then
		min_x = -short_mod
		min_z = 0
		max_x = short_mod
		max_z = -long_mod
	else
		min_x = -short_mod
		min_z = 0
		max_x = short_mod
		max_z = long_mod
	end

	local min = vector.round(vector.offset(pos, min_x, -y_mod, min_z))
	local max = vector.round(vector.offset(pos, max_x, y_mod, max_z))

	return min, max
end

local crop_to_seed = {
	["mcl_farming:beetroot"] = {
		seed = "mcl_farming:beetroot_seeds",
		seedling = "mcl_farming:beetroot_0",
	},
	["mcl_farming:carrot"] = {
		seed = "mcl_farming:carrot_item",
		seedling = "mcl_farming:carrot_1",
	},
	["mcl_farming:potato"] = {
		seed = "mcl_farming:potato_item",
		seedling = "mcl_farming:potato_1",
	},
	["mcl_farming:wheat"] = {
		seed = "mcl_farming:wheat_seeds",
		seedling = "mcl_farming:wheat_1",
	},
	["mcl_core:reeds"] = {replant = 1},
	["mcl_core:cactus"] = {replant = 1},
}

local function replant(player, pos, crop)
	local seed = crop_to_seed[crop]["seed"]
	local seedling = crop_to_seed[crop]["seedling"]
	local inv = player:get_inventory()

	-- player has a seed?
	if crop_to_seed[crop]["replant"] then
		core.add_node(pos, {name = crop, param2 = core.registered_nodes[crop].place_param2})
	elseif inv:contains_item("main", seed) then
		inv:remove_item("main", seed .. " 1")
		local fakestack = ItemStack(seed .. " 1")
		local under = vector.offset(pos, 0, -2, 0)
		local ground = {above = pos, type = "node", under = under}
		mcl_farming:place_seed(fakestack, player, ground, seedling)
	end
end

-- ==== Yoinked from Mineclonia mcl_item_entity.lua ====

-- Stupid workaround to get drops from a drop table:
-- Create a temporary table in core.registered_nodes that contains the proper drops,
-- because unfortunately core.get_node_drops needs the drop table to be inside a registered node definition
-- (very ugly)
local function get_drops(drop, toolname, param2, paramtype2)
	local tmp_node_name = "mcl_item_entity:TMP_NODE"
	core.registered_nodes[tmp_node_name] = {
		name = tmp_node_name,
		drop = drop,
		paramtype2 = paramtype2,
	}
	local drops = core.get_node_drops({name = tmp_node_name, param2 = param2}, toolname)
	core.registered_nodes[tmp_node_name] = nil
	return drops
end

local function discrete_uniform_distribution(drops, min_count, max_count, cap)
	local new_drops = table.copy(drops)
	for i, item in ipairs(drops) do
		local new_item = ItemStack(item)
		local multiplier = math.random(min_count, max_count)
		if cap then multiplier = math.min(cap, multiplier) end
		new_item:set_count(multiplier * new_item:get_count())
		new_drops[i] = new_item
	end
	return new_drops
end

local function get_fortune_drops(fortune_drops, fortune_level)
	local drop
	local i = fortune_level
	repeat
		drop = fortune_drops[i]
		i = i - 1
	until drop or i < 1
	return drop or {}
end

local function handle_node_drops(pos, drops, digger)
	local inv = digger:get_inventory()

	-- Check if node will yield its useful drop by the digger's tool
	local dug_node = core.get_node(pos)
	local tooldef
	local tool
	local is_book
	if digger and digger:is_player() then
		tool = digger:get_wielded_item()
		is_book = tool:get_name() == "mcl_enchanting:book_enchanted"
		tooldef = core.registered_items[tool:get_name()]

		if not mcl_autogroup.can_harvest(dug_node.name, tool:get_name(), digger) then return end
	end

	local diggroups = tooldef and tooldef._mcl_diggroups
	local shearsy_level = diggroups and diggroups.shearsy and diggroups.shearsy.level

	--[[ Special node drops when dug by shears by reading _mcl_shears_drop or with a silk touch tool reading _mcl_silk_touch_drop
	from the node definition.
	Definition of _mcl_shears_drop / _mcl_silk_touch_drop:
	* true: Drop itself when dug by shears / silk touch tool
	* table: Drop every itemstring in this table when dug by shears _mcl_silk_touch_drop
	]]

	local enchantments = tool and mcl_enchanting.get_enchantments(tool)

	local silk_touch_drop = false
	local nodedef = core.registered_nodes[dug_node.name]
	if not nodedef then return end

	if shearsy_level and shearsy_level > 0 and nodedef._mcl_shears_drop then
		if nodedef._mcl_shears_drop == true then
			drops = {dug_node.name}
		else
			drops = nodedef._mcl_shears_drop
		end
	elseif tool and not is_book and enchantments.silk_touch and nodedef._mcl_silk_touch_drop then
		silk_touch_drop = true
		if nodedef._mcl_silk_touch_drop == true then
			drops = {dug_node.name}
		else
			drops = nodedef._mcl_silk_touch_drop
		end
	end

	if tool and not is_book and nodedef._mcl_fortune_drop and enchantments.fortune then
		local fortune_level = enchantments.fortune
		local fortune_drop = nodedef._mcl_fortune_drop
		local simple_drop = nodedef._mcl_fortune_drop.drop_without_fortune
		if fortune_drop.discrete_uniform_distribution then
			local min_count = fortune_drop.min_count
			local max_count = fortune_drop.max_count + fortune_level * (fortune_drop.factor or 1)
			local chance = fortune_drop.chance or fortune_drop.get_chance and
				               fortune_drop.get_chance(fortune_level)
			if not chance or math.random() < chance then
				drops = discrete_uniform_distribution(fortune_drop.multiply and drops or
					                                      fortune_drop.items, min_count, max_count,
				                                      fortune_drop.cap)
			elseif fortune_drop.override then
				drops = {}
			end
		else
			-- Fixed Behavior
			local drop = get_fortune_drops(fortune_drop, fortune_level)
			drops = get_drops(drop, tool:get_name(), dug_node.param2, nodedef.paramtype2)
		end

		if simple_drop then for _, item in pairs(simple_drop) do table.insert(drops, item) end end
	end

	if digger and mcl_experience.add_xp and not silk_touch_drop then
		local experience_amount = core.get_item_group(dug_node.name, "xp")
		if experience_amount > 0 then
			mcl_experience.set_xp(digger, mcl_experience.get_xp(digger) + experience_amount)
		end
	end

	for _, item in ipairs(drops) do
		if type(item) == "string" then item = ItemStack(item) end

		local leftover = inv:add_item("main", item)
		if leftover:get_count() > 0 then core.add_item(digger:get_pos(), leftover) end
	end
end

-- ======== END YOINK ======

local function dig_area(player, pos, tool_master_level, kind, tool, oldnode, square)
	local min, max = area(player, pos, tool_master_level, square)
	local nn = core.find_nodes_in_area(min, max, kind)

	local caps = tool:get_tool_capabilities()
	local tool_name = tool:get_name()
	local wear = tool:get_wear()

	for _, n in pairs(nn) do
		if core.is_protected(n, player:get_player_name()) then
			core.record_protection_violation(n, player:get_player_name())
		else
			local node = core.get_node(n)
			local crop = node.name
			local def = core.registered_nodes[crop]
			local params = core.get_dig_params(def.groups, caps)
			-- If oldnode is set then only dig supported, grown, crops
			if def and ((not oldnode) or crop_to_seed[crop]) then
				local drops = core.get_node_drops(crop, tool_name)
				local counted_drops = {}

				for _, item in ipairs(drops) do
					if type(item) ~= "string" then item = item:get_name() .. item:get_count() end
					table.insert(counted_drops, item)
				end

				handle_node_drops(n, counted_drops, player)

				core.remove_node(n)
				for _, callback in pairs(core.registered_on_dignodes) do callback(n, node) end

				if crop_to_seed[crop] then replant(player, n, crop) end

				-- This will handle unbreaking, etc.
				tool:add_wear(params.wear)
				wear = wear + params.wear
				player:set_wielded_item(tool)
				if wear >= 65534 then return end
			end
		end
	end

	-- The original thing dug won't be matched, so replace it
	if oldnode and crop_to_seed[oldnode.name] then
		if core.is_protected(pos, player:get_player_name()) then
			core.record_protection_violation(pos, player:get_player_name())
		else
			replant(player, pos, oldnode.name)
		end
	end
end

local function right_click_area(player, pos, tool_master_level, kind)
	local min, max = area(player, pos, tool_master_level)
	local nn = core.get_objects_in_area(min, max, kind)

	for _, obj in pairs(nn) do
		if obj and not obj:is_player() then
			local ent = obj:get_luaentity()
			local def = core.registered_entities[ent.name]
			if def.on_rightclick then ent:on_rightclick(player) end
		end
	end
end

local function axe_strip(player, pos, tool_master_level, kind, itemstack)
	local min, max = area(player, pos, tool_master_level)
	local nn = core.find_nodes_in_area(min, max, kind)

	for _, n in pairs(nn) do
		if core.is_protected(n, player:get_player_name()) then
			core.record_protection_violation(n, player:get_player_name())
		else
			-- BUGBUG do wear?
			mcl_trees.strip_tree(itemstack, player,
			                     {type = "node", under = n, above = {x = n.x, y = n.y + 1, z = n.z}})
		end
	end
end

local function create_soil(pos)
	if pos == nil then return false end

	local node = core.get_node(pos)
	local name = node.name
	local above = core.get_node({x = pos.x, y = pos.y + 1, z = pos.z})

	if core.get_item_group(name, "cultivatable") == 2 then
		if above.name == "air" then
			node.name = "mcl_farming:soil"
			core.set_node(pos, node)
			core.sound_play("default_dig_crumbly", {pos = pos, gain = 0.5}, true)
			return true
		end
	elseif core.get_item_group(name, "cultivatable") == 1 then
		if above.name == "air" then
			node.name = "mcl_core:dirt"
			core.set_node(pos, node)
			core.sound_play("default_dig_crumbly", {pos = pos, gain = 0.6}, true)
			return true
		end
	end

	return false
end

local function tool_master_level(player)
	local tool_master_level = 0
	local wielding = player:get_wielded_item()
	local is_tool = core.get_item_group(wielding:get_name(), "tool") > 0

	if is_tool then
		local enchantments = mcl_enchanting.get_enchantments(wielding)
		if enchantments.tool_master then tool_master_level = 0 + enchantments.tool_master end
	end

	return tool_master_level
end

local function place_area(player, pos, tool_master_level, kind)
	local min, max = area(player, pos, tool_master_level, true)
	local nn = core.find_nodes_in_area_under_air(min, max, kind)

	for _, npos in pairs(nn) do
		if core.is_protected(npos, player:get_player_name()) then
			core.record_protection_violation(npos, player:get_player_name())
		else
			create_soil(npos)
		end
	end
end

local function tool_master(player, pos, oldnode)
	if player and player:is_player() and not player.is_fake_player and
		player:get_player_control().sneak then
		local wielding = player:get_wielded_item()
		local is_tool = core.get_item_group(wielding:get_name(), "tool") > 0

		if is_tool then
			local tool_master_level = tool_master_level(player)

			if tool_master_level then
				local tool_name = wielding:get_name()
				local pickaxe = core.get_item_group(tool_name, "pickaxe") > 0
				local axe = core.get_item_group(tool_name, "axe") > 0
				local shovel = core.get_item_group(tool_name, "shovel") > 0
				local hoe = core.get_item_group(tool_name, "hoe") > 0
				local shears = core.get_item_group(tool_name, "shears") > 0

				if pickaxe then
					dig_area(player, pos, tool_master_level, {"group:pickaxey"}, wielding)
				end

				if axe then dig_area(player, pos, tool_master_level, {"group:tree"}, wielding) end

				if shovel then dig_area(player, pos, tool_master_level, {"group:shovely"}, wielding) end

				if hoe then
					dig_area(player, pos, tool_master_level, {"group:hoey", "group:plant"}, wielding,
					         oldnode, true)
				end

				if shears then
					right_click_area(player, pos, tool_master_level,
					                 {"group:shearsy", "group:shearsy_wool", "mobs_mc:sheep"})
				end
			end
		end
	end
end

core.override_item("mcl_tools:shears", {
	on_secondary_use = function(itemstack, player, pointed_thing)
		if pointed_thing.type == "object" and player:get_player_control().sneak then
			tool_master(player, pointed_thing.ref:get_pos())
		end
	end,
})

local tool_materials = {"wood", "stone", "iron", "gold", "diamond", "netherite"}
local extra_materials = {"mcl_emerald_stuff", "mcl_amethyst_stuff"}

local function hoe(def)
	local old_on_place = def.on_place
	def.on_place = function(itemstack, player, pointed_thing)
		itemstack = old_on_place(itemstack, player, pointed_thing)

		if pointed_thing.type == "node" and player:get_player_control().sneak then
			local tool_master_level = tool_master_level(player)
			if tool_master_level then
				dig_area(player, pointed_thing.above, tool_master_level, {"group:plant"},
				         player:get_wielded_item(), nil, true)
				place_area(player, pointed_thing.under, tool_master_level, {"group:cultivatable"})
			end
		end
		return itemstack
	end
end

local function axe(def)
	local old_on_place = def.on_place
	def.on_place = function(itemstack, player, pointed_thing)
		itemstack = old_on_place(itemstack, player, pointed_thing)

		if pointed_thing.type == "node" and player:get_player_control().sneak then
			local tool_master_level = tool_master_level(player)
			if tool_master_level then
				axe_strip(player, pointed_thing.under, tool_master_level, {"group:tree"}, itemstack)
			end
		end
		return itemstack
	end
end

for _, mat in pairs(tool_materials) do
	local hoe_def = core.registered_tools["mcl_tools:hoe_" .. mat]
	if hoe_def then hoe(hoe_def) end

	local axe_def = core.registered_tools["mcl_tools:axe_" .. mat]
	if axe_def then axe(axe_def) end
end

for _, mat in pairs(extra_materials) do
	local hoe_def = core.registered_tools[mat .. ":hoe"]
	if hoe_def then hoe(hoe_def) end

	local axe_def = core.registered_tools[mat .. ":axe"]
	if axe_def then axe(axe_def) end
end

core.register_on_dignode(
	function(pos, oldnode, player) tool_master(player, pos, oldnode) end)

mcl_enchanting.enchantments.tool_master = {
	name = S("Tool Master"),
	max_level = 3,
	primary = {pickaxe = true, shovel = true, axe = true, hoe = true, shears = true},
	secondary = {},
	disallow = {},
	incompatible = {},
	weight = 10,
	description = S("Apply tool use to an area"),
	curse = false,
	on_enchant = function() end,
	requires_tool = false,
	treasure = false,
	power_range_table = {{15, 61}, {24, 71}, {33, 81}},
	inv_combat_tab = false,
	inv_tool_tab = true,
}

-- implemented via walkover.register_global
mcl_enchanting.enchantments.lava_walker = {
	name = S("Lava Walker"),
	max_level = 2,
	primary = {},
	secondary = {armor_feet = true},
	disallow = {non_combat_armor = true},
	incompatible = {}, -- depth_strider = true },
	weight = 2,
	description = S("Turns lava beneath the player into cobble"),
	curse = false,
	on_enchant = function() end,
	requires_tool = false,
	treasure = true,
	power_range_table = {{10, 25}, {20, 35}},
	inv_combat_tab = true,
	inv_tool_tab = false,
}

walkover.register_global(function(pos, _, player)
	local boots = player:get_inventory():get_stack("armor", 5)
	local lava_walker = mcl_enchanting.get_enchantment(boots, "lava_walker")
	if lava_walker <= 0 then return end

	local min, max = area(player, pos, lava_walker)

	local positions = core.find_nodes_in_area_under_air(min, max, {
		"mcl_core:lava_source",
		"mcl_core:lava_flowing",
		"mcl_nether:nether_lava_source",
		"mcl_nether:nether_lava_flowing",
	})

	local play_sound = true
	for _, p in ipairs(positions) do
		if core.is_protected(p, player:get_player_name()) then
			core.record_protection_violation(p, player:get_player_name())
		else

			if play_sound then
				core.sound_play("fire_extinguish_flame",
				                {pos = pos, gain = 0.25, max_hear_distance = 16}, true)
				play_sound = false
			end

			core.set_node(p, {name = "mcl_core:cobble"})
		end
	end
end)
