---@diagnostic disable: cast-local-type
-- ^--- we're using Vector3 here, so it gets confused
local Vector3 
if worldeditadditions_core then
	local wea_c = worldeditadditions_core
	Vector3 = dofile(wea_c.modpath.."/utils/vector3.lua")
else
	Vector3 = require("worldeditadditions_core.utils.vector3")
end

--- Parses an absolute axis name to a Vector3 instance.
-- @example
-- local v3instance = parse_abs_axis_name("-x")
-- -- v3instance will now be equal to Vector3.new(-1, 0, 0)
-- @param	axis_name	string	The axis name to parse.
-- @returns	bool,Vector3|string	A bool success value, followed by the resulting Vector3 instance (if succeeded) or an error message string (if failed).
local function parse_abs_axis_name(axis_name)
	if type(axis_name) ~= "string" then
		return false, "Error: Expected axis_name to be of type string, but found value of type '"..type(axis_name).."' instead."
	end
	
	local result = Vector3.new()
	if axis_name:match("-x") then result.x = -1
	elseif axis_name:match("x") then result.x = 1 end
	if axis_name:match("-y") then result.y = -1
	elseif axis_name:match("y") then result.y = 1 end
	if axis_name:match("-z") then result.z = -1
	elseif axis_name:match("z") then result.z = 1 end
	
	if Vector3.new() == result then
		return false, "Error: Unknown axis_name '"..axis_name.."'."
	end
	
	return true, result
end

-- Parses a relative axis name (e.g. "front", "back", etc) to an absolute
-- Vector3 instance.
-- @param	axis_name	string		The axis name to parse.
-- @param	player_name	PlayerDir	The directional information that the parsing should be relative to.
-- @returns	bool,Vector3|string	A bool success value, followed by the resulting Vector3 instance (if succeeded) or an error message string (if failed).
local function parse_relative_axis_name(axis_name, facing_dir)
	if type(axis_name) ~= "string" then
		return false, "Error: Expected axis_name to be of type string, value was of type '"..type(axis_name).."' instead."
	end
	
	local dir
	local flip = Vector3.new(-1, -1, -1)
	
	if axis_name == "?"							then dir = facing_dir.front	end
	if axis_name == "front"	or axis_name == "f"	then dir = facing_dir.front	end
	if axis_name == "back"	or axis_name == "b"	then dir = facing_dir.back	end
	if axis_name == "left"	or axis_name == "l"	then dir = facing_dir.left	end
	if axis_name == "right" or axis_name == "r"	then dir = facing_dir.right end
	if axis_name == "up"	or axis_name == "u"	then dir = facing_dir.up	end
	if axis_name == "down"	or axis_name == "d"	then dir = facing_dir.down	end
	
	if not dir then return false, "Error: Unknown axis name '"..axis_name.."'. Valid values: ?, front, f, back, b, left, l, right, r, up, u, down, d." end
	
	axis_name = (dir.sign < 0 and "-" or "")..dir.axis
	local success, result = parse_abs_axis_name(axis_name)
	if not success then return success, result end
	
	return true, result
end

--- Parses a relative or absolute axis name into a Vector3 instance.
-- @param	axis_name	string	The axis name to parse.
-- @param	facing_dir	table	The direction the player is facing. Obtain this by calling worldeditadditions_core.?????
-- TODO: Fix this comment.
local function parse_axis_name(axis_name, facing_dir)
	local success, result = parse_relative_axis_name(axis_name, facing_dir)
	if not success then
		success, result = parse_abs_axis_name(axis_name)
	end
	return success, result
end

local function vregion_apply(vpos1, vpos2, offset)
	-- It's a specific axis
	if offset.x < 0 then vpos1.x = vpos1.x + offset.x
	else vpos2.x = vpos2.x + offset.x end
	if offset.y < 0 then vpos1.y = vpos1.y + offset.y
	else vpos2.y = vpos2.y + offset.y end
	if offset.z < 0 then vpos1.z = vpos1.z + offset.z
	else vpos2.z = vpos2.z + offset.z end
	
	return vpos1, vpos2
end

local function in_list(list, str)
	for i,item in ipairs(list) do
		if item == str then return true end
	end
	return false
end

--- Parses a token list of axes and counts into a Vector3.
-- For example, "x 4" would become { x = 4, y = 0, z = 0 }, and "? 4 -z 10"
-- might become { x = 4, y = 0, z = -10 }.
-- Note that the input here needs to be *post split*. wea.split_shell is
-- recommended for this purpose.
-- @param	token_list	string[]	A list of tokens to parse
-- @param	facing_dir	PlayerDir	The direction the player is facing. Returned from wea.player_dir(name).
-- @returns	Vector3,Vector3		A Vector3 pair generated from parsing out the input token list representing the delta change that can be applied to a defined pos1, pos2 region.
local function parse_axes(token_list, facing_dir)
	local mirroring_keywords = {
		"sym", "symmetrical",
		"mirror", "mir",
		"rev", "reverse",
		"true"
	}
	
	if type(token_list) ~= "table" then
		return false, "Error: Expected list of tokens as a table, but found value of type '"..type(token_list).."' instead."
	end
	if type(facing_dir) ~= "table" then
		return false, "Error: Expected facing_dir to be a table, but found value of type '"..type(facing_dir).."' instead."
	end
	
	local vpos1, vpos2 = Vector3.new(), Vector3.new()
	
	if #token_list < 2 then
		return false, "Error: Not enough arguments (at least 2 are required)"
	end
	
	local state = "AXIS"
	local current_axis = nil
	local current_axis_text = nil
	local success
	
	-- for i,token in ipairs(token_list) do
	for i=1,#token_list do
		local token = token_list[i]
		local token_next
		if i < #token_list then token_next = token_list[i + 1] end
		
		if type(token) ~= "string" then
			return false, "Error: Found token of unexpected type '"..type(token).."' at position "..i.."."
		end
		
		-- print("DEBUG i", i, "token", token)
		
		if state == "AXIS" then
			if token == "h" or token == "horizontal" then
				current_axis_text = "horizontal"
				current_axis = Vector3.new(1, 0, 1)
			elseif token == "v" or token == "vertical" then
				current_axis_text = "vertical"
				current_axis = Vector3.new(0, 1, 0)
			elseif not in_list(mirroring_keywords, token) then
				current_axis_text = token
				success, current_axis = parse_axis_name(token, facing_dir)
				if not success then return success, current_axis end
			end
			
			state = "VALUE"
		elseif state == "VALUE" then
			
			local offset_this = tonumber(token)
			if not offset_this then
				return false, "Error: Invalid count value '"..tostring(token).."' for axis '"..current_axis_text.."'. Values may only be positive or negative integers."
			end
			
			offset_this = current_axis * offset_this
			
			-- print("DEBUG STATE VALUE offset_this", offset_this)
			
			-- Apply the new offset to the virtual defined region
			if current_axis_text == "horizontal" or current_axis_text == "vertical" then
				-- We're horizonal / vertical
				vpos1 = vpos1 + (offset_this * -1)
				vpos2 = vpos2 + offset_this
			else
				vpos1, vpos2 = vregion_apply(vpos1, vpos2, offset_this)
				-- Handle the extra mirroring keyword
				if in_list(mirroring_keywords, token_next) then
						vpos1, vpos2 = vregion_apply(vpos1, vpos2, offset_this * -1)
				end
			end
			
			state = "AXIS"
		else
			return false, "Error: Failed to parse input due to unknown state '"..tostring(state).."' (this is probably a bug - please report it!)"
		end
	end
	
	return true, vpos1, vpos2
end

return {
	parse_axes = parse_axes,
	parse_axis_name = parse_axis_name,
	parse_abs_axis_name = parse_abs_axis_name,
	parse_relative_axis_name = parse_relative_axis_name
}
