---@diagnostic disable: undefined-global, undefined-doc-name, undefined-field
mineTable = {}
mineTable.newCheckSystem = false
mineTable.newCheckTime = 0.25

---Create or alter a mine table element
---@param keyString string This variable handles which weapon class to alter (NOTE it has to be a real weapon class name or else it will not do anything in game!)
---@param createMissile boolean This variable tells the mine function if there is any scripted additions to the detonation or not REQUIRED (No Nil)
---@param subMissile string|nil Can be nil to not alter, If the createmissile value is true this mine will create missile classes associated with this string MUST BE A REAL WEAPON NAME OR BUGS WILL HAPPEN Technically does not require a missile
---@param subMissileNumber number This handles the number of missiles made from the missile mines explosion. nil means not update and is more for error checking
---@param distanceUntilDetonate any This handles the distance until detonation. "FUN" This function should either be handled at the start of the game OR in SEXPs every mission (and I mean EVERY MISSION)
---@param targetLaunchedMissiles boolean This function handles if a launched missile is targeted or not. REQUIRED (No Nil)
---@param stealthMultiplerDistance number This variable handled the distance mines will detonate on stealth ships
function createMineTableElement(keyString,createMissile,subMissile,subMissileNumber, distanceUntilDetonate,targetLaunchedMissiles,stealthMultiplerDistance)
	if mineTable[keyString] == nil then
		mineTable[keyString] = {}
	end

	mineTable[keyString].createMissile = createMissile
	if subMissile ~= nil then
		mineTable[keyString].subMissile = subMissile
	end

	if subMissile ~= nil then
		mineTable[keyString].subMissileNumber = subMissileNumber
	end

	if distanceUntilDetonate ~= nil then
		mineTable[keyString].distanceUntilDetonate = distanceUntilDetonate
	end
	mineTable[keyString].targetedMissile = targetLaunchedMissiles --Tells the function to not assign launched missiles to a target
	mineTable[keyString].randomDetonate = {}
	
	if stealthMultiplerDistance ~= nil then
		mineTable[keyString].stealthMultiplerDistance = stealthMultiplerDistance
	else
		mineTable[keyString].stealthMultiplerDistance = 0
	end
end

---THis function removes the mine type from the script. Used for disabling non-functioned mines. Functioned mines can be disabled but not easily renabled. If you want to renable a functioned mine then write an SEXP for it, contact me on discord if you need help
---@param keyString string Mine To Destroy
function destroyMineTableElement(keyString)
	mineTable[keyString] = nil
end

---Adds target type/s for mine detonate on (i.e. fighter, bomber, cruiser, navbouy, whatever) Only does something if the mine exists in the script already
---@param keyString string Name of mine to add limitations
---@param targetsToDetonateOn table List of targets the mine is allowed to detonate on Just add the strings for the pair
---@param createRestriction boolean Switches the mode from creation to destruction which destroys all restrictions allow the mine to detonate on all hostile shiptypes
function designateTargetType(keyString,targetsToDetonateOn,createRestriction)
	if mineTable[keyString] ~= nil then
		if createRestriction  then -- Updates the detonation table to set what ships the mine can detonate on.
			mineTable[keyString].restrictDetonate = {}
			for index, value in ipairs(targetsToDetonateOn) do
				mineTable[keyString].restrictDetonate[value] = true
			end
		else
			mineTable[keyString].restrictDetonate = nil
		end
	end
end

---Designates if the mine directs the blast towards it's unfortunate victem
---@param keyString string Name of mine
---@param targetLead any Seconds to extrapolate distance
---@param delayVar any Delay between subsquent launches (i.e. Making a machinegun mine)
---@param laserMine boolean Determines if the mine will be a laser mine, set to false to return the mine to a standard missile mine
function designateDirectedMine(keyString,targetLead,delayVar,laserMine)
	if mineTable[keyString] ~= nil then
		if laserMine then
			mineTable[keyString].laserMine = {}
			mineTable[keyString].laserMine.delay = delayVar
			mineTable[keyString].laserMine.targetLead = targetLead
		else 
			mineTable[keyString].laserMine = nil
		end
	end
end

---Designates if the mine will always detonate on a sweep or withold detionation. Tested per enemy per sweep. Sweep in new mine detection is once every 0.25 seconds
---@param keyString string Mine to alter
---@param highCheck number Sets the upper bound for the Random Number Generator
---@param thresholdToDetonate number Sets the check's upper threshold i.e. 4 means any number greater than 4 will trigger the detionation.
---@param randomFail number Sets if the mine will actually detionate or not (I wish LUA can use function overloads)
function designateRandomDetonate(keyString,highCheck,thresholdToDetonate,randomFail)
	if mineTable[keyString] ~= nil then
		if randomFail then
			mineTable[keyString].randomFail = {}
			mineTable[keyString].randomFail.highCheck = highCheck
			mineTable[keyString].randomFail.thresholdDetonate = thresholdToDetonate
		else 
			mineTable[keyString].randomFail = nil
		end
	end
end

---Mine Function Checks for a class of weapon and scans for detonation if something is within the mine's detonation range. Mine types include Functional mines REQUIRED as this actually detonates the mines. Run at any interval you want including every frame (Not Recommended for the faildet check due to randomness that is not controllable but aside from that it should work) (Will Update soon)
---Mine types include Functional mines - Mines linked to a custom user function;
---Plain Mines, Mines which just get their weaponLife set to 0 and detonate normally.;
---Directed Mines - Mines that direct a projectile or missile towards a target (can either be homing or not);
---Missile Mines (Targeted and non-Targeted). Targted missile mines target enemies and unleash missiles while non-targeted just unleash missiles. use non-targeted missiles with independent heatseekers to get them to attack multiple targets;
---Mines are prioritized in the order given (i.e. a functional mine will deploy it's function and detonate normally while a directed mine will only activate if the mine is not any of the above and missile mines activate only if all the other additions are not selecteds)
function mineFunction()
	for i=1, #mn.Weapons do
		local weapon = mn.Weapons[i]
		local weaponName = weapon.Class.Name
		local LmineTable = mineTable[weaponName]
		if LmineTable ~= nil and weapon:isArmed() then
			local weaponPosition = weapon.Position
			local weaponParent = weapon.Parent
			for tempShip in mn.getShipList() do
				if LmineTable.restrictDetonate == nil or LmineTable.restrictDetonate[tempShip.Class.Type.Name] ~= nil then
					local distance
					if tempShip:getFlag("stealth") == false then -- Protected
						distance = LmineTable.distanceUntilDetonate
					else
						distance = LmineTable.distanceUntilDetonate * LmineTable.stealthMultiplerDistance
					end
					if weapon.Team:attacks(tempShip.Team) and weaponPosition:getDistance(tempShip.Position) < distance and tempShip:getFlag("protect-ship") == false  then
						if LmineTable.randomFail == nil or ba.rand32(1,LmineTable.randomFail.highCheck) >= LmineTable.randomFail.thresholdDetonate then
							if LmineTable.mineFunction ~= nil then
								LmineTable.mineFunction(weapon,tempShip)
							elseif LmineTable.createMissile then
								weapon.LifeLeft = 0;
								local mineMissile = LmineTable.subMissile
								local detonationNumber = LmineTable.subMissileNumber
								async.run(function()
									for i=1,detonationNumber do
										if LmineTable.laserMine ~= nil and tempShip:isValid() then --Check if the mine is a laser mine
											createTargetedProjectile(weaponPosition,mineMissile,weaponParent,tempShip,LmineTable.laserMine.targetLead)
											async.await(mn.waitAsync(LmineTable.laserMine.delay))
										elseif LmineTable.targetedMissile then -- Then Check if the mine targets its missiles
											spawnMissileMine(weaponPosition,mineMissile,weaponParent,tempShip)
											async.await(mn.waitAsync(0.01))
										else --check if the mine does not target it's missiles
											spawnMissileMine(weaponPosition,mineMissile,weaponParent,nil)
											async.await(mn.waitAsync(0.01))
										end
									end
								end)
							else
								weapon.LifeLeft = 0
							end
							break
						end
					end
				end
			end
		end
	end
end

function mineFunctionNew(weapon)
	if mineTable.newCheckSystem then
		local weaponName = weapon.Class.Name
		local LmineTable = mineTable[weaponName]
		if LmineTable ~= nil then
			async.run(function()
				async.await(mn.waitAsync(0.1))
				while weapon ~= nil and weapon:isValid() and mineTable.newCheckSystem do
					if weapon:isArmed() then
						local weaponPosition = weapon.Position
						local weaponParent = weapon.Parent
						for tempShip in mn.getShipList() do 
							if LmineTable.restrictDetonate == nil or LmineTable.restrictDetonate[tempShip.Class.Type.Name] ~= nil then
								local distance
								if tempShip:getFlag("stealth") == false then
									distance = LmineTable.distanceUntilDetonate
								else
									distance = LmineTable.distanceUntilDetonate * LmineTable.stealthMultiplerDistance
								end
								if weapon.Team:attacks(tempShip.Team) and weaponPosition:getDistance(tempShip.Position) < distance and tempShip:getFlag("protect-ship") == false then
									if LmineTable.randomFail == nil or ba.rand32(1,LmineTable.randomFail.highCheck) >= LmineTable.randomFail.thresholdDetonate then
										if LmineTable.mineFunction ~= nil then
											LmineTable.mineFunction(weapon,tempShip)
										elseif LmineTable.createMissile then
											weapon.LifeLeft = 0;
											local mineMissile = LmineTable.subMissile
											local detonationNumber = LmineTable.subMissileNumber
											async.run(function()
												for i=1,detonationNumber do
													if LmineTable.laserMine ~= nil and tempShip:isValid() then --Check if the mine is a laser mine
														createTargetedProjectile(weaponPosition,mineMissile,weaponParent,tempShip,LmineTable.laserMine.targetLead)
														async.await(mn.waitAsync(LmineTable.laserMine.delay))
													elseif LmineTable.targetedMissile then -- Then Check if the mine targets its missiles
														spawnMissileMine(weaponPosition,mineMissile,weaponParent,tempShip)
														async.await(mn.waitAsync(0.01))
													else --check if the mine does not target it's missiles
														spawnMissileMine(weaponPosition,mineMissile,weaponParent,nil)
														async.await(mn.waitAsync(0.01))
													end
												end
											end)
										else
											weapon.LifeLeft = 0
										end
										break
									end
								end
							end
						end
					end
					async.await(mn.waitAsync(mineTable.newCheckTime + (math.random(0,10)/100)))
				end
			end)
		end
	end
end

---This function creates a missile from a weapon (Used in Mines but could theoretically be used in anything involving weapons) Will spawn missiles even if target is inactive
---@param position vector Ship for missile to spawn from
---@param weapon string Missile type launched
---@param parent ship Ship that owns the mine
---@param target any This variable determines what the missile is targeting (or nil if targeting nothing)
function spawnMissileMine(position,weapon,parent,target)
    local mat = ba.createRandomVector():getOrientation()
    local newIri = mn.createWeapon(tb.WeaponClasses[weapon], mat, position + mat:getFvec()*20, parent) -- + (mat:getFvec() * 10))
    newIri.Team = parent.Team
	if target ~= nil and target:isValid() then
		newIri.Target = target
	end
end

---This function creates a targeted weapon aimed at an object with a lead (NOTE that lead can be set to 0 for no lead)
---@param position vector Determines where the weapon is deployed 
---@param weapon string Determines class of weapon to make
---@param parent ship Ship which will own the weapon
---@param target any Determines what the projectile is targeting and which direction the projectile will be fired
---@param lead number determines the lead (in seconds) from the hostile in question. i.e. a lead of one will extrapolate 1 second in front of the target while a lead of zero extrapolates no distance. Code stolen from Blighted
function createTargetedProjectile(position,weapon,parent,target,lead)
	local orient = ((target.Position + (target.Physics.Velocity * lead)) - position):getNormalized():getOrientation()
    local charge = mn.createWeapon(tb.WeaponClasses[weapon], orient, position, parent)
    charge.Team = parent.Team
end

---This function activates the mineFunction check script every 0.25 seconds.
function templateCheckFunction()
	async.run(function()
        while mineTable.newCheckSystem == false do
			mineFunction()
            async.await(mn.waitAsync(0.25))
        end
    end)
end

mn.LuaSEXPs["lua-mine-activate-altMethod"].Action = function(boolValue)
	mineTable.newCheckSystem = boolValue
end

mn.LuaSEXPs["lua-mine-change-altMethod"].Action = function(intValue)
	mineTable.newCheckSystem = intValue
end

--MINE SEXPS (Just versions of the regular events)
mn.LuaSEXPs["lua-mine-addOrMod-element"].Action = function(keyString,createMissile,subMissile,subMissileNumber, distanceUntilDetonate,targetLaunchedMissiles)
    local LkeyString = keyString.Name
	local LsubMissile = subMissile.Name
	createMineTableElement(LkeyString,createMissile,LsubMissile,subMissileNumber, distanceUntilDetonate,targetLaunchedMissiles)
end

mn.LuaSEXPs["lua-mine-destroy-element"].Action = function(keyString)
    LkeyString = keyString.Name
	destroyMineTableElement(LkeyString)
end

mn.LuaSEXPs["lua-mine-set-targetTypes"].Action = function(keyString,createTargetTypes,...)
    local LkeyString = keyString.Name
	local Ltable = {}
	for i,v in ipairs(arg) do
		Ltable[i] = v[1]
	end
	designateTargetType(LkeyString,Ltable,createTargetTypes)
end

mn.LuaSEXPs["lua-mine-set-Directed"].Action = function(keyString,targetLead,delayVar,laserMine)
    local LkeyString = keyString.Name
	designateDirectedMine(LkeyString,targetLead,delayVar,laserMine)
end

mn.LuaSEXPs["lua-mine-set-DetFailRate"].Action = function(keyString,highCheck,thresholdToDetonate,randomFail)
    local LkeyString = keyString.Name
	designateRandomDetonate(LkeyString,highCheck,thresholdToDetonate,randomFail)
end

mn.LuaSEXPs["lua-mine-set-active"].Action = function()
    templateCheckFunction()
end

--#region utilityFunctions
utilsTable = {}
mn.LuaSEXPs["lua-weapon-no-lifetime"].Action = function(weaponClass,infiniteLifetime)
	if utilsTable[weaponClass.Name] == nil then
		utilsTable[weaponClass.Name] = {}
	end
	utilsTable[weaponClass.Name].infiniteLifetime = infiniteLifetime
end


function weaponUtilsFunction(weapon)
    if utilsTable[weapon.Class.Name] ~= nil then
        if utilsTable[weapon.Class.Name].infiniteLifetime == true then
            async.run(function()
				async.await(mn.waitAsync(1))
                while weapon ~= nil and weapon:isValid() do
                    weapon.LifeLeft = weapon.Class.LifeMax - 1
                    async.await(mn.waitAsync(weapon.Class.LifeMax/2))
                end
            end)
        end
    end
end

mn.LuaSEXPs["lua-ship-as-mine"].Action = function(ship,weaponClass,detRange)
	async.run(function()
		while ship ~= nil and ship:isValid() and ship:isDying() == false do
			for tempShip in mn.getShipList() do 
				if ship.Team:attacks(tempShip.Team) and ship.Position:getDistance(tempShip.Position) < detRange then
					local weaponMine = mn.createWeapon(weaponClass, ship.Orientation, ship.Position, ship)
					weaponMine.Team = ship.Team
					ship:kill(ship); break;
				end
			end
			async.await(mn.waitAsync(0.25))
		end
	end)
end

mn.LuaSEXPs["lua-ship-as-mineRestricted"].Action = function(ship,weaponClass,detRange,...)
	local Ltable = {}
	for i,v in ipairs(arg) do
		Ltable[i] = v[1]
	end

	async.run(function()
		while ship ~= nil and ship:isValid() and ship:isDying() == false do
			for key,tempShip in pairs(Ltable) do
				if ship.Position:getDistance(tempShip.Position) < detRange then
					local weaponMine = mn.createWeapon(weaponClass, ship.Orientation, ship.Position, ship)
					weaponMine.Team = ship.Team
					ship:kill(ship); break;
				end
			end
			async.await(mn.waitAsync(0.25))
		end
	end)
end

mn.LuaSEXPs["lua-mine-create-minefield"].Action = function(weaponClass,numberOfMines,x1,x2,y1,y2,z1,z2,team)
	for i = 1, numberOfMines, 1 do
		local x
		local y
		local z
		if x1 < x2 then
			x = ba.rand32(x1,x2)
		else
			x = ba.rand32(x2,x1)
		end

		if y1 < y2 then
			y = ba.rand32(y1,y2)
		else
			y = ba.rand32(y2,y1)
		end

		if z1 < z2 then
			z = ba.rand32(z1,z2)
		else
			z = ba.rand32(z2,z1)
		end
		local weaponMine = mn.createWeapon(weaponClass, ba.createRandomVector():getOrientation(), ba.createVector(x,y,z))
		weaponMine.Team = team
	end
end
--#endregion

mineTable.customMineFunctions = {}

---Custom Mine Example This mine turns into a second mine which will track the target only to turn back into the first mine later EXAMPLE for all the scripters out there
---@param originalWeaponClass string Mine to turn back into
---@param secondWeaponClass string Mobile version of said mine
---@param mine userdata|nil weapon that is mine
---@param targetShip userdata target ship of mine
---@param distance number distance before mine stops target tracking (includes model radius)
mineTable.customMineFunctions["Homeworld Mines"] = function(originalWeaponClass,secondWeaponClass,mine,targetShip,distance)
	--Start running an async function to not hold up the mine sweep function
	if mine ~= nil then
		async.run(function()
			--Create the weapon that wil follow the target
			local weapon = mn.createWeapon(tb.WeaponClasses[secondWeaponClass], mine.Orientation, mine.Position, mine.Parent)
			weapon.Team = mine.Team

			--Designate target for the new mine
			if mine.Target ~= nil and mine.Target:isValid() then
				weapon.Target = targetShip
			end

			--delete the origional mine so it won't endlessly spawn more mines
			mine:vanish()

			--handle recursive function
			while weapon ~= nil and weapon:isValid() do
				--Handle weapon effects
				if weapon.Target ~= nil and weapon.Target:isValid() and weapon.Position:getDistance(weapon.Target.Position) <= (weapon.Target.Radius + distance) then

				else
					--Create new secondary weapon and remove the first one
					async.await(mn.waitAsync(0.25))
					local secWeapon = mn.createWeapon(tb.WeaponClasses[originalWeaponClass], weapon.Orientation, weapon.Position, weapon.Parent)
					secWeapon.Team = weapon.Team
					weapon:vanish()
					break
				end

				--Wait before repeating cycle to prevent a game freeze
				async.await(mn.waitAsync(0.25))
			end
		end)
	end
end



--[[
	Mine Script
		Created by CRZgatecrusher
	
	Request for those who use it:
		just leave the comments in the code please (I don't care too much about public credit or anything but a way for me to show people that the code was in your mod is nice)

	if you have any questions or feature requests for the script just ping me in the HLP discord (Invite Link is: https://discord.gg/QFdueKEYrN) or DM me or something. my username is: CRZgatecrusher#6853

	with this script my mines will blot out the sun.
]]