class QuickHackableHelper
{

	public static function TranslateActionsIntoQuickSlotCommands( const actions : array< DeviceAction >, commands : ref< array< QuickhackData > >, gameObject : GameObject, scriptableComponentPS : ScriptableDeviceComponentPS )
	{
		var i, i1 : Int32;
		var newCommand : QuickhackData;
		var sAction : ScriptableDeviceAction;
		var choice : InteractionChoice;
		var actionRecord : ObjectAction_Record;
		var actionStartEffects : array< weak< ObjectActionEffect_Record > >;
		var statModifiers : array< weak< StatModifier_Record > >;
		var emptyChoice : InteractionChoice;
		var actionCompletionEffects : array< weak< ObjectActionEffect_Record > >;
		var playerRef : PlayerPuppet;
		var playerQHacksList : array< PlayerQuickhackData >;
		var actionMatchDeck : Bool;
		var isQueuePerkBought : Bool;
		var iceLVL : Float;
		var actionOwnerName : CName;
		var DEBUG_playerTweaks : array< TweakDBID >;
		var DEBUG_playerActionNames : array< CName >;
		var DEBUG_targetTweaks : array< TweakDBID >;
		playerRef = GetPlayer( gameObject.GetGame() );
		iceLVL = QuickHackableHelper.GetICELevel( gameObject );
		actionOwnerName = StringToName( gameObject.GetDisplayName() );
		playerQHacksList = RPGManager.GetPlayerQuickHackListWithQuality( playerRef );
		if( !( IsFinal() ) )
		{
			for( i = 0; i < playerQHacksList.Size(); i += 1 )
			{
				actionRecord = playerQHacksList[ i ].actionRecord;
				DEBUG_playerTweaks.PushBack( playerQHacksList[ i ].actionRecord.GetID() );
				if( actionRecord.ObjectActionType().Type() != gamedataObjectActionType.DeviceQuickHack && actionRecord.ObjectActionType().Type() != gamedataObjectActionType.VehicleQuickHack )
				{
					continue;
				}
				DEBUG_playerActionNames.PushBack( actionRecord.ActionName() );
			}
			for( i = 0; i < actions.Size(); i += 1 )
			{
				DEBUG_targetTweaks.PushBack( ( ( ScriptableDeviceAction )( actions[ i ] ) ).GetObjectActionRecord().GetID() );
			}
		}
		for( i = 0; i < DEBUG_playerTweaks.Size(); i += 1 )
		{
		}
		for( i = 0; i < DEBUG_playerActionNames.Size(); i += 1 )
		{
		}
		for( i = 0; i < DEBUG_targetTweaks.Size(); i += 1 )
		{
		}
		if( playerQHacksList.Size() == 0 )
		{
			newCommand = new QuickhackData;
			newCommand.m_title = "LocKey#42171";
			newCommand.m_isLocked = true;
			newCommand.m_actionState = EActionInactivityReson.Invalid;
			newCommand.m_actionOwnerName = StringToName( gameObject.GetDisplayName() );
			newCommand.m_description = "LocKey#42172";
			newCommand.m_noQuickhackData = true;
			commands.PushBack( newCommand );
		}
		else
		{
			for( i = 0; i < playerQHacksList.Size(); i += 1 )
			{
				newCommand = new QuickhackData;
				sAction = NULL;
				actionStartEffects.Clear();
				actionRecord = playerQHacksList[ i ].actionRecord;
				if( actionRecord.ObjectActionType().Type() != gamedataObjectActionType.DeviceQuickHack && actionRecord.ObjectActionType().Type() != gamedataObjectActionType.VehicleQuickHack )
				{
					continue;
				}
				actionMatchDeck = false;
				for( i1 = 0; i1 < actions.Size(); i1 += 1 )
				{
					sAction = ( ( ScriptableDeviceAction )( actions[ i1 ] ) );
					if( actionRecord.ActionName() == sAction.GetObjectActionRecord().ActionName() )
					{
						actionMatchDeck = true;
						if( actionRecord.Priority() >= sAction.GetObjectActionRecord().Priority() )
						{
							sAction.SetObjectActionID( playerQHacksList[ i ].actionRecord.GetID() );
						}
						else
						{
							actionRecord = sAction.GetObjectActionRecord();
						}
						newCommand.m_uploadTime = sAction.GetActivationTime();
						if( ( ( Device )( gameObject ) ) )
						{
							newCommand.m_duration = scriptableComponentPS.GetDistractionDuration( sAction );
						}
						else
						{
							newCommand.m_duration = sAction.GetDurationValue();
						}
						break;
					}
				}
				newCommand.m_itemID = playerQHacksList[ i ].itemID;
				newCommand.m_actionOwnerName = actionOwnerName;
				newCommand.m_title = LocKeyToString( actionRecord.ObjectActionUI().Caption() );
				newCommand.m_description = LocKeyToString( actionRecord.ObjectActionUI().Description() );
				newCommand.m_icon = actionRecord.ObjectActionUI().CaptionIcon().TexturePartID().GetID();
				newCommand.m_iconCategory = actionRecord.GameplayCategory().IconName();
				newCommand.m_type = actionRecord.ObjectActionType().Type();
				newCommand.m_actionOwner = gameObject.GetEntityID();
				newCommand.m_isInstant = false;
				newCommand.m_ICELevel = iceLVL;
				newCommand.m_ICELevelVisible = false;
				newCommand.m_vulnerabilities = scriptableComponentPS.GetActiveQuickHackVulnerabilities();
				newCommand.m_actionState = EActionInactivityReson.Locked;
				newCommand.m_quality = playerQHacksList[ i ].quality;
				newCommand.m_costRaw = BaseScriptableAction.GetBaseCostStatic( playerRef, actionRecord );
				newCommand.m_category = actionRecord.HackCategory();
				actionCompletionEffects.Clear();
				actionRecord.CompletionEffects( actionCompletionEffects );
				newCommand.m_actionCompletionEffects = actionCompletionEffects;
				actionRecord.StartEffects( actionStartEffects );
				for( i1 = 0; i1 < actionStartEffects.Size(); i1 += 1 )
				{
					if( actionStartEffects[ i1 ].StatusEffect().StatusEffectType().Type() == gamedataStatusEffectType.PlayerCooldown )
					{
						actionStartEffects[ i1 ].StatusEffect().Duration().StatModifiers( statModifiers );
						newCommand.m_cooldown = RPGManager.CalculateStatModifiers( statModifiers, gameObject.GetGame(), playerRef, playerRef.GetEntityID(), playerRef.GetEntityID() );
						newCommand.m_cooldownTweak = actionStartEffects[ i1 ].StatusEffect().GetID();
						statModifiers.Clear();
					}
					if( newCommand.m_cooldown != 0.0 )
					{
						break;
					}
				}
				if( actionMatchDeck )
				{
					if( !( ( ( GenericDevice )( gameObject ) ) ) )
					{
						choice = emptyChoice;
						choice = sAction.GetInteractionChoice();
						if( TDBID.IsValid( choice.choiceMetaData.tweakDBID ) )
						{
							newCommand.m_titleAlternative = LocKeyToString( TweakDBInterface.GetInteractionBaseRecord( choice.choiceMetaData.tweakDBID ).Caption() );
						}
					}
					newCommand.m_cost = sAction.GetCost();
					newCommand.m_awarenessCost = sAction.GetAwarenessCost( gameObject.GetGame() );
					newCommand.m_willReveal = WillHackRevealPlayer( playerRef, GameObject.GetTDBID( gameObject ), sAction, newCommand.m_itemID );
					newCommand.m_showRevealInfo = ShouldShowRevealInfo( playerRef, newCommand.m_awarenessCost );
					if( sAction.IsInactive() )
					{
						newCommand.m_isLocked = true;
						newCommand.m_inactiveReason = sAction.GetInactiveReason();
						if( gameObject.HasActiveQuickHackUpload() )
						{
							newCommand.m_action = sAction;
						}
					}
					else
					{
						if( StatusEffectSystem.ObjectHasStatusEffect( playerRef, newCommand.m_cooldownTweak ) )
						{
							newCommand.m_isLocked = true;
							newCommand.m_inactiveReason = "LocKey#7019";
						}
						if( !( sAction.CanPayCost( NULL, true ) ) )
						{
							newCommand.m_actionState = EActionInactivityReson.OutOfMemory;
							newCommand.m_isLocked = true;
							newCommand.m_inactiveReason = "LocKey#27398";
						}
						if( GameInstance.GetStatPoolsSystem( gameObject.GetGame() ).HasActiveStatPool( gameObject.GetEntityID(), gamedataStatPoolType.QuickHackUpload ) )
						{
							isQueuePerkBought = PlayerDevelopmentSystem.GetData( playerRef ).IsNewPerkBought( gamedataNewPerkType.Intelligence_Left_Milestone_2 ) == 2;
							if( !( isQueuePerkBought ) )
							{
								newCommand.m_isLocked = true;
								newCommand.m_inactiveReason = "LocKey#27398";
							}
						}
						if( !( sAction.IsInactive() ) || gameObject.HasActiveQuickHackUpload() )
						{
							newCommand.m_action = sAction;
						}
					}
				}
				else
				{
					newCommand.m_isLocked = true;
					newCommand.m_inactiveReason = "LocKey#10943";
				}
				newCommand.m_actionMatchesTarget = actionMatchDeck;
				if( !( newCommand.m_isLocked ) )
				{
					newCommand.m_actionState = EActionInactivityReson.Ready;
				}
				commands.PushBack( newCommand );
			}
		}
		for( i = 0; i < commands.Size(); i += 1 )
		{
			if( commands[ i ].m_isLocked && commands[ i ].m_action )
			{
				( ( ScriptableDeviceAction )( commands[ i ].m_action ) ).SetInactiveWithReason( false, commands[ i ].m_inactiveReason );
			}
		}
		QuickhackModule.SortCommandPriority( commands, gameObject.GetGame() );
	}

	public static function GetICELevel( gameObject : GameObject ) : Float
	{
		var playerLevel : Float;
		var targetLevel : Float;
		var resistance : Float;
		var statsSystem : StatsSystem;
		statsSystem = GameInstance.GetStatsSystem( gameObject.GetGame() );
		playerLevel = statsSystem.GetStatValue( GetPlayer( gameObject.GetGame() ).GetEntityID(), gamedataStatType.Level );
		targetLevel = statsSystem.GetStatValue( gameObject.GetEntityID(), gamedataStatType.PowerLevel );
		resistance = statsSystem.GetStatValue( gameObject.GetEntityID(), gamedataStatType.HackingResistance );
		return resistance + ( 0.5 * ( targetLevel - playerLevel ) );
	}

	public static function CanActivateOverclockedState( player : GameObject ) : Bool
	{
		var isReplacer : Bool;
		var gameInstance : GameInstance;
		if( !( player ) )
		{
			return false;
		}
		gameInstance = player.GetGame();
		isReplacer = GameInstance.GetBlackboardSystem( gameInstance ).Get( GetAllBlackboardDefs().UI_PlayerStats ).GetBool( GetAllBlackboardDefs().UI_PlayerStats.isReplacer );
		return ( ( !( isReplacer ) && ( GameInstance.GetStatsSystem( gameInstance ).GetStatValue( player.GetEntityID(), gamedataStatType.HasCyberdeck ) > 0.0 ) ) && GameInstance.GetStatsSystem( gameInstance ).GetStatBoolValue( player.GetEntityID(), gamedataStatType.CanUseOverclock ) ) && ( PlayerDevelopmentSystem.GetData( player ).IsNewPerkBought( gamedataNewPerkType.Intelligence_Central_Milestone_3 ) == 3 );
	}

	public static function TryToCycleOverclockedState( player : GameObject ) : Bool
	{
		var isOverclockActivated : Bool;
		var isOverclockOnCooldown : Bool;
		if( !( CanActivateOverclockedState( player ) ) )
		{
			return false;
		}
		isOverclockActivated = IsOverclockedStateActive( player );
		isOverclockOnCooldown = !( GameInstance.GetStatPoolsSystem( player.GetGame() ).HasStatPoolValueReachedMax( player.GetEntityID(), gamedataStatPoolType.CyberdeckOverclock ) );
		if( isOverclockActivated || isOverclockOnCooldown )
		{
			GameObject.PlaySound( player, 'ui_gmpl_perk_overclock_deactivate' );
			if( isOverclockActivated )
			{
				GameInstance.GetStatPoolsSystem( player.GetGame() ).RequestSettingStatPoolMinValue( player.GetEntityID(), gamedataStatPoolType.CyberdeckOverclock, player );
				GameInstance.GetDelaySystem( player.GetGame() ).DelayEventNextFrame( player, new RefreshQuickhackMenuEvent );
			}
			StatusEffectHelper.RemoveStatusEffect( player, QuickHackableHelper.GetOverclockedStateTweakDBID() );
			GameInstance.GetRazerChromaEffectsSystem( player.GetGame() ).StopAnimation( 'Overclock' );
		}
		else
		{
			StatusEffectHelper.ApplyStatusEffect( player, GetOverclockedStateTweakDBID() );
			GameInstance.GetDelaySystem( player.GetGame() ).DelayEventNextFrame( player, new RefreshQuickhackMenuEvent );
			GameInstance.GetRazerChromaEffectsSystem( player.GetGame() ).PlayAnimation( 'Overclock', true );
			return true;
		}
		return false;
	}

	public static function IsOverclockedStateActive( player : GameObject ) : Bool
	{
		if( !( player ) )
		{
			return false;
		}
		return StatusEffectSystem.ObjectHasStatusEffect( player, GetOverclockedStateTweakDBID() );
	}

	public constexpr static function GetOverclockedStateTweakDBID() : TweakDBID
	{
		return T"BaseStatusEffect.Intelligence_Central_Milestone_3_Overclock_Buff";
	}

	public static function CanPayWithHealthInOverclockedState( player : GameObject, memoryCost : Int32, out healthReduction : Float ) : Bool
	{
		var statPoolsSystem : StatPoolsSystem;
		var currentHealth : Float;
		var currentMemory : Float;
		var overclockedStateHealthCost : Float;
		statPoolsSystem = GameInstance.GetStatPoolsSystem( player.GetGame() );
		currentHealth = statPoolsSystem.GetStatPoolValue( player.GetEntityID(), gamedataStatPoolType.Health, false );
		currentMemory = statPoolsSystem.GetStatPoolValue( player.GetEntityID(), gamedataStatPoolType.Memory, false );
		overclockedStateHealthCost = GameInstance.GetStatsSystem( player.GetGame() ).GetStatValue( player.GetEntityID(), gamedataStatType.OverclockedStateHealthCost );
		if( currentMemory >= ( ( Float )( memoryCost ) ) )
		{
			healthReduction = 0.0;
			return false;
		}
		healthReduction = overclockedStateHealthCost * ( ( ( Float )( memoryCost ) ) - currentMemory );
		return ( currentHealth - healthReduction ) > 0.0;
	}

	public static function PayWithHealthInOverclockedState( player : GameObject, memoryCost : Int32 ) : Bool
	{
		var statPoolsSystem : StatPoolsSystem;
		var healthReduction : Float;
		var currentMemory : Float;
		statPoolsSystem = GameInstance.GetStatPoolsSystem( player.GetGame() );
		if( CanPayWithHealthInOverclockedState( player, memoryCost, healthReduction ) )
		{
			GameObject.PlaySoundEvent( player, 'ui_focus_mode_quickhack_perk_overclock' );
			statPoolsSystem.RequestChangingStatPoolValue( player.GetEntityID(), gamedataStatPoolType.Health, -( healthReduction ), player, false, false );
			currentMemory = statPoolsSystem.GetStatPoolValue( player.GetEntityID(), gamedataStatPoolType.Memory, false );
			if( currentMemory > 0.0 )
			{
				statPoolsSystem.RequestChangingStatPoolValue( player.GetEntityID(), gamedataStatPoolType.Memory, -( currentMemory ), player, false, false );
			}
			return true;
		}
		return false;
	}

	public constexpr static function WillHackRevealPlayer( playerRef : PlayerPuppet, targetRecordId : TweakDBID, action : ScriptableDeviceAction, optional programItemID : ItemID ) : Bool
	{
		var npcWillCounterHack : Bool;
		var isForcedQHUploadAwarenessBumps : Bool;
		var itemRecord : Item_Record;
		var isUntraceableHack : Bool;
		var playerQHacksList : array< PlayerQuickhackData >;
		var playerQHackData : PlayerQuickhackData;
		var i : Int32;
		npcWillCounterHack = NPCManager.HasTag( targetRecordId, 'WillCounterHack' );
		isForcedQHUploadAwarenessBumps = StatusEffectSystem.ObjectHasStatusEffect( playerRef, T"BaseStatusEffect.ForcedQHUploadAwarenessBumps" );
		if( programItemID == ItemID.None() )
		{
			playerQHacksList = RPGManager.GetPlayerQuickHackListWithQuality( playerRef );
			for( i = 0; i < playerQHacksList.Size(); i += 1 )
			{
				playerQHackData = playerQHacksList[ i ];
				if( playerQHackData.actionRecord.ActionName() == action.GetObjectActionRecord().ActionName() )
				{
					programItemID = playerQHackData.itemID;
					break;
				}
			}
		}
		if( programItemID != ItemID.None() )
		{
			itemRecord = TweakDBInterface.GetItemRecord( ItemID.GetTDBID( programItemID ) );
			isUntraceableHack = itemRecord.OnAttachContains( TweakDBInterface.GetGameplayLogicPackageRecord( T"EquipmentGLP.UntraceableHack" ) );
		}
		else
		{
			isUntraceableHack = action.GetAwarenessCost( playerRef.GetGame() ) == 0.0;
		}
		return ( ( !( isUntraceableHack ) && !( playerRef.IsBeingRevealed() ) ) && !( isForcedQHUploadAwarenessBumps ) ) && ( !( playerRef.IsInCombat() ) || npcWillCounterHack );
	}

	public constexpr static function ShouldShowRevealInfo( playerRef : PlayerPuppet, awarenessCost : Float ) : Bool
	{
		var statsSystem : StatsSystem;
		var isForcedQHUploadAwarenessBumps : Bool;
		var ignoreAwarenessBumpingAbility : Bool;
		statsSystem = GameInstance.GetStatsSystem( playerRef.GetGame() );
		isForcedQHUploadAwarenessBumps = StatusEffectSystem.ObjectHasStatusEffect( playerRef, T"BaseStatusEffect.ForcedQHUploadAwarenessBumps" );
		ignoreAwarenessBumpingAbility = statsSystem.GetStatBoolValue( playerRef.GetEntityID(), gamedataStatType.IgnoreAwarenessCostWhenOverclocked );
		return ( ( awarenessCost != 0.0 ) && !( ignoreAwarenessBumpingAbility ) ) && ( playerRef.IsBeingRevealed() || isForcedQHUploadAwarenessBumps );
	}

	public constexpr static function ShouldShowRevealInfoWithTarget( playerRef : PlayerPuppet, target : ScriptedPuppet, awarenessCost : Float ) : Bool
	{
		var ignoreAwarenessBumpingSonicShock : Bool;
		ignoreAwarenessBumpingSonicShock = StatusEffectSystem.ObjectHasStatusEffectWithTag( target, 'CommsNoiseJam' );
		return ( ShouldShowRevealInfo( playerRef, awarenessCost ) && !( ignoreAwarenessBumpingSonicShock ) ) || ( awarenessCost < 0.0 );
	}

}

class OnSpecialQuickhackTriggeredEvent extends DelayEvent
{
	var quickhackData : QuickhackData;
}

class OnMonowireQuickhackContagiousTargetStatusAppliedCallback extends DelayCallback
{
	var m_ContagiousTarget : weak< ScriptedPuppet >;

	public static function Create( targetScriptedPuppet : ScriptedPuppet ) : OnMonowireQuickhackContagiousTargetStatusAppliedCallback
	{
		var created : OnMonowireQuickhackContagiousTargetStatusAppliedCallback;
		created = new OnMonowireQuickhackContagiousTargetStatusAppliedCallback;
		created.m_ContagiousTarget = targetScriptedPuppet;
		return created;
	}

	public override function Call()
	{
		if( m_ContagiousTarget == NULL )
		{
			return;
		}
		StatusEffectHelper.ApplyStatusEffect( m_ContagiousTarget, T"BaseStatusEffect.MonoWireQuickhackContagiousHittableTarget" );
	}

}

class OnMonowireWindowToSpreadQuickhackCallback extends DelayCallback
{
	var m_MonoWireApplyQuickhackEffector : MonoWireQuickHackApplyEffector;
	var m_PlayerPuppet : weak< PlayerPuppet >;

	public static function Create( effector : MonoWireQuickHackApplyEffector, playerPuppet : PlayerPuppet ) : OnMonowireWindowToSpreadQuickhackCallback
	{
		var created : OnMonowireWindowToSpreadQuickhackCallback;
		created = new OnMonowireWindowToSpreadQuickhackCallback;
		created.m_MonoWireApplyQuickhackEffector = effector;
		created.m_PlayerPuppet = playerPuppet;
		return created;
	}

	public override function Call()
	{
		var blackboardSystem : BlackboardSystem;
		var blackboard : IBlackboard;
		var spreadableQuickhackActionID : TweakDBID;
		var hasTargetWithContagiousEffect : Bool;
		var i : Int32;
		var targetsToSpread : array< MonowireSpreadableNPC >;
		var spreadableQuickhackData : QuickhackData;
		blackboardSystem = GameInstance.GetBlackboardSystem( m_PlayerPuppet.GetGame() );
		blackboard = blackboardSystem.GetLocalInstanced( m_PlayerPuppet.GetEntityID(), GetAllBlackboardDefs().PlayerStateMachine );
		spreadableQuickhackActionID = ( ( TweakDBID )( blackboard.GetVariant( GetAllBlackboardDefs().PlayerStateMachine.MeleeSpreadableQuickhackActionID ) ) );
		targetsToSpread = m_MonoWireApplyQuickhackEffector.m_targetsToSpreadQuickhack;
		if( ( spreadableQuickhackActionID == TDBID.None() ) || ( targetsToSpread.Size() == 0 ) )
		{
			m_MonoWireApplyQuickhackEffector.ClearSpreadAttack();
			return;
		}
		for( i = 0; i < targetsToSpread.Size(); i += 1 )
		{
			if( StatusEffectSystem.ObjectHasStatusEffect( targetsToSpread[ i ].m_NPCPuppet, T"BaseStatusEffect.MonoWireQuickhackContagiousHittableTarget" ) )
			{
				hasTargetWithContagiousEffect = true;
				break;
			}
		}
		if( !( hasTargetWithContagiousEffect ) )
		{
			return;
		}
		blackboard.SetInt( GetAllBlackboardDefs().PlayerStateMachine.AmountOfCostFreeActions, targetsToSpread.Size() - 1 );
		for( i = 0; i < targetsToSpread.Size(); i += 1 )
		{
			if( StatusEffectSystem.ObjectHasStatusEffect( targetsToSpread[ i ].m_NPCPuppet, T"BaseStatusEffect.MonoWireQuickhackContagiousHittableTarget" ) )
			{
				continue;
			}
			spreadableQuickhackData = RPGManager.CreateSimpleQuickhackData( m_PlayerPuppet, targetsToSpread[ i ].m_NPCPuppet, TweakDBInterface.GetObjectActionRecord( spreadableQuickhackActionID ) );
			if( StatusEffectSystem.ObjectHasStatusEffect( targetsToSpread[ i ].m_NPCPuppet, T"BaseStatusEffect.MonoWireQuickhackContagiousHittableTarget" ) )
			{
				continue;
			}
			StatusEffectHelper.ApplyStatusEffectForTimeWindow( targetsToSpread[ i ].m_NPCPuppet, T"BaseStatusEffect.MonoWireQuickhackApliedBySpread", m_PlayerPuppet.GetEntityID(), spreadableQuickhackData.m_uploadTime, spreadableQuickhackData.m_duration );
			m_MonoWireApplyQuickhackEffector.SpawnFXs( targetsToSpread[ i ].m_HitEvent, targetsToSpread[ i ].m_NPCPuppet, false );
			m_MonoWireApplyQuickhackEffector.TriggerSpecialQuickHackAttack( m_PlayerPuppet, spreadableQuickhackData, 0.1 );
		}
		m_MonoWireApplyQuickhackEffector.ClearSpreadAttack();
	}

}

