function widget:GetInfo()
	return {
		name      = "Field Factory Selector",
		desc      = "Selects construction option from a factory",
		author    = "GoogleFrog, Invite part Helwor",
		date      = "2 April 2024",
		license   = "GNU GPL, v2 or later",
		layer     = 0,
		enabled   = true,  --  loaded by default?
		handler   = true,
	}
end

local Echo = Spring.Echo

local requirements = {
	exists = {
		[(WIDGET_DIRNAME or LUAUI_DIRNAME .. 'Widgets/') .. '-SelectionAPI.lua'] = {nil, nil, true}
	}
}


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

local CMD_FIELD_FAC_SELECT    = Spring.Utilities.CMD.FIELD_FAC_SELECT
local CMD_FIELD_FAC_UNIT_TYPE = Spring.Utilities.CMD.FIELD_FAC_UNIT_TYPE
local screenWidth, screenHeight = Spring.GetViewGeometry()

local OPT_WIDTH = 380
local OPT_HEIGHT = 148

local ROWS = 2
local COLUMNS = 6

local Chili
local optionsWindow

local _, factoryUnitPosDef = include("Configs/integral_menu_commands.lua", nil, VFS.RAW_FIRST)

local factoryDefs = {}
do

	local factories = {
		[[factoryshield]],
		[[factorycloak]],
		[[factoryveh]],
		[[factoryplane]],
		[[factorygunship]],
		[[factoryhover]],
		[[factoryamph]],
		[[factoryspider]],
		[[factoryjump]],
		[[factorytank]],
		[[factoryship]],
		[[striderhub]],
		[[plateshield]],
		[[platecloak]],
		[[plateveh]],
		[[plateplane]],
		[[plategunship]],
		[[platehover]],
		[[plateamph]],
		[[platespider]],
		[[platejump]],
		[[platetank]],
		[[plateship]],
	}

	for i = 1, #factories do
		local factoryName = factories[i]
		factoryDefs[UnitDefNames[factoryName].id] = true
	end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- declarations for the Invite part


local fieldFacRange = {}
for unitDefID = 1, #UnitDefs do
	local ud = UnitDefs[unitDefID]
	if ud.customParams.field_factory then
		fieldFacRange[unitDefID] = ud.buildDistance + 128
	end
end
local plateOfFac = {}
local facOfPlate = {}

for i = 1, #UnitDefs do
	local ud = UnitDefs[i]
	local cp = ud.customParams
	if cp.child_of_factory then
		facOfPlate[i] = UnitDefNames[cp.child_of_factory].id
	end
	if cp.parent_of_plate then
		plateOfFac[i] = UnitDefNames[cp.parent_of_plate].id
	end
end

-- local CMD_FIELD_FAC_QUEUELESS = Spring.Utilities.CMD.FIELD_FAC_QUEUELESS

local OPT_EXTRA_HEIGHT = 120
local OPT_MINI_WIDTH = 300
local OPT_MINI_HEIGHT = 115
local OPT_MINI_EXTRA_HEIGHT = 80

local UPDATE_RATE = 30
local invSize = 60 -- default size
local rX = 7/11 -- relative position in the UI
local rY = 7/10  

local vsx, vsy = Spring.Orig.GetViewSizes()
local invX = vsx * rX
local invY = vsy * rY
local invite, invited, warned = false, false, false
local fieldIcon = 'LuaUI/Images/commands/Bold/fac_select.png'
local offsetY = 0
local targettedFacs = {}
local gamePaused = false
local tweakMode, gamePaused = false, false
local time
local field_tweak_win
local wh -- we don't have the real widgetHandler at loading

-- speed ups
local spGetUnitsInCylinder 		= Spring.GetUnitsInCylinder
local spGetUnitPosition 		= Spring.GetUnitPosition
local spGetUnitDefID 			= Spring.GetUnitDefID
local spGetUnitIsStunned 		= Spring.GetUnitIsStunned
local spGetScreenGeometry 		= Spring.GetScreenGeometry
local spIsGUIHidden 			= Spring.IsGUIHidden
local spGetUnitIsStunned		= Spring.GetUnitIsStunned
local spGetUnitRulesParam		= Spring.GetUnitRulesParam
local spGetSelectedUnitsSorted 		= Spring.GetselectedUnitsSorted

local ALLY_UNITS 	= Spring.ALLY_UNITS

local glPushMatrix 	= gl.PushMatrix
local glTexture 	= gl.Texture
local glTranslate 	= gl.Translate
local glScale		= gl.Scale
local glTexRect 	= gl.TexRect
local glBillboard 	= gl.Billboard
local glPopMatrix 	= gl.PopMatrix
local glColor		= gl.Color


local function MakeTweakWin() -- a window only to be interacted with during tweak mode
	field_tweak_win = {
		parent = WG.Chili.Screen0,
		name = 'field_tweak_win',
		-- NOTE: dockable beeing true  and if the control has fixed name, the window pos and size are recovered after unloading/reloading widget (use dockableSavePositionOnly=true to not really dock but only save position)
		-- but what if user doesn't have docking enabled
		-- dockable = true, 
		-- dockableSavePositionOnly = true,
		minWidth = 30,
		minHeight = 30,
		maxHeight = 100,
		maxWidth = 100,
		hitpadding = {0,0,0,0}, -- default is 4,4,4,4 -- need to lower it to catch grip when window is small
		x = invX,
		y = invY,
		width = invSize,
		height = invSize,
		fixedRatio = true,
		resizable = false,
		draggable = false,
		OnResize = { function(self)
			invSize = self.width
			-- resize grip area doesn't adapt with small window, and only bottom and right are changed to -1, -1 in tweakmode even if it visually appear shrinked
			local min = math.min
			local grip = self.boxes.resize -- -21 -21 -10 -10 as default
			grip[1], grip[2] = - min(invSize/4, 21), - min(invSize/4, 21)
			 -- grip[3], grip[4] = - min(invSize/10, 10), - min(invSize/10, 10) -- no need if we're doing this only in tweak mode
		end},
		tweakDraggable = true,
		tweakResizable = true,
		borderThickness = 0,
		color = {0,0,0,0}, -- avoid the window flashing between the time the tweak mode goes off and the time we can detect it happened (there's no direct callin to inform us)
	}
	WG.Chili.Window:New(field_tweak_win)
end



-------------------------------------------------------------------------------

--------------------------------------------------------------------------------

local function GetOptionsPosition(width, height)
	local x, y = Spring.ScaledGetMouseState()
	y = screenHeight - y
	x = x - width / 2
	y = y - height - 20
	
	if x + width > screenWidth - 2 then
		x = screenWidth - width - 2
	end
	if y + height > screenHeight - 2 then
		y = screenHeight - height - 2
	end
	
	local map = WG.MinimapPosition
	if map then
		-- Only move tooltip up and/or left if it overlaps the minimap. This is because the
		-- minimap does not have tooltips.
		if x < map[1] + map[3] and y < map[2] + map[4] then
			local inX = x + width - map[1] + 2
			local inY = y + height - map[2] + 2
			if inX > 0 and inY > 0 then
				if inX > inY then
					y = y - inY
				else
					x = x - inX
				end
			end
		end
		
		if x < 2 then
			x = 2
		end
		if y < 2 then
			y = 2
		end
		if x + width > screenWidth - 2 then
			x = screenWidth - width - 2
		end
		if y + height > screenHeight - 2 then
			y = screenHeight - height - 2
		end
	end
	
	return math.floor(x), math.floor(y)
end

local function GetButton(parent, x, y, unitDefID, ud, unitName, isInvited, offsetY, factoryID, stunned, mini)
	-- TODO Uniformize the size handling
	local xStr = tostring((x - 1)*100/COLUMNS) .. "%"
	local yStr 
	local height

	if offsetY then
		height = mini and 40 or 59
		yStr = (offsetY or 0) + (y-1) * (mini and 40 or 59)
	else
		height = "50%"
		yStr = tostring((y - 1)*100/ROWS) .. "%"
	end
	local function DoClick()
		if unitDefID then
			if isInvited then
				-- since we use insertion, overriding the nextDesiredUnitType from gadget before
				Spring.GiveOrder(CMD_FIELD_FAC_UNIT_TYPE, {unitDefID}, 0)
				Spring.GiveOrder(CMD.INSERT, {0, CMD_FIELD_FAC_SELECT, CMD.OPT_SHIFT, factoryID}, CMD.OPT_ALT)

				-- alternative, but then it doesn't wait for factory to be finished
				-- Spring.GiveOrder(CMD_FIELD_FAC_QUEUELESS, {factoryID, unitDefID}, 0)
			else
				Spring.GiveOrder(CMD_FIELD_FAC_UNIT_TYPE, {unitDefID}, 0)
			end
		else
			if not isInvited then
				Spring.GiveOrder(CMD_FIELD_FAC_UNIT_TYPE, {-1}, 0)
			end
			-- Spring.GiveOrder(CMD.INSERT, {0, CMD_FIELD_FAC_UNIT_TYPE, 0, -1}, CMD.OPT_ALT)
		end
		invited = false
		optionsWindow:Dispose()
		optionsWindow = false
		if gamePaused then -- renew the invite after the pick
			return widget:GameFrame(UPDATE_RATE)
		end
	end

	local button = Chili.Button:New {
		name = name,
		x = xStr,
		y = yStr,
		width = "16.7%",
		height = height,
		caption = false,
		noFont = true,
		padding = {0, 0, 0, 0},
		parent = parent,
		preserveChildrenOrder = true,
		tooltip = (unitName and "BuildUnit" .. unitName) or "Cancel",
		OnClick = {DoClick},
		backgroundColor = stunned and {0.9,0.4,0.2,1} or nil,
		focusColor = stunned and {0.9,0.4,0.2,1} or nil,
		-- backgroundHoveredColor = stunned and {0.9,0.4,0.2,1} or nil,

	}

	if unitDefID then
		Chili.Label:New {
			name = "bottomLeft",
			x = "15%",
			right = 0,
			bottom = 2,
			height = 12,
			fontsize = 12,
			parent = button,
			caption = ud.metalCost,
		}
		Chili.Image:New {
			x = "5%",
			y = "4%",
			right = "5%",
			bottom = 12,
			keepAspect = false,
			file = "#" .. unitDefID,
			file2 = WG.GetBuildIconFrame(ud),
			parent = button,
		}
	else
		Chili.Image:New {
			x = "7%",
			y = "10%",
			right = "7%",
			bottom = "10%",
			keepAspect = true,
			file = "LuaUI/Images/commands/Bold/cancel.png",
			parent = button,
		}
	end
end

local function GenerateOptionsSelector(factoryID, isInvited, combine)
	-- TODO Uniformize the size handling
	if not combine then
		warned = false
	end
	local mini = vsy < 850
	if not factoryID and isInvited then
		GetButton(optionsWindow.children[1], 1, 2, nil, nil, nil, isInvited, isInvited and offsetY, false, false, mini)
		return
	end
	if optionsWindow and not combine then
		optionsWindow:Dispose()
		optionsWindow = false
	end
	local unitDefID = Spring.ValidUnitID(factoryID) and Spring.GetUnitDefID(factoryID)
	if not unitDefID then
		return
	end
	if not Spring.AreTeamsAllied(Spring.GetUnitTeam(factoryID), Spring.GetMyTeamID()) then
		return
	end
	if not factoryDefs[unitDefID] then
		return
	end
	local ud = UnitDefs[unitDefID]
	if not ud then
		return
	end
	local name = ud.name
	local buildList = ud.buildOptions
	local layoutData = factoryUnitPosDef[name]
	if not buildList then
		return
	end

	local width, height, extraHeight, bottomGap
	if mini then
		height = OPT_MINI_HEIGHT
		width = OPT_MINI_WIDTH
		extraHeight = OPT_MINI_EXTRA_HEIGHT
	else
		height = OPT_HEIGHT
		width = OPT_WIDTH
		extraHeight = OPT_EXTRA_HEIGHT
	end

	if combine then
		offsetY = offsetY + extraHeight
	else
		offsetY = 0
	end
	

	local stunned, _, inbuild = spGetUnitIsStunned(factoryID)
	stunned = stunned or ((spGetUnitRulesParam(factoryID, "totalEconomyChange") or 1) <= 0)
	if stunned and not warned then
		bottomGap = mini and 21 or 24
		height = height + bottomGap
		extraHeight = extraHeight + bottomGap
	else
		bottomGap = 0
	end

	
	local x, y = GetOptionsPosition(width, height)
	if combine then
		optionsWindow:SetPos(nil,optionsWindow.y -  extraHeight, nil, optionsWindow.height + extraHeight)
	else
		optionsWindow = Chili.Window:New{
			x = x,
			y = y,
			width = width,
			height = height,
			padding = {14, 22, 14, 10},
			classname = "main_window_small",
			textColor = {1, 1, 1, 0.55},
			parent = Chili.Screen0,
			dockable  = false,
			resizable = false,
			caption = "Select blueprint to copy:",
			backgroundColor = {0,0,0,0}
		}
		if WG.MakeMinizable then
			WG.MakeMinizable(optionsWindow)
		end
		optionsWindow:BringToFront()
	end
	local panel
	if combine then
		panel = optionsWindow.children[1]
	else
		panel = Chili.Panel:New{
			x = 0,
			y = 0,
			right = 0,
			bottom = bottomGap,
			padding = {0, 0, 0, 0},
			backgroundColor = {1, 1, 1, 0},
			parent = optionsWindow,
		}
	end
	
	if stunned and not warned then
		Chili.Label:New{
			x      = 0,
			right  = 0,
			bottom = 0,
			height = bottomGap,
			caption = inbuild and "Warning: Factory must be complete" or "Warning: Factory must be functional",
			align  = "center",
			autosize = false,
			font   = {
				color = {0.9,0.4,0.2,1},
				size = mini and 15 or 16,
				outline = true,
				outlineWidth = 1,
				outlineWeight = 1,
			},
			parent = optionsWindow,
		}
		warned = true
	end

	for i = 1, #buildList do
		local buildDefID = buildList[i]
		local bud = UnitDefs[buildDefID]
		local buildName = bud.name
		local position = buildName and layoutData and layoutData[buildName]
		local row, col
		if position then
			col, row = position.col, position.row
		else
			row = (i > 6) and 2 or 1
			col = (i - 1)%6 + row
		end
		GetButton(panel, col, row, buildDefID, bud, buildName, isInvited, isInvited and offsetY, factoryID, stunned, mini)
	end
	if not isInvited then
		GetButton(panel, 1, 2)
	end
end

local function DrawInvite()
	if spIsGUIHidden() then
		return
	end
	if field_tweak_win.dragging then -- no OnDragging callin
		invX, invY = field_tweak_win.x, field_tweak_win.y
		rX, rY = invX / vsx, invY / vsy
	end

	glPushMatrix()
	local scale = WG.uiScale
	glScale(scale, scale, 1)
	glTranslate(invX, vsy/scale - (invY + invSize), 0)
	local t = (time -  os.clock())%2 - 1.2
	glColor(1, 1, 1, 0.35 + (t < 0 and - t or t))
	glTexture(fieldIcon)
	glTexRect(0, 0, invSize, invSize)
	glTexture(false)
	glPopMatrix()
	glColor(1, 1, 1, 1)
end
local function EndInvite()
	if invite then
		invite = false
		-- widgetHandler:RemoveWidgetCallIn('DrawScreen', widget)  -- we can't remove DrawScreen or TweakDrawScreen will not trigger
	end
end
local function DevelopInvite()
	EndInvite()

	invited = true
	local count = 0
	for _, factoryID in pairs(targettedFacs) do
		count = count + 1
		GenerateOptionsSelector(factoryID, true, count > 1)
	end
	targettedFacs = false
	GenerateOptionsSelector(nil, true)
	optionsWindow:UpdateLayout()
end

local function MakeInvite()
	if not invite then
		invite = true
		if not tweakMode then
			time = os.clock() + 1
		end
		-- widgetHandler:UpdateWidgetCallIn('DrawScreen', widget) -- we can't remove DrawScreen or TweakDrawScreen will not trigger
	end
end

------------------------------------------------------------------
------------------------------------------------------------------

-- CallIns

function widget:CommandNotify(cmdID, params, options)
	if (cmdID == CMD_FIELD_FAC_SELECT) and params and params[1] then
		EndInvite()
		invited = false
		GenerateOptionsSelector(params[1])
		return false
	end
	--if optionsWindow then
	--	optionsWindow:Dispose()
	--	Spring.GiveOrder(CMD_FIELD_FAC_UNIT_TYPE, {-1}, 0)
	--	optionsWindow = false
	--end
end


function widget:MousePress(x,y,button)
	if optionsWindow then
		if WG.uiScale and WG.uiScale ~= 1 then
			x, y = x/WG.uiScale, y/WG.uiScale
		end
		if not Chili.Screen0:IsAbove(x,y) then
			optionsWindow:Dispose()
			optionsWindow = false
			if invited then
				invited = false
				widget:GameFrame(UPDATE_RATE)
				return true
			else
				Spring.GiveOrder(CMD_FIELD_FAC_UNIT_TYPE, {-1}, 0)
			end
		end
	else
		if button == 1 and invite then
			y = vsy - y
			if  x > invX and x < invX + invSize
			and y > invY and y < invY + invSize
			then
				DevelopInvite()
				return true
			end
		end
	end
end

function widget:DrawScreen()
	if tweakMode and not wh.tweakMode then
		tweakMode = false
		field_tweak_win:Hide()
	end
	if invite or tweakMode then
		DrawInvite()
	end
end

function widget:TweakDrawScreen()
	if not tweakMode then -- no callin to get informed we're entering Tweak mode
		tweakMode = true
		field_tweak_win:Show()
		time = os.clock() + 1
	end
end




local spFindUnitCmdDesc = Spring.FindUnitCmdDesc
function widget:CommandsChanged()
	candidates = false
	targettedFacs = false
	EndInvite()
	invited = false
	if optionsWindow then
		optionsWindow:Dispose()
		optionsWindow = false
	end
	if not WG.commandMap or WG.commandMap[CMD_FIELD_FAC_SELECT] then
		for defID, units in pairs(WG.selectionDefID or spGetSelectedUnitsSorted()) do
			local range = fieldFacRange[defID]
			if range then
				local valid, v = {}, 0
				for i, id in ipairs(units) do
					if spFindUnitCmdDesc(id, CMD_FIELD_FAC_SELECT) then
						v = v + 1
						valid[v] = id
					end
				end
				if v > 0 then
					if not candidates then candidates = {} end
					candidates[range] = valid
				end
			end
		end
		if candidates and gamePaused then
			widget:GameFrame(UPDATE_RATE)
		end
	end
end

function widget:GamePaused(_, isPaused)
	gamePaused = isPaused
end

local function IsBroken(id)
	local stunned, _, inbuild = spGetUnitIsStunned(id)
	return stunned or inbuild or ((spGetUnitRulesParam(id, "totalEconomyChange") or 1) <= 0)
end

function widget:GameFrame(f)
	if candidates and f%UPDATE_RATE == 0 then
		if not optionsWindow then
			targettedFacs = false
			local broken
			for range, units in pairs(candidates) do
				for _, candidateID in ipairs(units) do
					local x, y, z = spGetUnitPosition(candidateID)
					local around = spGetUnitsInCylinder(x, z, range, ALLY_UNITS)
					for _, id in ipairs(around) do
						local defID = spGetUnitDefID(id)
						if factoryDefs[defID] then
							if not targettedFacs then 
								targettedFacs = { [defID] = id }
								if IsBroken(id) then
									broken = {[id] = true}
								end
							else
								local correspondingDefID = facOfPlate[defID] or plateOfFac[defID]
								local oneAlready = targettedFacs[defID]
									or correspondingDefID and targettedFacs[correspondingDefID]
								if not oneAlready or broken and (broken[oneAlready] and not broken[id]) then
									if correspondingDefID then
										targettedFacs[ correspondingDefID ] = nil
									end
									if IsBroken(id) then
										if not broken then broken = {} end
										broken[id] = true
									end
									-- Echo('add', id, UnitDefs[defID].name, 'isBroken', broken[id])
									targettedFacs[defID] = id

								end
							end
						end
					end
				end
			end
			if targettedFacs then
				MakeInvite()
			else
				EndInvite()
			end
		end
	end
end
-- saving position
function widget:GetConfigData()
	return {rX = rX, rY = rY, invSize = invSize}
end
function widget:SetConfigData(data)
	if data.rX then
		rX, rY = data.rX, data.rY
		invX, invY = vsx * rX, vsy * rY
		invSize = data.invSize
	end
end

function widget:ViewResize(x, y)
	screenWidth = x/WG.uiScale
	screenHeight = y/WG.uiScale
	vsx, vsy = x, y
	invX, invY = rX * vsx, rY * vsy
end



function widget:Initialize()
	if widget.Requires and not widget:Requires(requirements) then
		return
	end

	-- widgetHandler:RemoveWidgetCallIn('DrawScreen', widget)  -- we can't remove DrawScreen or TweakDrawScreen will not trigger
	widget:ViewResize(gl.GetViewSizes()) -- or Spring.Orig.GetViewSizes
	Chili = WG.Chili

	wh = widgetHandler
	MakeTweakWin()

	field_tweak_win:Hide()


	widget:CommandsChanged()
	if Spring.GetGameFrame() > 0 then
		widget:GameFrame(UPDATE_RATE)
	end
end
