/* 
 * Copyright (c)2009-2025 DemiVision, LLC. All Rights Reserved. The information 
 * herein is the CONFIDENTIAL and PROPRIETARY information of DemiVision, LLC. 
 */

using DXLib.UI.Alert;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Provides all functionality for automatic lineup substitutions and libero swaps. Any preconfigured subs/swaps will be
 * processed when a sideout occurs. The user can be optionally asked for confirmation first.
 */
public class RecordAuto
{
	/* Constants */

	// Replace types
	public const string AutoSub = LineupEntryReplace.SubKey;
	public const string AutoSwap = LineupEntryReplace.SwapKey;
	public const string AutoBoth = "both";

	/* Properties */
	private bool HasReplace => HasSubs || HasSwaps;

	// Lineup has subs/swaps defined?
	private bool HasSubs => lineupSubs is { Count: > 0 };
	private bool HasSwaps => lineupSwaps is { Count: > 0 };

	/* Fields */
	private readonly RecordState sm;

	// From set lineup
	private readonly IList<LineupEntryReplace> lineupSubs;
	private readonly IList<LineupEntryReplace> lineupSwaps;

	// Pending confirmations
	private List<LineupEntryReplace> confirmSubs;
	private List<LineupEntryReplace> confirmSwaps;

	// Sub warnings
	private bool warned;

	/* Methods */
	public RecordAuto( RecordState sm, Lineup lineup )
	{
		this.sm = sm;

		// Cache subs/swaps from lineup
		lineupSubs = lineup?.Subs;
		lineupSwaps = lineup?.Swaps;
	}

	// Processes auto subs/swaps for specified rotation
	public void Process( string type, int rotation, bool start )
	{
		warned = false;

		// MUST clear each time
		confirmSubs = [];
		confirmSwaps = [];

		// Sub confirmations
		if ( HasSubs && type is AutoBoth or AutoSub )
		{
			ConfirmOrProcess( AutoSub, rotation, start );
		}

		// Swap confirmations
		if ( HasSwaps && type is AutoBoth or AutoSwap )
		{
			ConfirmOrProcess( AutoSwap, rotation, start );
		}

		// Prompt user
		if ( HasReplace )
		{
			Confirm( AutoSub );
		}
	}

	// Builds list of subs/swaps requiring confirmation, or processes immediately
	private void ConfirmOrProcess( string type, int rotation, bool start )
	{
		List<LineupEntryReplace> entries = GetEntries( type, rotation, start );

		// Check each sub/swap
		foreach ( LineupEntryReplace entry in entries )
		{
			// Only process if valid
			if ( IsValid( entry ) )
			{
				List<LineupEntryReplace> confirm = (type == AutoSub) ? confirmSubs : confirmSwaps;

				// Confirm first
				if ( entry.Confirm )
				{
					confirm.Add( entry );
				}
				// Handle immediately
				else
				{
					ProcessEntry( entry );
				}
			}
		}
	}

	// Determines if specified entry valid to be processed
	private bool IsValid( LineupEntryReplace entry )
	{
 		Player outPlayer = entry.OutEntry.Player;
		Player inPlayer = entry.InEntry.Player;

		RecordLineup lineup = sm.Lineup1;

		switch ( entry.Type )
		{
			// Sub OUT must be on court, IN must not already be on court
			case AutoSub:
			{
				bool outOnCourt = lineup.IsOnCourt( outPlayer );
				bool inOnCourt = lineup.IsOnCourt( inPlayer );

				return outOnCourt && !inOnCourt;
			}
			// Swap OUT must be on court in backrow, IN must be valid libero
			case AutoSwap:
			{
				bool outBackrow = lineup.IsBackrow( outPlayer, sm.Rotation1 );

				// Libero must be in libero zone or just rotated out
				int liberoZone = lineup.GetZone( inPlayer, sm.Rotation1 );
				bool liberoValid = LineupEntry.ZoneIsLibero( liberoZone ) || (liberoZone == Lineup.LiberoZoneOut);

				return outBackrow && liberoValid;
			}
		}

		return false;
	}

	// Prompts user to confirm subs/swaps, only one alert per type
	private void Confirm( string type )
	{
		List<LineupEntryReplace> entries = GetConfirms( type );
		int count = entries.Count;

		// Single replace
		if ( count > 0 )
		{
			// '#11 J.Smith in for #12 J.Doe'
			string label = entries[0].GetLabel();

			// Double replace
			if ( count > 1 )
			{
				string label2 = entries[1].GetLabel();

				// '#10 F.Bar in for #9 F.Baz'
				label += $"\n\n{label2}";
			}

			string title = DXString.Get( $"auto.{type}" );

			// Each entry on separate line
			DXAlert.ShowPositiveCancelRaw( title, label, "alert.ok", () => { OnConfirmed( type ); },
																	 () => { OnCancelled( type ); } );
		}
		// Handle swaps after subs
		else
		{
			if ( type == AutoSub )
			{
				Confirm( AutoSwap );
			}
		}
	}

	// Processes sub/swap for specified entry (either immediate or post-confirmation)
	private void ProcessEntry( LineupEntryReplace entry )
	{
		// Validate subs remaining
		if ( entry.Type == AutoSub )
		{
			if ( (sm.Subs1 < 1) && !Shell.Settings.IsUnlimitedSubs && !warned )
			{
				DXAlert.ShowOkCancel( "teambar.sub.title", "teambar.sub.none1", () => { OnProcessConfirmed( entry ); } );
				warned = true;
			}
			else
			{
				OnProcessConfirmed( entry );
			}
		}
		// NA for swap
		else
		{
			OnProcessConfirmed( entry );
		}
	}

	/* Utilities */

	// Returns list of auto subs/swaps for specified rotation
	private List<LineupEntryReplace> GetEntries( string type, int rotation, bool start )
	{
		IList<LineupEntryReplace> entries = (type == LineupEntryReplace.SubKey) ? lineupSubs : lineupSwaps;

		// Build sub/swap list
		return entries.Where( entry => (entry.Rotation == rotation) && (entry.Start == start) ).ToList();
	}

	// Returns list of confirmations for specified sub/swap type
	private List<LineupEntryReplace> GetConfirms( string type )
	{
		return (type == AutoSub) ? confirmSubs : confirmSwaps;
	}

	/* Event Callbacks */

	// Processes sub/swap for specified entry (either immediate or post-confirmation)
	private void OnProcessConfirmed( LineupEntryReplace entry )
	{
		string type = entry.Type;
		bool sub = (type == AutoSub);

		// MUST deep copy
		LineupEntry outEntry = new( entry.OutEntry );
		LineupEntry inEntry = new( entry.InEntry );

		RecordLineup lineup = sm.Lineup1;

		// Find current player(s) on court
		LineupEntry outPlayer = lineup.GetEntry( outEntry.Player );
		LineupEntry inPlayer = lineup.GetEntry( inEntry.Player );

		if ( outPlayer != null )
		{
			// Get lineup before replace (MUST deep-copy)
			RecordLineup pre = new( lineup );

			// Replace player on court
			lineup.SetEntry( inEntry, outPlayer.Zone );

			// Swap puts OUT player in libero zone
			if ( !sub )
			{
				outEntry.Position = outPlayer.Position;

				lineup.SetEntry( outEntry, inPlayer.Zone );
			}

			// Get lineup after replace (MUST deep-copy)
			RecordLineup post = new( lineup );

			// Persist event
			sm.SubSwap( type, sm.Team1OnSideA, outEntry, inEntry, pre.Entries, post.Entries, true );

			// Low sub warning
			if ( sub )
			{
				int remaining = sm.Subs1;

				// 'You only have N subs remaining.'
				if ( remaining == Shell.Settings.SubsWarn )
				{
					string msg = (remaining == 1) ? "teambar.sub.warn1" : DXString.Get( "teambar.sub.warn", remaining );

					DXAlert.ShowOkRaw( DXString.Get( "teambar.sub.title" ), msg );
				}

				// Update sub display
				sm.UpdateSubs( sm.Team1Side );
			}

			// Update UI
			sm.UpdatePlayers();
		}
	}

	// User confirmed sub/swap
	private void OnConfirmed( string type )
	{
		// Process 1-2 entries
		foreach ( LineupEntryReplace entry in GetConfirms( type ) )
		{
			ProcessEntry( entry );
		}

		// Update UI
		sm.UpdatePlayers();

		// Handle swaps after subs
		if ( type == AutoSub )
		{
			Confirm( AutoSwap );
		}
	}

	// User cancelled confirmation
	private void OnCancelled( string type )
	{
		// Handle swaps after subs, otherwise done
		if ( type == AutoSub )
		{
			Confirm( AutoSwap );
		}
	}
}

//
