﻿using RimWorld;
using System.Collections.Generic;
using UnityEngine;
using Verse;
using Verse.AI;
using Multiplayer.API;
using rjw.Modules.Interactions.Enums;
using rjw.Modules.Interactions;
using rjw.Modules.Interactions.Implementation;
using rjw.Modules.Interactions.Objects;

namespace rjw.RMB
{
	static class RMB_Menu
	{
		/// <summary>
		/// TargetingParameters for all things that can have RMB options
		/// </summary>
		private static TargetingParameters ForRmbOptions
		{
			get
			{
				if (targetParametersCache == null)
				{
					targetParametersCache = new TargetingParameters()
					{
						canTargetHumans = true,
						canTargetAnimals = true,
						canTargetItems = true,
						mapObjectTargetsMustBeAutoAttackable = false,
					};
				}
				return targetParametersCache;
			}
		}

		/// <summary>
		/// Don't use this directly, use <see cref="ForRmbOptions"/> instead
		/// </summary>
		private static TargetingParameters targetParametersCache = null;

		/// <summary>
		/// Adds RJW floating menu options to <paramref name="opts"/> list
		/// </summary>
		/// <param name="pawn">Selected pawn</param>
		/// <param name="opts">Full menu options list, vanilla options included. Do not clear it, please</param>
		/// <param name="clickPos">Where player clicked in the world</param>
		public static void AddSexFloatMenuOptions(Pawn pawn, List<FloatMenuOption> opts, Vector3 clickPos)
		{
			AcceptanceReport canControlPawn = CanControlPawn(pawn);
			if (!canControlPawn)
			{
				if (RJWSettings.DevMode)
					opts.Add(new FloatMenuOption($"No interactions: {canControlPawn.Reason}", null));

				return;
			}

			// Find valid targets for sex.
			IEnumerable<LocalTargetInfo> validTargets = GenUI.TargetsAt(clickPos, ForRmbOptions);

			foreach (LocalTargetInfo target in validTargets)
			{
				if (target == null)
				{
					continue;
				}

				if (target.Pawn != null)
				{
					Pawn targetPawn = target.Pawn;

					if (!targetPawn.Spawned)
					{
						if (RJWSettings.DevMode)
							opts.Add(new FloatMenuOption($"No interactions for {targetPawn}: Pawn not spawned", null));
						continue;
					}
					if (targetPawn.Drafted)
					{
						if (RJWSettings.DevMode)
							opts.Add(new FloatMenuOption($"No interactions for {targetPawn}: Pawn is drafted", null));
						continue;
					}
					if (targetPawn.IsHiddenFromPlayer())
					{
						if (RJWSettings.DevMode)
							opts.Add(new FloatMenuOption($"No interactions for {targetPawn}: Pawn is hidden", null));
						continue;
					}
				}

				// Ensure target is reachable.
				if (!pawn.CanReach(target, PathEndMode.ClosestTouch, Danger.Deadly))
				{
					//option = new FloatMenuOption("CannotReach".Translate(target.Thing.LabelCap, target.Thing) + " (" + "NoPath".Translate() + ")", null);
					continue;
				}

				AddFloatMenuOptionsForTarget(pawn, opts, target);
			}
		}

		/// <summary>
		/// Check if <paramref name="pawn"/> can be controlled by the player
		/// </summary>
		/// <returns>
		/// AcceptanceReport. Reason is an untranslated string and should only be shown in the DevMode
		/// </returns>
		private static AcceptanceReport CanControlPawn(Pawn pawn)
		{
			// If the pawn in question cannot take jobs, don't bother.
			if (pawn.jobs == null)
				return "Cannot take jobs";

			// If the pawn is drafted - quit.
			if (pawn.Drafted)
				return "Drafted";

			// Getting raped - no control
			if (pawn.jobs.curDriver is JobDriver_SexBaseRecieverRaped)
				return "Getting raped";

			//is colonist?, is hospitality colonist/guest?, no control for guests
			if (!pawn.IsFreeColonist || pawn.Faction == null || pawn.GetExtraHomeFaction(null) != null)
				return "Not a colonist";

			//not hero mode or override_control - quit
			if (!(RJWSettings.RPG_hero_control || RJWSettings.override_control))
				return "Enable hero mode or direct control";

			bool isHero = false;    //is hero
			bool otherPlayerHero = false;    //not owned hero? maybe prison check etc in future

			if (RJWSettings.RPG_hero_control)
			{
				isHero = pawn.IsDesignatedHero();
				otherPlayerHero = isHero && !pawn.IsHeroOwner();
			}
			else if (!RJWSettings.override_control)
				return "Not a hero, direct control disabled";

			//not hero, not override_control - quit
			if (!isHero && !RJWSettings.override_control)
				return "Not a hero";

			//not owned hero - quit
			if (isHero && otherPlayerHero)
				return "Not owned hero";

			if (pawn.IsPrisoner) // Removed slave restrictions - we can draft slaves, so why not control them directly?
				return "Prisoner";

			return true;
		}

		/// <summary>
		/// Adds RJW floating menu options to <paramref name="opts"/> list
		/// </summary>
		/// <param name="pawn">Selected pawn</param>
		/// <param name="opts">Full menu options list, vanilla options included. Do not clear it, please</param>
		/// <param name="target">Pawn or item player clicked on</param>
		private static void AddFloatMenuOptionsForTarget(Pawn pawn, List<FloatMenuOption> opts, LocalTargetInfo target)
		{
			RMB_Mechanitor.AddFloatMenuOptions(pawn, opts, target);

			if (pawn == target)
			{
				RMB_Masturbate.AddFloatMenuOptions(pawn, opts, target);
			}
			else if (target.Pawn != null)
			{
				if (xxx.is_human(target.Pawn))
				{
					RMB_Sex.AddFloatMenuOptions(pawn, opts, target);
				}

				if (xxx.is_animal(target.Pawn))
				{
					RMB_Bestiality.AddFloatMenuOptions(pawn, opts, target);
					RMB_RapeAnimal.AddFloatMenuOptions(pawn, opts, target);
				}
				else
				{
					RMB_Rape.AddFloatMenuOptions(pawn, opts, target);
				}

				RMB_Socialize.AddFloatMenuOptions(pawn, opts, target);
			}
			else if (target.Thing is Corpse)
			{
				RMB_Necro.AddFloatMenuOptions(pawn, opts, target);
			}
		}

		/// <summary>
		/// Generates sub-menu options for two pawns
		/// </summary>
		/// <param name="pawn">Initiator pawn</param>
		/// <param name="target">Reciever pawn or corpse</param>
		/// <param name="job"></param>
		/// <param name="rape"></param>
		/// <param name="reverse"></param>
		/// <returns>A list of menu options, where each item represents a possible interaction</returns>
		public static List<FloatMenuOption> GenerateNonSoloSexRoleOptions(Pawn pawn, LocalTargetInfo target, JobDef job, bool rape, bool reverse = false)
		{
			List<FloatMenuOption> opts = new();

			Pawn partner = target.Pawn;

			if (target.Thing is Corpse corpse)
			{
				partner = corpse.InnerPawn;
			}

			InteractionTag mainTag = InteractionTag.Consensual;      //sex
			if (xxx.is_animal(partner))
			{
				mainTag = InteractionTag.Bestiality;      //bestiality/breeding
			}
			else if (rape)
			{
				mainTag = InteractionTag.Rape;      //rape
			}

			ILewdInteractionValidatorService service = LewdInteractionValidatorService.Instance;

			foreach (InteractionDef d in SexUtility.SexInterractions)
			{
				var interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(d);
				if (interaction.Extension.rjwSextype == xxx.rjwSextype.None.ToStringSafe())
					continue;
				if (interaction.Extension.rjwSextype == xxx.rjwSextype.Masturbation.ToStringSafe())
					continue;
				if (!interaction.HasInteractionTag(mainTag))
					continue;
				if (reverse != interaction.HasInteractionTag(InteractionTag.Reverse))
					continue;
				if (!service.IsValid(d, pawn, partner))
					continue;

				string label = interaction.Extension.RMBLabel.CapitalizeFirst();
				if (RJWSettings.DevMode)
					label += $" ( defName: {d.defName} )";

				FloatMenuOption option = new(label, delegate () { HaveSex(pawn, job, target, d); }, MenuOptionPriority.High);
				opts.Add(FloatMenuUtility.DecoratePrioritizedTask(option, pawn, target));
			}

			if (RJWSettings.DevMode && opts.NullOrEmpty())
			{
				opts.Add(new FloatMenuOption("No interactions found", null));
			}

			return opts;
		}

		//multiplayer synch actions

		/// <summary>
		/// Stops current job of the <paramref name="pawn"/> and starts new sex job
		/// </summary>
		/// <param name="pawn"></param>
		/// <param name="jobDef"></param>
		/// <param name="target">Reciever pawn or corpse</param>
		/// <param name="interactionDef">Sex interaction def</param>
		[SyncMethod]
		public static void HaveSex(Pawn pawn, JobDef jobDef, LocalTargetInfo target, InteractionDef interactionDef)
		{
			Pawn partner;
			if (target.Thing is Corpse corpse)
			{
				partner = corpse.InnerPawn;
			}
			else if (target.Pawn == null || pawn == target.Pawn) // masturbation
			{
				partner = pawn;
			}
			else
			{
				partner = target.Pawn;
			}

			InteractionWithExtension interaction = Modules.Interactions.Helpers.InteractionHelper.GetWithExtension(interactionDef);

			bool rape = interaction.HasInteractionTag(InteractionTag.Rape);

			Job job;
			if (jobDef == xxx.casual_sex)
			{
				job = new Job(jobDef, target, partner.CurrentBed());
			}
			else if (jobDef == xxx.bestialityForFemale)
			{
				if (target.Pawn.ownership.OwnedBed == null) //Check if Partner have bed
					job = new Job(jobDef, target, pawn.ownership.OwnedBed); //Go to Pawn bed if partner don't have bed assigned
				else job = new Job(jobDef, target, target.Pawn.ownership.OwnedBed); //If Partner have bed - go to partner
			}
			else if (jobDef == xxx.Masturbate)
			{
				job = new Job(jobDef, pawn, null, target.Cell);
			}
			else
			{
				job = new Job(jobDef, target);
			}

			var SP = new SexProps
			{
				pawn = pawn,
				partner = partner,
				sexType = SexUtility.rjwSextypeGet(interactionDef),
				isRape = rape,
				isRapist = rape,
				canBeGuilty = false, //pawn.IsHeroOwner();//TODO: fix for MP someday
				dictionaryKey = interactionDef,
				rulePack = SexUtility.SexRulePackGet(interactionDef)
			};

			pawn.GetRJWPawnData().SexProps = SP;
			pawn.jobs.EndCurrentJob(JobCondition.InterruptForced);
			pawn.jobs.TryTakeOrderedJob(job);
		}
	}
}