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

using DXLib.UI;
using DXLib.UI.Alert;
using DXLib.UI.Gestures;
using DXLib.UI.Container;

using DXLib.UI.Control;
using DXLib.UI.Control.Image;
using DXLib.UI.Control.Button;

using DXLib.Data;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Draws an individual cell in the 3x2 court layout associated with one team bar. The cell can be used for lineup preview,
 * editing, substitutions, or libero swaps.
 */
public class TeamCell : DXGridGestures
{
	/* Constants */
	private const double MenuAdjust = 40;

	// Controls visual appearance
	public enum State
	{
		Unknown,
		Normal,
		Preview,
		Empty,
		Libero,
		Selected,
		EditDisabled,
		Disabled
	};

	/* Properties */
	public Player Player { get; private set; }
	public string Position { get; private set; }
	public bool IsAltNumber { get; private set; }

	// No player defined?
 	public bool IsEmpty => (Player == null);

    // Special libero cell?
	public bool IsLibero => (Position is Lineup.LiberoKey);
	public bool IsLiberoZone => (zone > Lineup.BaseEntries);

	// State control shorthand
	public bool IsPreview { get => (state == State.Preview); set => SetState( value ? State.Preview : State.Normal ); }
	public bool IsSelected { set { if ( value ) SetState( State.Selected ); } }
	public bool IsDisabled { set { if ( value ) SetState( State.Disabled ); } }
	public bool IsEditDisabled { get => (state == State.EditDisabled); set { if ( value ) SetState( State.EditDisabled ); } }

	/* Fields */
	private readonly DXContent zoneBgnd;
	private readonly DXLabel zoneLbl;

	private readonly DXImageArea imageArea;

	private readonly DXLabel name1Lbl;
	private readonly DXLabel name2Lbl;

	private readonly DXNumberButton numberBtn;
	private readonly DXButton positionBtn;
	private readonly DXLabel positionLbl;

	// External references
	private readonly TeamOverlay parent;

	// Constant position on court
	private readonly int zone;

	// State control
	private State state;
	private State prevState;

	/* Methods */
	public TeamCell( TeamOverlay parent, int zone )
	{
		this.parent = parent;
		this.zone = zone;

		bool tablet = DXDevice.IsTablet;

		// Zone
		zoneBgnd = new DXContent();

		string zoneTxt = IsLiberoZone ? $"{DXString.GetUpper( "lineup.lib" )} {zone - Lineup.BaseEntries}" : zone.ToString();

		zoneLbl = new DXLabel
		{
			Text = zoneTxt,
			Font = DXFonts.Oswald,

			Horizontal = LayoutOptions.Center,
			Vertical = LayoutOptions.Center
		};

		// Icon/Image
		imageArea = new DXImageArea
		{
			IsVisible = false,
			DefaultIcon = "player"
		};

		imageArea.SetImage( (ImageSource)null );

		// Full/First Name
		name1Lbl = new DXLabel
		{
			Font = DXFonts.RobotoBold,
			LineBreakMode = LineBreakMode.TailTruncation
		};

		// Last Name
		name2Lbl = new DXLabel
		{
			Font = DXFonts.RobotoBold,
			LineBreakMode = LineBreakMode.TailTruncation
		};

		// Jersey Number 
		numberBtn = new DXNumberButton
		{
			IsVisible = false,
			IsCircle = true,
			DisabledOpacity = 0.3,

			Horizontal = LayoutOptions.Center,
			Vertical = LayoutOptions.Center,

			IsSticky = true,
			HasShadow = true,

			ButtonTapped = OnNumberTapped
		};

		numberBtn.Init();
		
		// Position (non-libero)
		positionBtn = new DXButton
		{
			IsVisible = false,

			Type = DXButton.ButtonType.Positive,
			Font = DXFonts.Roboto,

			Horizontal = LayoutOptions.Start,
			Vertical = LayoutOptions.Center,

			IsSticky = true,
			HasShadow = true,

			ButtonTapped = OnPositionTapped
		};

		// Position (libero)
		positionLbl = new DXLabel
		{
			IsVisible = false,

			TextColor = DXColors.Dark4,
			Font = DXFonts.RobotoBold,

			LineBreakMode = LineBreakMode.WordWrap,
			Vertical = LayoutOptions.Center
		};

		// Use down for better performance
		if ( tablet )
		{
			Down += OnDown;
		}
		// Mobile scrolls so requires tap
		else
		{
			Tapped += OnTapped;
		}

		// Initial state
		SetState( IsLiberoZone ? State.Libero : State.Normal );
	}

	/* Get/Set */

	// Returns lineup entry populated with info from this cell
	public LineupEntry GetEntry()
	{
		return IsEmpty ? null : new LineupEntry
		{
			Zone = zone,

			Position = Position,
			IsAltNumber = IsAltNumber,

			PlayerId = Player.UniqueId,
			Player = Player
		};
	}

	// Populates cell from lineup entry
	public void SetEntry( LineupEntry entry )
	{
		UpdateCell( entry?.Player, entry?.Position, entry is { IsAltNumber: true } );

		UpdateLayout();
	}

	// Populates cell from menu selected player
	public void SetPlayer( Player player )
	{
		string position;

		// Libero MUST have static label
		if ( IsLiberoZone )
		{
			position = Lineup.LiberoKey;
		}
		// Use roster position from incoming player (if only 1), otherwise inherit position from existing player
		else
		{
			position = (player == null) ? null : Lineup.GetNewPosition( player, Position );
		}

		UpdateCell( player, position, false );
		UpdateLayout();

		// Persist substitution immediately
		if ( parent.IsSub )
		{
			Opacity = 0.60;

			parent.Substituted?.Invoke( zone );
		}
	}

	// Updates cell UI with specified values
	private void UpdateCell( Player player, string position, bool altNumber )
	{
		Player = player;

		bool empty = (player == null);
		bool libero = (position == Lineup.LiberoKey);

		// Do NOT update other states here
		if ( empty )
		{
			SetState( State.Empty );
		}

		if ( libero )
		{
			SetState( State.Libero );
		}

		// Image
		imageArea.IsVisible = !empty;
		imageArea.SetImage( player?.ImageUrl );

		// Name set in layout

		// Number
		IsAltNumber = altNumber;

		numberBtn.IsVisible = !empty;
		numberBtn.Text = player?.GetNumber( altNumber );

		// Position
		Position = position;

		// Libero has static text
		if ( libero || IsLiberoZone )
		{
			positionBtn.IsVisible = false;

			positionLbl.IsVisible = !empty;
			positionLbl.Text = (string.IsNullOrEmpty( position ) ? null : DXString.GetLookupValue( "lineup.position", position ).ToUpper())!;
		}
		// Normal has position button
		else
		{
			positionBtn.IsVisible = !empty;
			positionBtn.Text = string.IsNullOrEmpty( position ) ? Lineup.EmptyPosition : position.ToUpper();
		}

		// Must update here
		SetColors();
	}

	/* State */

	// Resets selection state
	private void Deselect()
	{
		// Unknown state reverts to normal
		if ( prevState == State.Unknown )
		{
			SetState( IsLiberoZone ? State.Libero : State.Normal );
		}
		// Revert to previous state after selection
		else
		{
			SetState( prevState );
		}
	}

	// Updates visual state of cell
	private void SetState( State newState )
	{
		// Remember state before selection
		prevState = (newState == State.Selected) ? state : State.Unknown;

		state = newState;

		switch ( state )
		{
			// Non-interactive preview mode
			case State.Preview:
			{
				IsEnabled = false;

				// Flat, disabled buttons
				numberBtn.HasShadow = false;
				positionBtn.HasShadow = false;

				numberBtn.IsDisabled = false;
				positionBtn.IsDisabled = true;

				Opacity = 1.0;
				break;
			}
			// Dimmed opacity
			case State.Selected:
			{
				IsEnabled = false;
				Opacity = 0.6;

				numberBtn.IsDisabled = true;
				positionBtn.IsDisabled = true;
				break;
			}
			// Normal/Libero/Empty
			case State.Normal:
			case State.Libero:
			case State.Empty:
			{
				IsEnabled = true;
				Opacity = 1.0;

				numberBtn.HasShadow = true;
				positionBtn.HasShadow = true;

				numberBtn.IsDisabled = !IsEmpty && !Player.HasAltNumber;
				positionBtn.IsDisabled = false;
				break;
			}
			// Number/Position input disabled
			case State.EditDisabled:
			{
				IsEnabled = true;
				Opacity = 1.0;

				numberBtn.IsDisabled = true;
				positionBtn.IsDisabled = true;
				break;
			}
			// Fully dimmed opacity
			case State.Disabled:
			{
				IsEnabled = false;
				Opacity = 0.6;

				numberBtn.IsDisabled = true;
				positionBtn.IsDisabled = true;
				break;
			}
			default: break;
		}
	}

	// Used internally to set colors based on player type
	private void SetColors()
	{
		Color light = DXColors.Light4;
		Color dark = DXColors.Dark1;
		Color darkAlt = DXColors.Dark3;

		Color custom = parent.Color;

		// Libero is darker
		if ( IsLibero )
		{
			BackgroundColor = DXColors.Light1;

			// Zone
			zoneBgnd.Color = darkAlt;
			zoneLbl.TextColor = light;

			// Image
			imageArea.Color = custom;
			imageArea.IconColor = light;

			// Name
			name1Lbl.TextColor = dark;
			name2Lbl.TextColor = dark;

			// Number
			numberBtn.ButtonColor = custom;
			numberBtn.NumberColor = light;
		}
		// All other states same
		else
		{
			BackgroundColor = IsEmpty ? DXColors.Light1 : DXColors.Light2;

			// Zone
			zoneBgnd.Color = light;
			zoneLbl.TextColor =  dark;

			// Image
			imageArea.Color = custom;
			imageArea.IconColor = light;

			// Name
			name1Lbl.TextColor = dark;
			name2Lbl.TextColor = dark;

			// Number
			numberBtn.ButtonColor = custom;
			numberBtn.NumberColor = light;
		}
	}

	/* Menus */

	// Displays menu for selecting replacement player
	private void ShowLineupMenu()
	{
		LineupMenu menu = parent.GetLineupMenu();

		string prefix = DXString.GetUpper( IsLiberoZone ? "lineup.libero" : "lineup.rot" );
		int zoneAdj = IsLiberoZone ? (zone - Lineup.BaseEntries) : zone;

		// Update popup menu
		menu.Header = $"{prefix} {zoneAdj}";
		menu.HasClear = !IsEmpty;
		menu.MenuColor = parent.Color;

		menu.SetReduced( parent.GetPlayers() );
		menu.SetDisabled( [Player] );

		menu.PlayerSelected = OnPlayerSelected;
		menu.Cancelled = OnCancelled;

		menu.ShowFromView( this, MenuAdjust, MenuAdjust );
	}

	// Displays menu for selecting substitution player
	private void ShowSubMenu()
	{
		string prefix = DXString.GetUpper( "lineup.rot" );

		LineupMenu menu = parent.GetSubMenu( zone );

		// Config popup
		menu.Header = $"{prefix} {zone}";
		menu.PlayerSelected = OnPlayerSelected;
		menu.Cancelled = OnCancelled;

		menu.ShowFromView( this, MenuAdjust, MenuAdjust );
	}

	// Handle substitution request, validates subs, shows sub menu
	private void HandleSub()
	{
		LineupMenu menu = parent.GetSubMenu( zone );

		// Must have valid players to sub
		if ( menu == null )
		{
			Deselect();

			DXAlert.ShowError( "teambar.sub.title", "teambar.sub.roster" );
		}
		// Should have subs remaining (user can override)
		else if ( (parent.Subs < 1) && !Shell.Settings.IsUnlimitedSubs )
		{
			Deselect();

			DXAlert.ShowOkCancel( "teambar.sub.title", "teambar.sub.none", OnSubConfirmed );
		}
		// Normal sub
		else
		{
			ShowSubMenu();
		}
	}

	/* Event Callbacks */

	// Determines if child button bounds contains specified touch point
	private bool ChildContains( Point touch )
	{
		return positionBtn.Contains( touch ) || numberBtn.Contains( touch );
	}

	// Used to handle either down or tap
	private void HandleTouch( Point touch )
	{
		// NA during preview
		if ( IsPreview )
		{
			return;
		}

		// Do not handle number/position buttons
		if ( IsEmpty || !ChildContains( touch ) )
		{
			SetState( State.Selected );

			switch ( parent.Mode )
			{
				// Initial blank lineup
				case TeamOverlay.LineupMode.Blank:
				{
					ShowLineupMenu();
					break;
				}
				// Manual replace
				case TeamOverlay.LineupMode.Replace:
				{
					ShowLineupMenu();
					break;
				}
				// Sub
				case TeamOverlay.LineupMode.Sub:
				{
					// Cannot sub libero
					if ( !IsLibero )
					{
						HandleSub();
					}

					break;
				}
				// Swap
				case TeamOverlay.LineupMode.Swap:
				{
					parent.Swapped?.Invoke( zone );
					break;
				}
			}
		}
	}

	// User overrode no subs remaining
	private void OnSubConfirmed()
	{
		ShowSubMenu();
	}

	// User touched down anywhere within cell
	private void OnDown( object sender, MR.Gestures.DownUpEventArgs args )
	{
		HandleTouch( args.Center );
	}

	// User tapped anywhere within cell
	private void OnTapped( object sender, MR.Gestures.TapEventArgs args )
	{
		HandleTouch( args.Center );
	}

	// User selected player from popup menu
	private void OnPlayerSelected( Player player, bool cancel )
	{
		if ( player != null )
		{
			Deselect();
		}

		if ( !cancel )
		{
			SetPlayer( player );
		}
	}

	// User dismissed menu without selection
	private void OnCancelled()
	{
		Deselect();
	}

	// User tapped jersey number
	private void OnNumberTapped( object sender )
	{
		// NA during preview
		if ( IsPreview )
		{
			return;
		}

		// Only valid if multiple numbers
		if ( Player.HasAltNumber )
		{
			string num = Player.Number;
			string alt = Player.AltNumber;

			// Build number list
			List<DXItem> list =
			[
				new() { Key = LineupEntry.NumberKey, Value = num },
				new() { Key = LineupEntry.AltNumberKey, Value = alt }
			];

			// Show menu (highlight current value)
			DXMenu numMenu = new()
			{
				Title = "lineup.number",
				MenuWd = 100,

				ItemSelected = OnNumberSelected,
				Cancelled = OnNumberCancelled
			};

			string selectedKey = IsAltNumber ? LineupEntry.AltNumberKey : LineupEntry.NumberKey;

			numMenu.SetItems( list, selectedKey );
			numMenu.ShowFromView( numberBtn );
		}
	}

	// User selected number from popup menu
	private void OnNumberSelected( DXItem item )
	{
		// User may have cancelled
		if ( item != null )
		{
			IsAltNumber = (item.Key == LineupEntry.AltNumberKey);

			numberBtn.Text = item.Value;
		}

		numberBtn.Reset();
	}

	// User cancelled number selection
	private void OnNumberCancelled()
	{
		numberBtn.Reset();
	}

	// User tapped position
	private void OnPositionTapped( object sender )
	{
		// NA during preview
		if ( IsPreview )
		{
			return;
		}

		// Show menu (highlight current value)
		DXMenu posMenu = new()
		{
			Title = "lineup.position",
			MenuWd = 225,

			ItemSelected = OnPositionSelected,
			Cancelled = OnPositionCancelled
		};

		posMenu.SetItems( Lineup.GetPositions(), Position );
		posMenu.ShowFromView( positionBtn );
	}

	// User selected position from popup menu
	private void OnPositionSelected( DXItem item )
	{
		// User may have cancelled
		if ( item != null )
		{
			Position = item.Key;

			positionBtn.Text = item.Key.ToUpper();
		}

		positionBtn.Reset();
	}

	// User cancelled position selection
	private void OnPositionCancelled()
	{
		positionBtn.Reset();
	}

	/* Layout */

	// Redraws for RallyFlow specific orientation
	public override void UpdateLayout( bool wide = false )
	{
		UpdateLayout( parent.GetLayoutType() );
	}

	// Redraws cell contents
	public override void UpdateLayout( LayoutType type )
	{
		ClearAll();

		// Do NOT call base here
		switch ( type )
		{
			case LayoutType.Landscape:
			case LayoutType.WideLandscape:
			case LayoutType.MobileLandscape:
			{
				LayoutWide();
				break;
			}
			case LayoutType.Portrait:
			case LayoutType.WidePortrait:
			case LayoutType.MobilePortrait:
			{
				LayoutTall();
				break;
			}
			default: break;
		}
	}

	// Used for 2x3 (and portrait libero) short/wide layout
	private void LayoutWide()
	{
		bool sync = parent.IsSyncMode;

		bool tablet = DXDevice.IsTablet;
		bool wide = DXDevice.IsTabletWide;
		bool ios = DXDevice.IsIOS;

		bool shift = !DXDevice.IsPlusSize() && parent.IsSideA && LineupEntry.ZoneIsBackRow( zone );

		double wd = DXDevice.GetScreenWd();

		double margin = sync ? 7 : (tablet ? 8 : 4);
		double border = (margin - 3);

		double leftMargin = shift ? (margin + 15) : margin;
		bool libero2 = (zone == Lineup.LiberoZone2);

		// Spacing
		Margin = IsLiberoZone ? (tablet ? new Thickness( border, border, border, (libero2 ? border : 0) ) : 0) : new Thickness( border, border, 0, 0 );
		Padding = 0;

		RowSpacing = sync ? 1 : (tablet ? 2 : 1);
		ColumnSpacing = 0;

		double imgSize = sync ? 45 : (tablet ? (wd * (wide ? 0.041 : 0.055)) : 35);
		double numSize = tablet ? (wd * 0.039) : 31;

		double nameRowHt = sync ? 48 : (wd * 0.036);
		double numRowHt = sync ? 52 : (wd * (wide ? 0.051 : 0.050));

		// 3 rows
		AddFixedRow( imgSize );						// Zone, Image
		AddStarRow( nameRowHt );					// Name
		AddStarRow( numRowHt );						// Number, Position

		// 3 columns
		AddFixedColumn( numSize + leftMargin );		// Zone, Number
		AddStarColumn();							// Zone, Position
		AddFixedColumn( imgSize );					// Image

		// Zone
		Add( zoneBgnd, 0, 0 );
		SpanColumns( zoneBgnd, 2 );

		zoneLbl.FontSize = sync ? (IsLiberoZone ? 26 : 32) : (tablet ? (wd * (IsLiberoZone ? 0.027 : 0.035)) : (IsLiberoZone ? 20 : 24));
		zoneLbl.Margin = DXUtils.Top( sync ? -2 : (wide ? -16 : -3) );

		Add( zoneLbl, 0, 0 );
		SpanColumns( zoneLbl, 2 );

		// Icon/Image
		imageArea.SetSize( imgSize, imgSize, 0.80 );

		Add( imageArea, 2, 0 );

		// Full name
		name1Lbl.Text = Player?.FullName!;
		name1Lbl.FontSize = tablet ? (wd * 0.017) : 12;

		name1Lbl.Margin = new Thickness( leftMargin, (ios ? (margin - RowSpacing - 2) : 0), margin, 0 );

		Add( name1Lbl, 0, 1 );
		SpanColumns( name1Lbl, 3 );

		double btnSize = sync ? 26 : (tablet ? (ios ? (wd * 0.029) : (wide ? 50 : 55)) : 25);
		
		// Jersey Number 
		numberBtn.NumberSize = sync ? 15 : (tablet ? (wd * 0.017) : 12);
		numberBtn.Margin = new Thickness( leftMargin, 0, 0, (tablet ? 0 : -2) );
		numberBtn.TextAdjustY = -1;
		numberBtn.Size = btnSize;

		Add( numberBtn, 0, 2 );

		Thickness pad = DXUtils.Left( tablet ? (wd * 0.003) : 0 );

		// Position (libero)
		if ( IsLibero || IsLiberoZone )
		{
			positionLbl.Margin = pad;
			positionLbl.FontSize = sync ? 12 : (tablet ? (wd * 0.014) : 10);

			Add( positionLbl, 1, 2 );
			SpanColumns( positionLbl, 2 );
		}
		// Position (non-libero)
		else
		{
			positionBtn.Margin = pad;
			positionBtn.FontSize = sync ? 13 : (tablet ? (wd * 0.014) : 11);

			positionBtn.ButtonWd = sync ? 40 : (tablet ? (wd * 0.044) : 35);
			positionBtn.ButtonHt = sync ? 22 : (tablet ? (wd * 0.023) : 20);

			Add( positionBtn, 1, 2 );
			SpanColumns( positionBtn, 2 );
		}
	}

	// Used for 3x2 tall cell layout
	private void LayoutTall()
	{
		bool tablet = DXDevice.IsTablet;
		bool wide = DXDevice.IsTabletWide;
		bool ios = DXDevice.IsIOS;

		double wd = DXDevice.GetScreenWd();
		double margin = tablet ? (wd * 0.008) : 4;

		// Spacing
		Margin = new Thickness( (margin - 3), (tablet ? 5 : 1), 0, 0 );
		Padding = 0;

		RowSpacing = tablet ? 0 : 1;
		ColumnSpacing = 0;

		double imgSize = tablet ? (wd * 0.091) : 45;
		double nameSize = tablet ? (wd * 0.025) : (wd * 0.035);
		double numSize = tablet ? (wd * 0.055) : (wd * 0.090);

		double nameHt = (wd * 0.033);
		double numHt = (wd * (tablet ? 0.065 : 0.090));

		// 4 rows
		AddFixedRow( imgSize );					// Zone, Image
		AddStarRow( nameHt );					// First
		AddStarRow( nameHt );					// Last
		AddStarRow( numHt );					// Number, Position

		// 3 columns
		AddFixedColumn( numSize + margin );     // Zone, Number
		AddStarColumn();                        // Zone, Position
		AddFixedColumn( imgSize );				// Image

		// Zone
		Add( zoneBgnd, 0, 0 );
		SpanColumns( zoneBgnd, 2 );

		zoneLbl.FontSize = tablet ? (wd * (IsLiberoZone ? 0.036 : 0.051)) : (IsLiberoZone ? 20 : 28);

		Add( zoneLbl, 0, 0 );
		SpanColumns( zoneLbl, 2 );

		// Icon/Image
		imageArea.SetSize( imgSize, imgSize, 0.80 );

		Add( imageArea, 2, 0 );

		// First Name
		name1Lbl.Text = Player?.FirstName!;
		name1Lbl.FontSize = nameSize;
		name1Lbl.Margin = new Thickness( margin, margin, margin, 0 );

		Add( name1Lbl, 0, 1 );
		SpanColumns( name1Lbl, 3 );

		// Last Name
		name2Lbl.Text = Player?.LastName!;
		name2Lbl.FontSize = nameSize;
		name2Lbl.Margin = new Thickness( margin, 0, margin, 0 );

		Add( name2Lbl, 0, 2 );
		SpanColumns( name2Lbl, 3 );

		// Jersey Number 
		numberBtn.NumberSize = tablet ? (wide ? 20 : (wd * 0.030)) : (ios ? 14 : 12);
		numberBtn.Margin = new Thickness( margin, 0, 0, RowSpacing );

		double buttonSize = (numSize - 5);

		numberBtn.ButtonWd = buttonSize;
		numberBtn.ButtonHt = buttonSize;

		Add( numberBtn, 0, 3 );

		// Position (libero)
		if ( IsLibero || IsLiberoZone )
		{
			positionLbl.Margin = new Thickness( margin, (IsLibero ? 0 : -3), 0, 0 );
			positionLbl.FontSize = tablet ? (wd * 0.018) : 10;

			Add( positionLbl, 1, 3 );
			SpanColumns( positionLbl, 2 );
		}
		// Position (non-libero)
		else
		{
			positionBtn.Margin = DXUtils.Left( margin );
			positionBtn.FontSize = tablet ? (wd * 0.018) : 11;

			positionBtn.ButtonWd = tablet ? (wd * 0.059) : 35;
			positionBtn.ButtonHt = tablet ? (wd * 0.031) : 20;

			Add( positionBtn, 1, 3 );
			SpanColumns( positionBtn, 2 );
		}
	}
}

//
