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

using Plugin.Firebase.Firestore;

using DXLib.UI;

using DXLib.UI.Form;
using DXLib.UI.Form.Message;
using DXLib.UI.Form.Control;

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

namespace iStatVball3;

/*
 * CRUD data entry form for a Set. Can be embedded in parent SetEditForm.
 */
public sealed class SetForm : DXForm
{
	/* Fields */
	private readonly Match match;

	// Unlock control
	private bool unlockPending;

	// Video settings
	private SetVideoForm videoForm;
	private SetVideo video;

	/* Methods */
	public SetForm( Set set, Match match ) : base( set, "set" )
	{
		this.match = match;
		
		// Start with existing or default settings
		video = (HasData && (set.Video != null)) ? set.Video : new SetVideo();
		
		Settings settings = Shell.Settings;

		int setCount = match.Sets.Count;
		bool firstSet = (setCount == 0);

		Set prevSet = firstSet ? null : match.Sets[ setCount - 1 ];

		// 'Set X'
		header.Title = HasData ? set.Name : CreateHeader();
		imagePanel.Color = match.Color;

		PrimaryList.TitleText = match.Team1Name;
		SecondaryList.TitleText = match.Team2Name;

		bool started = HasData && !set.IsNew;

		/* Team 1 */

		// Use existing data, or Team 1 serves first set, else opposite previous set
		bool serve = HasData ? set.Serve1 : (firstSet || prevSet.Serve2);

		// Serving first
		AddControl( new DXCheckboxField
		{
			Key = "serve1",
			Text = "set.serve",
			Checked = serve,
			IsDisabled = started,
			Hint = DXFormControl.HintType.None,
			Help = "set.serve"
		}, true, true );

		bool switchSides = settings.IsSwitchSides;

		// Use existing data, or Team 1 on Side A first set, else opposite previous set (unless not switching sides)
		string side = HasData ? set.Side1 : (firstSet ? "sidea" : (switchSides ? prevSet.Side2 : prevSet.Side1));

		// Court side
		AddControl( new DXSelectorField
		{
			Key = "side1",
			Title = "set.side",
			Items = "set.side",
			SelectedItem = side,
			IsDisabled = started,
			Hint = DXFormControl.HintType.Required,
			Help = "set.side"
		}, true, true );

		Season season = match.Season;

		bool rallyLow = (settings.IsRally && settings.IsLowLevel);
		bool hasLineups = (season.Lineups.Count > 0);

		// Use existing data, or default blank if Rally-Low or no lineups
		string type = HasData ? set.Type1 : ((rallyLow || !hasLineups) ? "blank" : "lineup");

		// Lineup type
		AddControl( new LineupFormControl
		{
			Key = "type1",
			Title = "set.type",
			Items = "set.type",
			SelectedItem = type,
			IsDisabled = (started || rallyLow),
			Hint = DXFormControl.HintType.Required,
			Help = "set.type",
			Color = match.Color,
			PreviewTapped = OnPreviewTapped
		}, true, true );

		bool lineup = (type == "lineup");

		// Use existing lineup, or first set default first lineup, else use lineup from previous set
		Lineup lineup1 = HasData ? set.Lineup1 : (lineup ? (firstSet ? season.Lineups[0] : prevSet.Lineup1) : null);

		// Lineup
		AddControl( new DXSelectorField
		{
			Key = "lineup1",
			Title = "set.lineup",
			Objects = match.Season.LineupList,
			SelectedObject = lineup1,
			IsDisabled = started || !lineup,
			Hint = DXFormControl.HintType.Required,
			Help = "set.lineup"
		}, true, true );

		// Lineup mode starts in error state
		GetControl( "lineup1", true ).SetState( (lineup && (lineup1 == null)) ? DXFormControl.ControlState.Error : DXFormControl.ControlState.Normal );

		// Use existing data, else default based on serve
		int rotation = HasData ? set.Rotation1 : (serve ? 1 : 6);

		// Rotation
		AddControl( new DXStepper
		{
			Key = "rotation1",
			Title = "set.rotation",
			Number = rotation,
			MinValue = 1,
			MaxValue = 6,
			Step = 1,
			IsLooping = true,
			IsDisabled = started,
			Help = "set.rotation"
		}, true, false );

		// Points
		AddControl( new DXKeypadField
		{
			Key = "points1",
			Title = "set.points",
			Number = HasData ? set.Points1 : 0,
			MinValue = 0,
			MaxValue = 99,
			Hint = DXFormControl.HintType.RequiredRange,
			Help = "set.points"
		}, true, false );

		// Notes
		AddControl( new DXEditorField
		{
			Key = "notes1",
			Title = "form.notes",
			Text = set?.Notes1,
			MaxLength = 1024,
			Help = "set.notes"
		}, true, false );

		/* Team 2 */

		// Serving first (opposite Team 1)
		AddControl( new DXCheckboxField
		{
			Key = "serve2",
			Text = "set.serve",
			Checked = !serve,
			IsDisabled = started,
			Hint = DXFormControl.HintType.None,
			Help = "set.serve"
		}, false, true );

		// Court side (opposite Team 1)
		AddControl( new DXSelectorField
		{
			Key = "side2",
			Title = "set.side",
			Items = "set.side",
			SelectedItem = (side == "sidea") ? "sideb" : "sidea",
			IsDisabled = started,
			Hint = DXFormControl.HintType.Required,
			Help = "set.side"
		}, false, true );

		// Lineup type (always 'Blank Lineup' for now)
		AddControl( new DXSelectorField
		{
			Key = "type2",
			Title = "set.type",
			Items = "set.type",
			SelectedItem = "blank",
			IsDisabled = started,
			Hint = DXFormControl.HintType.Required,
			Help = "set.type"
		}, false, true );

		// Lineup (always NA for now)
		AddControl( new DXSelectorField
		{
			Key = "lineup2",
			Title = "set.lineup",
			Objects = null,
			SelectedObject = null,
			IsDisabled = true,
			Help = "set.lineup"
		}, false, false );

		// Rotation (default based on serve)
		AddControl( new DXStepper
		{
			Key = "rotation2",
			Title = "set.rotation",
			Number = HasData ? set.Rotation2 : (serve ? 6 : 1),
			MinValue = 1,
			MaxValue = 6,
			Step = 1,
			IsLooping = true,
			IsDisabled = started,
			Help = "set.rotation"
		}, false, false );

		// Points
		AddControl( new DXKeypadField
		{
			Key = "points2",
			Title = "set.points",
			Number = HasData ? set.Points2 : 0,
			MinValue = 0,
			MaxValue = 99,
			Hint = DXFormControl.HintType.RequiredRange,
			Help = "set.points"
		}, false, false );

		// Notes
		AddControl( new DXEditorField
		{
			Key = "notes2",
			Title = "form.notes",
			Text = set?.Notes2,
			MaxLength = 1024,
			Help = "set.notes"
		}, false, false );

		// Ended set eligible to be re-opened
		buttons.Custom = (!match.IsEnded && (set is { IsEnded: true }) ? "lock" : null);

		// Image
		SetImage( set?.ImageUrl );

		// Control initialization
		Init();

		LineupFormControl type1 = GetControl( "type1", true ) as LineupFormControl;

		// Pre-created only valid if at least 1 exists
		if ( !hasLineups )
		{
			type1.RemoveItem( "lineup" );
		}

		// Blank lineup preview not valid until set ended (MUST be after init)
		type1.IsPreviewDisabled = !lineup && set is not { IsEnded: true };

		// Video
		UpdateHeader();

		// All required fields have defaults except for lineup selection
		buttons.SaveEnabled = !HasData && IsValid();
	}

	// Shows/hides custom header video button
	private void UpdateHeader()
	{
		if ( HasVideo() )
		{
			Set set = data as Set;

			// Must have video already defined, or data form populated
			bool valid = (set is { Video: not null }) || ((videoForm != null) && videoForm.IsValid());
			
			CustomHeader = match.Video.Sync ? "video" : null;
			CustomHeaderColor = valid ? DXColors.Negative : DXColors.Neutral;
		}
		
		CustomHeaderBtn?.Reset();
	}

	// Determines if video sync button should be available
	private bool HasVideo()
	{
		Set set = data as Set;
		
		// NA for sample org
		return match.IsSynced && !match.IsRallySync && (set is not { IsSample: true });
	}

	// Determines if form or extended controls contain modified fields
	protected override bool HasChanges()
	{
		return base.HasChanges() || ((videoForm != null) && videoForm.HasChanges());
	}

	// Determines if form or extended controls have all valid values
	public override bool IsValid()
	{
		return base.IsValid() && (!match.IsSynced || (videoForm == null) || videoForm.IsValid());
	}

	/* Event Callbacks */

	// User confirmed delete
	protected override async void OnDeleteConfirmed()
	{
		base.OnDeleteConfirmed();

		if ( data is Set set )
		{
			Match parent = set.Match;

			int number = set.Number;
			int setCount = parent.SetCount;

			bool lastSet = (number == setCount);

			// Cascading delete
			await set.Delete( true );

			IWriteBatch batch = DXData.StartBatch();

			// Batch update parent match
			parent.UpdateScores( batch );
			parent.UpdateResult( batch );
			parent.UpdateRecord( batch );

			// Ensure sets are 1,2...N
			if ( !lastSet )
			{
				parent.UpdateSetNumbers( batch );
			}

			// Re-open if deleting only set of From Paper match
			if ( parent.IsPaper && (setCount == 1) )
			{
				await parent.End( null );
			}

			// Persist
			await DXData.CommitBatch( batch );

			// Refresh UI
			Shell.Instance.HideForm();
		}
	}

	// Used confirmed cancel
	protected override void OnCancelConfirmed()
	{
		base.OnCancelConfirmed();

		Shell.Instance.HideForm();
	}

	// Used tapped save
	protected override async void OnSaveTapped()
	{
		base.OnSaveTapped();

		// Team 1
		bool serve1 = GetBool( "serve1", true );
		string side1 = GetString( "side1", true );
		string type1 = GetString( "type1", true );

		byte rotation1 = (byte) GetInt( "rotation1", true )!;
		byte points1 = (byte) GetInt( "points1", true )!;
		string notes1 = GetString( "notes1", true );

		// Team 2
		bool serve2 = GetBool( "serve2", false );
		string side2 = GetString( "side2", false );
		string type2 = GetString( "type2", false );

		byte rotation2 = (byte) GetInt( "rotation2", false )!;
		byte points2 = (byte) GetInt( "points2", false )!;
		string notes2 = GetString( "notes2", false );

		// References
		Lineup lineup1 = GetObject( "lineup1", true ) as Lineup;

		Set set;
		
		// Update existing object
		if ( HasData )
		{
			set = data as Set;
			
			if ( set != null )
			{
				set.Serve1 = serve1;
				set.Serve2 = serve2;

				set.Side1 = side1;
				set.Side2 = side2;

				set.Type1 = type1;
				set.Type2 = type2;

				set.Rotation1 = rotation1;
				set.Rotation2 = rotation2;

				set.Points1 = points1;
				set.Points2 = points2;

				set.Notes1 = notes1;
				set.Notes2 = notes2;

				// Video
				set.Video = video;

				// References
				set.Lineup1Id = lineup1?.UniqueId;
				set.Lineup2Id = null;

				set.Lineup1 = lineup1;
				set.Lineup2 = null;

				// Image
				await SaveImage( set );

				// Reopen ended set
				if ( unlockPending )
				{
					set.EndTime = null;
				}

				// Persist
				await set.Update();

				// User reopened or edited score, must update results
				if ( unlockPending || GetControl( "points1", true ).HasChanges || GetControl( "points2", false ).HasChanges )
				{
					await set.UpdateResult();
				}
			}
		}
		// Create new object
		else
		{
			bool legacy = Shell.IsLegacy;

			set = new Set
			{
				Imported = false,
				Legacy = legacy,
				Synced = match.IsSynced,

				Version = App.AppVersion,
				RallyLevel = legacy ? null : Shell.Settings.RallyLevel,

				Number = (byte)(match.Sets.Count + 1),

				Serve1 = serve1,
				Serve2 = serve2,

				Side1 = side1,
				Side2 = side2,

				Type1 = type1,
				Type2 = type2,

				Rotation1 = rotation1,
				Rotation2 = rotation2,

				Points1 = points1,
				Points2 = points2,

				Notes1 = notes1,
				Notes2 = notes2,

				// Video
				Video = video,

				// References
				Lineup1Id = lineup1?.UniqueId,
				Lineup2Id = null,

				Lineup1 = lineup1,
				Lineup2 = null,

				// Set parent
				MatchId = match.UniqueId,
				Match = match,

				SeasonId = match.Season.UniqueId
			};

			// Image
			await SaveImage( set );

			// Add to parent
			match.Sets.Add( set );
			
            match.Season.AddToSet(set);

			// Persist
			await set.Create();
		}

		// Paper stats must manually set start/end
		if ( match.IsPaper )
		{
			if ( set != null )
			{
				// Start based off match start
				if ( set.IsNew )
				{
					await set.Start( set.CalcStartTime() );
				}

				// End based off set start
				await set.End( set.CalcEndTime(), points1, points2 );
			}
		}

		// Refresh UI
		Shell.Instance.HideForm();
	}

	// Shows video settings form in modal popup
	protected override void OnCustomHeaderTapped()
	{
		// Create form
		videoForm = new SetVideoForm( match, video )
		{
			Completed = OnVideoCompleted,
			Cancelled = OnVideoCancelled
		};

		// Show
		videoForm.Show( CustomHeaderBtn );
	}

	// User finished editing video settings
	private void OnVideoCompleted( SetVideo setVideo )
	{
		video = setVideo;
	
		bool changes = HasChanges();
		bool valid = IsValid();
	
		// Save only enabled if valid changes
		buttons.SaveEnabled = changes && valid;
	
		UpdateHeader();
	}
	
	// User cancelled editing video settings
	private void OnVideoCancelled()
	{
		UpdateHeader();
	}

	// User tapped lineup preview button
	private async void OnPreviewTapped()
	{
		LineupFormControl type1 = GetControl( "type1", true ) as LineupFormControl;
		DXSelectorField lineup1 = GetControl( "lineup1", true ) as DXSelectorField;

		Lineup lineup;

		// Completed set uses actual lineup at first point
		if ( (data is Set { IsEnded: true } set) )
		{
			lineup = await set.GetStartingLineup();
		}
		// Otherwise use preconfigured lineup
		else
		{
			lineup = lineup1?.GetObject() as Lineup;
		}

		// Display non-modal popup
		if ( lineup != null )
		{
			type1?.ShowLineup( lineup );
		}
	}

	/* Unlock */

	// User tapped unlock button
	protected override void OnCustomFooterTapped()
	{
		// Reopening ended set
		if ( data is Set { IsEnded: true } )
		{
			DXAlert.ShowOkCancel( "set.unlock", "set.unlock.msg", OnUnlockConfirmed, buttons.Reset );
		}
	}

	// User confirmed reopen set
	private void OnUnlockConfirmed()
	{
		unlockPending = true;

		// Change to unlock icon
		buttons.Custom = null;
		buttons.Custom = "unlock";

		CustomFooterBtn.IsDisabled = true;

		buttons.SaveEnabled = true;
	}

	/* Dynamic Fields */

	// Called back when any underlying control value changes
	protected override void OnControlChanged( object sender, DXFormChangedMessage msg )
	{
		base.OnControlChanged( sender, msg );

		DXFormControl control = msg.Control;
		
		switch ( control.Key )
		{
			case "serve1": UpdateServe( true, control ); break;
			case "serve2": UpdateServe( false, control ); break;

			case "side1": UpdateSide( true, control ); break;
			case "side2": UpdateSide( false, control ); break;

			case "type1": UpdateType( control ); break;
		}
	}

	// Toggles serving side, updates rotation accordingly
	private void UpdateServe( bool team1, DXFormControl control )
	{
		if ( control is DXCheckboxField checkbox )
		{
			bool serve = checkbox.Checked;

			// Team 1 toggled, make Team 2 opposite
			if ( team1 )
			{
				if ( GetControl( "serve2", false ) is DXCheckboxField serve2 )
				{
					serve2.Checked = !serve;
				}

				// Serving team starts Ro1, receiving team Ro6
				if ( GetControl( "rotation1", true ) is DXStepper rotation1 )
				{
					rotation1.Number = serve ? 1 : 6;
				}

				if ( GetControl( "rotation2", false ) is DXStepper rotation2 )
				{
					rotation2.Number = serve ? 6 : 1;
				}
			}
			// Team 2 toggled, make Team 1 opposite
			else
			{
				if ( GetControl( "serve1", true ) is DXCheckboxField serve1 )
				{
					serve1.Checked = !serve;
				}

				// Serving team starts Ro1, receiving team Ro6
				if ( GetControl( "rotation1", true ) is DXStepper rotation1 )
				{
					rotation1.Number = serve ? 6 : 1;
				}

				if ( GetControl( "rotation2", false ) is DXStepper rotation2 )
				{
					rotation2.Number = serve ? 1 : 6;
				}
			}
		}
	}

	// Switches starting sides for both teams
	private void UpdateSide( bool team1, DXFormControl control )
	{
		string side = control.GetObject() as string;

		// Team 1 side changed, update Team 2 to opposite side
		if ( team1 )
		{
			if ( GetControl( "side2", false ) is DXSelectorField side2 )
			{
				side2.SelectedItem = (side == "sidea") ? "sideb" : "sidea";
			}
		}
		// Team 2 side changed, update Team 1 to opposite side
		else
		{
			if ( GetControl( "side1", true ) is DXSelectorField side1 )
			{
				side1.SelectedItem = (side == "sidea") ? "sideb" : "sidea";
			}
		}
	}

	// Lineup field only enabled for pre-created lineup type
	private void UpdateType( DXFormControl control )
	{
		if ( control is LineupFormControl type1 )
		{
			if ( GetControl( "lineup1", true ) is DXSelectorField lineup1 )
			{
				List<Lineup> lineups = match.Season.Lineups;

				bool isLineup = (control.GetObject() as string) == "lineup";
				bool disabled = !isLineup || (lineups.Count == 0);

				Lineup lineup = disabled ? null : lineups[0];

				// Lineup disables and clears for other types
				lineup1.SelectedObject = lineup;
				lineup1.IsDisabled = disabled;
				lineup1.SetState( (isLineup && (lineup == null)) ? DXFormControl.ControlState.Error : DXFormControl.ControlState.Normal );

				// Preview invalid for blank
				type1.IsPreviewDisabled = !isLineup;
			}
		}

		// Update Save button
		IsValid();
	}

	/* Layout */

	// Device/orientation specific layout
	public override void UpdateLayout( LayoutType type )
	{
		base.UpdateLayout( type );

		// Smaller than normal padding on mobile
		if ( DXDevice.IsMobile )
		{
			Padding = DXUtils.AddPadding( 10, DXDevice.SafeAreaLR() );
		}
	}
}

//
