/* 
 * 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.Layout;
using DXLib.UI.Gestures;
using DXLib.UI.Container;

using DXLib.UI.Control.Badge;
using DXLib.UI.Control.Button;

using DXLib.Data;
using DXLib.Utils;

namespace iStatVball3;

/*
 * Represents an individual action menu for one type (serve, team, block, error) for one team. The menu can be anonymous
 * or tied to a lineup. The menu can have separate layouts for horizontal and vertical orientations.
 */
public class ActionMenu : DXGridLayout, ActionMenuHandler
{
	/* Constants */

	// Available action menu types
	public enum MenuType
	{
		Anonymous,
		Serve,
		Team,
		Block,
		Error
	};

	// Sizing based on device/mode
	public enum MenuSize
	{
		Small,
		Medium,
		Large
	};

	// Menu state
	private enum State
	{
		Hidden,
		PreShowing,
		Showing
	};

	// Draw control
	public const double PreOpacity = 0.01;
	private static double BorderSize;

	// Header/footer
	private static double HeaderHt;
	private static double FooterHt;

	private static readonly Color BorderColor = DXColors.Dark1;
	private static readonly Color HeaderColor = DXColors.Dark2;
	private static readonly Color TextColor = DXColors.Light4;

	/* Events */
	private Action<RecordData> Selected { get; set; }

	/* Properties */

	// Determines menu size
	public MenuType Type { get; set; }

	// Menu for primary team or opponent?
	public bool IsTeam1 { get; set; }

	// Team/roster/lineup
	public RecordLineup.LineupType LineupType { get; set; }

	// Anonymous team-only?
	public bool IsAnonymous => LineupType == RecordLineup.LineupType.Anonymous;

	// Stat action key to be returned on selection
	public string Action { get; private set; }

	// Name/number/photo
	public string DisplayMode { get; set; }
	public bool IsPhotoMode => DisplayMode == ActionCell.PhotoMode;

	// Does menu allow multiple cell selections?
	public bool IsMultiSelect { get; set; }

	// Show menu first or record stat immediately?
	public bool IsQuickSelect { get; set; }

	// Show action title?
	public bool HasHeader { set => hasHeader = (value && (Type != MenuType.Error)); }

	// Overall draw size
	public double MenuWd { get; private set; }
	public double MenuHt { get; private set; }

	public double CellWd => cells[0].CellWd;
	public double CellHt => cells[0].CellHt;

	// Force vertical layout in sync mode?
	public bool SyncMode { get; set; }

	// Small/medium/large display
	public MenuSize Size { get; set; }

	public bool IsLarge => Size == MenuSize.Large;
	public bool IsMedium => Size == MenuSize.Medium;

	// Tracks time of initial stat entry tap
	private DateTimeOffset timestamp;

	/* Fields */

	// External refs
	private DXAbsoluteGestures menuLayout;

	// Used for Material elevation
	private DXBorder border;

	// Controls
	private DXTextBadge headerBadge;
	private DXButton confirmBtn;

	// Underlying grid cells
	private List<ActionCell> cells;

	// Ratings, modifiers, selectors
	private FlyoutStack flyouts;

	// Original touch location
	private float touchX;
	private float touchY;

	// Menu state
	private State state;
	private bool hasHeader;

	// Draw control
	private double headerHt;
	private double footerHt;

	private bool vertical;

	// Underlying cell data
	private List<LineupEntry> menuEntries;
	private RecordCourt.Side menuSide;
	private string menuSort;

	// Profiling
	private int whpZone;

	// External ref
	private RallyState rallyState;

	/* Methods */

	// Initializes menu based on property settings
	public void Init( DXAbsoluteGestures layout, RallyState rally )
	{
		menuLayout = layout;
		rallyState = rally;

		Horizontal = LayoutOptions.Fill;
		Vertical = LayoutOptions.Fill;
		
		// Required for full bleed images
		IsClippedToBounds = true;

		// REQUIRED
		IgnoreSafeArea = true;
		
		double dim = DXDevice.GetScreenDim();
		
		// Pseudo-constants
		BorderSize = IsLarge ? (dim * 0.004) : 3;

		HeaderHt = IsLarge ? (dim * 0.029) : IsMedium ? 28 : 25;
		FooterHt = IsLarge ? (dim * 0.044) : IsMedium ? 42 : 38;

		// Layout
		BackgroundColor = BorderColor;

		Padding = BorderSize;
		RowSpacing = BorderSize;
		ColumnSpacing = BorderSize;

		// Underlying grid cells
		cells = [];

		// Ratings, modifiers, selectors
		flyouts = new FlyoutStack( layout, this, Size );

		// Sync mode forces vertical layout
		vertical = SyncMode || DXDevice.IsLandscape();

		// Layout depends on menu type
		switch ( Type )
		{
			// Static single cell
			case MenuType.Anonymous:
			case MenuType.Serve:
			{
				Init1();
				break;
			}
			// Dynamic 6 cells
			case MenuType.Team:
			{
				Init6();
				break;
			}
			// Dynamic 3 cells
			case MenuType.Block:
			{
				Init3();
				break;
			}
			// Static error cell
			case MenuType.Error:
			{
				InitError();
				break;
			}
		}

		// Simulate Material elevation
		border = new DXBorder
		{
			CornerRadius = (float)(IsLarge ? (dim * 0.004) : 4),
			Elevation = 4,
			
			Content = this
		};

		border.Init();
		
		// Initially hidden
		Hide();
	}

	// Adds this menu to parent layout
	private void Add()
	{
		menuLayout.Add( border );
	}

	// Removes this menu from parent layout
	private void Remove()
	{
		menuLayout.Remove( border );
	}

	// Initializes static single cell layout for Anonymous/Serve menus
	private void Init1()
	{
		InitStatic( false );
	}

	// Initializes 6 cell layout for Team menus
	private void Init6()
	{
		// 3x2 or 2x3 depending on orientation
		InitDynamic( (vertical ? 3 : 2), (vertical ? 2 : 3) );
	}

	// Initializes 3 cell layout for Block menus
	private void Init3()
	{
		// 3x1 or 1x3 depending on orientation
		InitDynamic( (vertical ? 3 : 1), (vertical ? 1 : 3) );
	}

	// Initializes static single cell layout for Error menus
	private void InitError()
	{
		InitStatic( true );
	}

	// Initializes dynamic menu layout with specified cell dimensions
	private void InitDynamic( int rowCount, int colCount )
	{
		// Optional header
		InitHeader( colCount );

		// Create grid
		AddStarRows( rowCount );
		AddStarColumns( colCount );

		// Create underlying cells
		for ( int row = 0; row < rowCount; row++ )
		{
			for ( int col = 0; col < colCount; col++ )
			{
				ActionCell cell = new()
				{
					Mode = DisplayMode,
					Size = Size,

					IsMultiSelect = IsMultiSelect,
					HasPlayerError = true,

					Changed = OnChanged,
					Selected = OnSelected,
					ErrorSelected = OnErrorSelected
				};

				cell.Init();

				int rowIndex = hasHeader ? (row + 1) : row;

				cells.Add( cell );
				Add( cell, col, rowIndex );
			}
		}

		// Optional footer
		InitFooter( colCount );

		// Resize menu
		UpdateSize();
	}

	// Initializes static single cell layout
	private void InitStatic( bool error )
	{
		// Optional header
		InitHeader( 1 );

		// 1 row, 1 column
		AddStarRow();
		AddStarColumn();

		// Underlying cell, always highlighted
		ActionCell cell = new()
		{
			Mode = error ? ActionCell.ErrorMode : DisplayMode,
			Size = Size,

			IsMultiSelect = false,
			HasPlayerError = false,

			Selected = OnSelected,
			ErrorSelected = OnErrorSelected
		};

		cell.Init();

		cells.Add( cell );
		Add( cell, 0, (hasHeader ? 1 : 0) );

		// Sized based on display type
		UpdateSize();
	}

	// Initializes optional title header
	private void InitHeader( int colCount )
	{
		if ( hasHeader )
		{
			double dim = DXDevice.GetScreenDim();

			// 1 row
			AddFixedRow( HeaderHt );

			// Badge
			headerBadge = new DXTextBadge
			{
				Color = HeaderColor,
				TextColor = TextColor,

				Font = DXFonts.RobotoCondensedBold,
				FontSize = IsLarge ? (dim * 0.020) : IsMedium ? 19 : 16,

				Margin = DXUtils.Top( IsLarge ? 0 : -1 )
			};

			// Centered across entire menu
			Add( headerBadge, 0, 0, colCount, 1 );
		}
	}

	// Initializes optional multiselect confirm footer
	private void InitFooter( int colCount )
	{
		if ( IsMultiSelect )
		{
			// 1 row
			AddFixedRow( FooterHt );

			// Button
			confirmBtn = new DXButton
			{
				Resource = "alert.ok",
				Type = DXButton.ButtonType.Positive,

				Margin = new Thickness( 5, 5, 0, 0 ),

				ButtonWd = 60,
				ButtonHt = 35,

				ButtonTapped = OnConfirmTapped
			};

			int row = (RowCount - 1);

			// Centered across entire menu
			Fill( HeaderColor, 0, row, colCount, 1 );
			Add( confirmBtn, 0, row, colCount, 1 );
		}
	}

	// Dynamically re-calculates menu size
	void UpdateSize()
	{
		headerHt = hasHeader ? (HeaderHt + BorderSize) : 0;
		footerHt = IsMultiSelect ? (FooterHt + BorderSize) : 0;

		int rowCount = RowCount - (hasHeader ? 1 : 0) - (IsMultiSelect ? 1 : 0);

		// Calculate
		MenuWd = (ColumnCount * CellWd) + (BorderSize * (ColumnCount + 1));
		MenuHt = headerHt + ((rowCount * CellHt) + (BorderSize * (RowCount + 1))) + footerHt;
	}

	/* Setters */

	// Saves original touch location for stat action
	public void SetTouch( double x, double y )
	{
		// Save time of touch
		timestamp = DXUtils.Now();

		touchX = (float)x;
		touchY = (float)y;
	}

	// Sets action to be populated in stat event
	public void SetAction( string action )
	{
		Action = action;
	}

	// Sets optional menu title
	public void SetHeader( string header, string headerAbbrev )
	{
		if ( headerBadge != null )
		{
			string text = header;

			// Some menus require abbreviated title
			if ( DisplayMode is ActionCell.NumberMode or ActionCell.PhotoMode )
			{
				// Block only needs abbreviated in vertical
				if ( Type == MenuType.Anonymous || Type == MenuType.Serve || ((Type == MenuType.Block) && vertical) )
				{
					text = headerAbbrev;
				}
			}

			headerBadge.TextUpper = text;
		}
	}

	// Sets handler to be called back when player selected
	public void SetCallback( Action<RecordData> callback )
	{
		Selected = callback;
	}

	// Marks cell at specified zone as highlighted default, 0 to clear
	public void SetDefault( int zone )
	{
		if ( !IsAnonymous )
		{
			// Highlight matching zone, clear all others
			foreach ( ActionCell cell in cells )
			{
				cell.IsHighlighted = (cell.Zone == zone);
			}
		}
	}

	// Populates button list for ratings flyout
	public void SetRatings( string title, List<DXItem> ratings, string key )
	{
		flyouts.SetRatings( title, ratings, key );
	}

	// Populates button list for first modifier flyout
	public void SetModifiers( List<DXItem> modifiers, string key )
	{
		flyouts.SetModifiers( modifiers, key );
	}

	// Populates button list for second modifier flyout
	public void SetModifiers2( List<DXItem> modifiers, string key )
	{
		flyouts.SetModifiers2( modifiers, key );
	}

	// Populates button list for selector flyout
	public void SetSelectors( List<DXItem> selectors, string key )
	{
		flyouts.SetSelectors( selectors, key );
	}

	// Determines whether menu has multiple selectors (other than error)
	public bool IsMultiSelector()
	{
		return flyouts.IsMultiSelector();
	}

	// Used for error menus only
	public void SetError( string key, string text, bool teamError )
	{
		cells[0].SetError( key, text, teamError );
	}

	// Populates menu with specified entry list
	public void SetEntries( List<LineupEntry> entries, RecordCourt.Side side = RecordCourt.Side.Unknown, string sort = null )
	{
		menuEntries = entries;
		menuSide = side;
		menuSort = sort;

		switch ( Type )
		{
			// Anon/serve always 1 entry
			case MenuType.Anonymous:
			case MenuType.Serve:
			{
				SetEntries1();
				break;
			}
			// Team always 6 entries
			case MenuType.Team:
			{
				SetEntries6();
				break;
			}
			// Team always 3 entries
			case MenuType.Block:
			{
				SetEntries3();
				break;
			}
		}

		if ( !IsAnonymous )
		{
			int index = GetDefaultIndex();

			// Remember for profiling
			whpZone = cells[ index ].Zone;
		}
	}

	// Sets single entry for 1 cell Serve menu
	private void SetEntries1()
	{
		cells[0].SetEntry( menuEntries, 1 );
	}

	// Sets all entries for a 6 cell Team menu
	private void SetEntries6()
	{
		if ( !IsAnonymous && (menuEntries != null) )
		{
			// Rotation order must map based on orientation and side
			if ( RecordLineup.IsRotationOrder( menuSort ) )
			{
				// Vertical 2x3 layout
				if ( vertical )
				{
					// Side A
					if ( menuSide == RecordCourt.Side.SideA )
					{
						cells[0].SetEntry( menuEntries, 5 );
						cells[1].SetEntry( menuEntries, 4 );
						cells[2].SetEntry( menuEntries, 6 );
						cells[3].SetEntry( menuEntries, 3 );
						cells[4].SetEntry( menuEntries, 1 );
						cells[5].SetEntry( menuEntries, 2 );
					}
					// Side B
					else
					{
						cells[0].SetEntry( menuEntries, 2 );
						cells[1].SetEntry( menuEntries, 1 );
						cells[2].SetEntry( menuEntries, 3 );
						cells[3].SetEntry( menuEntries, 6 );
						cells[4].SetEntry( menuEntries, 4 );
						cells[5].SetEntry( menuEntries, 5 );
					}
				}
				// Horizontal 3x2 layout
				else
				{
					// Side A
					if ( menuSide == RecordCourt.Side.SideA )
					{
						cells[0].SetEntry( menuEntries, 1 );
						cells[1].SetEntry( menuEntries, 6 );
						cells[2].SetEntry( menuEntries, 5 );
						cells[3].SetEntry( menuEntries, 2 );
						cells[4].SetEntry( menuEntries, 3 );
						cells[5].SetEntry( menuEntries, 4 );
					}
					// Side B
					else
					{
						cells[0].SetEntry( menuEntries, 4 );
						cells[1].SetEntry( menuEntries, 3 );
						cells[2].SetEntry( menuEntries, 2 );
						cells[3].SetEntry( menuEntries, 5 );
						cells[4].SetEntry( menuEntries, 6 );
						cells[5].SetEntry( menuEntries, 1 );
					}
				}
			}
			// Fixed ordering is always left-to-right, top-to-bottom
			else
			{
				for ( int i = 0; i < menuEntries.Count; i++ )
				{
					cells[i].SetEntry( menuEntries[i] );
				}
			}
		}
	}

	// Sets all entries for a 3 cell Block menu (always rotation based)
	private void SetEntries3()
	{
		if ( !IsAnonymous && (menuEntries != null) )
		{
			// Vertical 1x3 layout
			if ( vertical )
			{
				// Side A
				if ( menuSide == RecordCourt.Side.SideA )
				{
					cells[0].SetEntry( menuEntries, 4 );
					cells[1].SetEntry( menuEntries, 3 );
					cells[2].SetEntry( menuEntries, 2 );
				}
				// Side B
				else
				{
					cells[0].SetEntry( menuEntries, 2 );
					cells[1].SetEntry( menuEntries, 3 );
					cells[2].SetEntry( menuEntries, 4 );
				}
			}
			// Horizontal 3x1 layout
			else
			{
				// Side A
				if ( menuSide == RecordCourt.Side.SideA )
				{
					cells[0].SetEntry( menuEntries, 2 );
					cells[1].SetEntry( menuEntries, 3 );
					cells[2].SetEntry( menuEntries, 4 );
				}
				// Side B
				else
				{
					cells[0].SetEntry( menuEntries, 4 );
					cells[1].SetEntry( menuEntries, 3 );
					cells[2].SetEntry( menuEntries, 2 );
				}
			}
		}
	}

	// Visually disables cell corresponding to specified player
	public void SetDisabled( Player player, bool disabled )
	{
		// Find matching cell, disable
		foreach ( ActionCell cell in cells )
		{
			if ( (cell.Player != null) && cell.Player.Equals( player ) )
			{
				cell.IsDisabled = disabled;
				return;
			}
		}
	}

	/* Selection */

	// Returns list of currently selected cells
	private List<ActionCell> GetSelectedCells( bool autoSelect = false )
	{
		// Always 1 for single select, 0-3 for multi
		List<ActionCell> selected = new( 3 );

		// Can optionally auto-select single cell
		if ( autoSelect )
		{
			selected.Add( cells[0] );
		}
		// Build list
		else
		{
			foreach ( ActionCell cell in cells )
			{
				if ( cell.IsSelected )
				{
					selected.Add( cell );
				}
			}
		}

		return selected;
	}

	// Deselects all menu components
	private void DeselectAll()
	{
		// Deselect all cells
		foreach ( ActionCell cell in cells )
		{
			cell.IsSelected = false;
		}
	}

	// Flyout tap can auto-select if only 1 player and 1 flyout
	public bool CanAutoSelect()
	{
		return (cells.Count == 1) && (flyouts.GetCount() == 1);
	}

	/* Draw */

	// Convenience structure for passing draw location
	private struct MenuLocation
	{
		public double X;
		public double Y;

		public bool IsRightFacing;
	}

	// Draws menu at specified location
	public void Draw()
	{
		// Determine draw location based on default cell, flyouts, etc
		MenuLocation pt = GetDrawPoint( touchX, touchY );

		// Draw
		border.SetLayoutBounds( menuLayout, pt.X, pt.Y, MenuWd, MenuHt );

		// Must add before drawing
		Add();

		// Flyouts to right of menu if fit, otherwise to left
		DrawFlyouts( pt.IsRightFacing );

		// Left-facing requires adding again on top to cover shadow
		if ( !pt.IsRightFacing )
		{
			Remove();
			Add();
		}
	}

	// Determines draw location within screen bounds
	private MenuLocation GetDrawPoint( double x, double y )
	{
		// Place default cell over touch location
		int index = GetDefaultIndex();

		int row = (index / ColumnCount);
		int col = (index % ColumnCount);

		double wd = CellWd;
		double ht = CellHt;

		// Center cell at location
		double drawX = (x - (col * wd) - (wd / 2));
		double drawY = (y - (row * ht) - (ht / 2)) - (hasHeader ? headerHt : 0);

		const double edge = ActionOverlay.Edge;
		Thickness safeArea = DXDevice.ExtraSafeArea();

		double screenWd = DXDevice.GetScreenWd();
		double screenHt = DXDevice.GetScreenHt();
		
		// Account for optional flyout(s)
		double flyoutsWd = flyouts.GetTotalWd();
		bool rightFacing = (drawX + MenuWd + flyoutsWd + ActionOverlay.Edge + safeArea.Right) < screenWd;

		// Bounds check screen left/right
		double minX = (safeArea.Left + edge + (rightFacing ? 0 : flyoutsWd));
		double maxX = (screenWd - safeArea.Right - edge - (rightFacing ? flyoutsWd : 0) - MenuWd);

		drawX = (minX > maxX) ? maxX : Math.Max( Math.Min( drawX, maxX ), minX );

		// Bounds check screen top/bottom
		double minY = (safeArea.Top + edge);
		double maxY = (screenHt - safeArea.Bottom - edge - MenuHt);

		drawY = Math.Max( Math.Min( drawY, maxY ), minY );

		return new MenuLocation
		{
			X = drawX,
			Y = drawY,

			IsRightFacing = rightFacing
		};
	}

	// Draws full flyout stack (to either right or left)
	private void DrawFlyouts( bool rightFacing )
	{
		// X attached to menu left/right
		double x = rightFacing ? border.LayoutBounds.Right : border.LayoutBounds.Left;

		double adjustY = 0;

		// Adjust for header
		if ( hasHeader )
		{
			adjustY = ((HeaderHt + BorderSize) / 2);
		}

		// Y centered vertically on menu
		double y = border.LayoutBounds.Center.Y + adjustY;

		// Draw at calculated location
		flyouts.Draw( rightFacing, x, y );
	}

	// Returns index of default WHP selected cell
	private int GetDefaultIndex()
	{
		// Find current default
		for ( int i = 0; i < cells.Count; i++ )
		{
			if ( cells[i].IsHighlighted )
			{
				return i;
			}
		}

		// Default to first/only cell
		return 0;
	}

	// Returns menu cell (if any) containing specified x,y point
	private ActionCell GetCell( Point pt )
	{
		Point origin = border.Bounds.Location;

		// Find match
		return cells.FirstOrDefault( cell => cell.Contains( origin, pt ) );
	}

	/* Show/Hide */

	// Prepares menu for display (rendered but not yet visible)
	public void PreShow()
	{
		if ( state == State.Hidden )
		{
			state = State.PreShowing;

			// Confirm starts disabled
			if ( confirmBtn != null )
			{
				confirmBtn.IsDisabled = true;
			}

			// Must be visible to start render
			border.Opacity = PreOpacity;
			border.IsVisible = true;

			// Flyouts last
			flyouts.PreShow();
		}
	}

	// Makes menu fully visible
	public void Show()
	{
		if ( state == State.PreShowing )
		{
			state = State.Showing;

			// Now at full opacity
			border.Opacity = 1.0;
			border.IsVisible = true;

			// Flyouts last
			flyouts.Show();
		}
	}

	// Hides entire action menu
	public void Hide()
	{
		state = State.Hidden;

		// Flyouts first
		flyouts.Hide();

		// Hidden and zero opacity
		border.IsVisible = false;
		border.Opacity = 0.0;

		// Reset
		DeselectAll();
		Remove();
	}

	/* Event Handling */

	// Determines if menu (or flyout) contains specified touch point
	public bool Contains( Point pt )
	{
		return (state != State.Hidden) && (border.Bounds.Contains( pt ) || flyouts.Contains( pt ));
	}

	// Forwards down event to child cells and flyouts
	public void OnDown( Point pt )
	{
		ActionCell cell = GetCell( pt );

		// Forward to matching cell
		if ( cell != null )
		{
			cell.OnDown();
			return;
		}

		// Flyout stack
		if ( flyouts.Contains( pt ) )
		{
			flyouts.OnDown( pt );
		}
	}

	// Forwards up event to child cells and flyouts
	public void OnUp( Point pt )
	{
		ActionCell cell = GetCell( pt );

		// Forward to matching cell
		if ( cell != null )
		{
			cell.OnUp();
			return;
		}

		// Flyout stack
		if ( flyouts.Contains( pt ) )
		{
			flyouts.OnUp( pt );
		}
	}

	// Forwards long press to child cell
	public void OnLongPress( Point pt )
	{
		ActionCell cell = GetCell( pt );

		// Forward to matching cell
		cell?.OnLongPress();
	}

	// Called back when cell changes selection state
	private void OnChanged()
	{
		List<ActionCell> selected = GetSelectedCells();

		// Confirm requires at least 1 selected player
		confirmBtn.IsDisabled = (selected.Count == 0);
	}

	// Called back when user taps menu cell
	private void OnSelected()
	{
		HandleSelected();
	}

	// User tapped multiselect confirm button
	private void OnConfirmTapped( object sender )
	{
		HandleSelected();
	}

	// Handles menu cell selection (non-error)
	public void HandleSelected( bool auto = false )
	{
		List<ActionCell> selected = GetSelectedCells( auto );
		int count = selected.Count;

		// Up to 3 players possible
		LineupEntry entry = (count > 0) ? selected[0].Entry : null;
		LineupEntry entry2 = (count > 1) ? selected[1].Entry : null;
		LineupEntry entry3 = (count > 2) ? selected[2].Entry : null;

		// Server and setter can be auto-populated
		if ( IsQuickSelect )
		{
			entry = Action switch
			{
				Stats.ServeKey => rallyState.GetServer( IsTeam1 ),
				Stats.SecondKey => rallyState.GetSetter( IsTeam1 ),
				
				_ => entry
			};
		}

		// Populate data for persistence
		RecordData data = new()
		{
			Timestamp = timestamp,

			X = touchX,
			Y = touchY,

			Player = entry?.Player,
			Player2 = entry2?.Player,
			Player3 = entry3?.Player,

			Number = entry?.Number,
			Number2 = entry2?.Number,
			Number3 = entry3?.Number,

			Position = entry?.Position,
			Position2 = entry2?.Position,
			Position3 = entry3?.Position,

			Action = Action,

			Rating = flyouts.GetRating(),
			Modifier = flyouts.GetModifier(),
			Selector = flyouts.GetSelector(),

			IsError = false,
			IsTeamError = false
		};

		if ( !IsAnonymous && (entry != null) )
		{
			// Some fields only applicable for lineup mode
			if ( LineupType == RecordLineup.LineupType.Lineup )
			{
				int zone = entry.Zone;

				data.CourtPosition = zone;
				data.CourtRow = (int)(LineupEntry.ZoneIsBackRow( zone ) ? RecordCourt.Row.Backrow : RecordCourt.Row.Frontrow);
			}

			// WHP profiling (primary team only)
			#if DEBUG
				if ( count == 1 )
				{
					int zone = selected[0].Zone;
	
					float distance = WHP.GetInstance().UpdateAccuracy( Action, whpZone, zone );
					data.WHP = distance;
				}
			#endif
		}

		// Callback handler
		Selected.Invoke( data );

		Hide();
	}

	// Handles error menu cell selection
	private void OnErrorSelected( string key, bool teamError )
	{
		bool touch = false;

		// Might be player error
		List<ActionCell> selected = GetSelectedCells(); 
		LineupEntry entry = (selected.Count > 0) ? selected[0].Entry : null;

		// Defense out/net must track touch off block
		if ( rallyState.State == "defense" )
		{
			Stat prev = rallyState.PreviousStat( 2 );
			touch = prev is { IsBlock: true };
		}

		// Build result data
		RecordData data = new()
		{
			X = touchX,
			Y = touchY,

			Player = entry?.Player,
			Number = entry?.Number,
			Position = entry?.Position,

			Action = Action,

			Rating = flyouts.GetRating(),
			Modifier = flyouts.GetModifier(),
			Selector = flyouts.GetSelector(),

			Result = Stats.ErrorKey,
			Error = key,
			Touch = touch,

			IsError = true,
			IsTeamError = teamError
		};

		if ( !IsAnonymous && (entry != null) )
		{
			// Some fields only applicable for lineup mode
			if ( LineupType == RecordLineup.LineupType.Lineup )
			{
				int zone = entry.Zone;

				data.CourtPosition = zone;
				data.CourtRow = (int)(LineupEntry.ZoneIsBackRow( zone ) ? RecordCourt.Row.Backrow : RecordCourt.Row.Frontrow);
			}
		}

		// Callback handler
		Selected.Invoke( data );

		Hide();
	}

	/* Layout */

	// Forces a complete menu re-layout on orientation change
	public override void UpdateLayout( LayoutType type )
	{
		ClearAll();

		// Recreate grid
		Init( menuLayout, rallyState );

		// Re-populate data
		SetEntries( menuEntries, menuSide, menuSort );
	}
}

//
