/* 
 * 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.UI.Control;

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

using DXLib.Data;
using DXLib.Data.Model;

using DXLib.UI;
using DXLib.Utils;

namespace iStatVball3;

/*
 * CRUD data entry form for a Match.
 */
public sealed class MatchForm : DXForm
{
	/* Fields */
	private readonly Tournament tournament;
	private readonly Season season;

	// Unlock control
	private bool unlockPending;

	// Video settings
	private MatchVideoForm videoForm;
	private MatchVideo video;

	// Retroactive opponent change
	private bool HasOpponentChange;

	/* Methods */
	public MatchForm( Match match, Tournament tournament, Season season ) : base( match, "match" )
	{
		try
		{
			this.tournament = tournament;
			this.season = season;

			// Start with existing or default settings
			video = (HasData && (match.Video != null)) ? match.Video : new MatchVideo();

			// 'Opponent/Date' or 'New Match'
			header.Title = HasData ? match.Name : CreateHeader();
			imagePanel.Color = season.Color;

			/* Required */

			bool started = HasData && !match.IsNew;

			// Default to live
			string recording = HasData ? match.Recording : Match.LiveKey;

			// Recording
			AddControl( new DXSelectorField
			{
				Key = "record",
				Title = "match.record",
				Items = "match.record",
				SelectedItem = recording,
				IsDisabled = started,
				Hint = DXFormControl.HintType.Required,
				Help = "match.record",

				ControlChanged = OnRecordingChanged
			}, true, true );

			bool disabled = !HasData && (recording == Match.LiveKey);

			// Default to current date/time
			DateTimeOffset date = HasData ? match.MatchTime : DXUtils.Now();
			DateTime local = date.LocalDateTime;

			// Date
			AddControl( new DXPicker
			{
				Key = "date",
				Title = "match.date",
				Date = local,
				Type = DXPicker.PickerType.Date,
				IsDisabled = disabled,
				Hint = DXFormControl.HintType.Required,
				Help = "match.date"
			}, true, true );

			// Time
			AddControl( new DXPicker
			{
				Key = "time",
				Title = "match.time",
				Time = local.TimeOfDay,
				Type = DXPicker.PickerType.Time,
				IsDisabled = disabled,
				Hint = DXFormControl.HintType.Required,
				Help = "match.time"
			}, true, true );

			bool hasOpponents = (season.Opponents.Count > 0);

			// Default to anonymous
			bool anonymous = (HasData && match.IsAnonOpponent) || !hasOpponents;

			// Cannot change opponent while set in-progress
			disabled = HasData && match.SetInProgress();

			// Anonymous opponent
			AddControl( new DXCheckboxField
			{
				Key = "anon",
				Text = "match.anon",
				Checked = anonymous,
				IsDisabled = disabled,
				Hint = DXFormControl.HintType.None,
				Help = "match.anon",

				ControlChanged = OnOpponentChanged
			}, true, true );

			// Opponent
			AddControl( new DXSelectorField( true )
			{
				Key = "opponent",
				Title = "match.opp",
				Objects = season.OpponentList,
				SelectedObject = season.GetOpponent( match?.OpponentId ),
				IsDisabled = (anonymous || disabled),
				Hint = DXFormControl.HintType.Required,
				Help = "match.opp",
				ObjectLabel = "opponent",

				ControlChanged = OnOpponentChanged,
				AddTapped = OnAddOpponent
			}, true, true );

			bool error = !HasData || anonymous;
			
			// Non-anonymous, empty opponent starts in error state
			GetControl( "opponent", true ).SetState( error ? DXFormControl.ControlState.Error : DXFormControl.ControlState.Normal );

			/* Optional */

			// Tournament
			AddControl( new DXSelectorField( true )
			{
				Key = "tournament",
				Title = "match.tourn",
				Objects = season.TournamentList,
				SelectedObject = tournament ?? season.GetTournament( match?.TournamentId ),
				Help = "match.tourn",
				ObjectLabel = "tournament",

				AddTapped = OnAddTournament
			}, false, false );

			// Type
			AddControl( new DXSelectorField
			{
				Key = "type",
				Title = "match.type",
				Items = "match.type",
				SelectedItem = match?.Type,
				Hint = DXFormControl.HintType.Required,
				Help = "match.type",
			}, false, false );

			// Score Format
			AddControl( new DXSelectorField
			{
				Key = "format",
				Title = "match.format",
				Items = "match.format",
				SelectedItem = match?.ScoreFormat,
				Help = "match.format",
			}, false, false );

			// League
			AddControl( new DXSelectorField
			{
				Key = "league",
				Title = "match.league",
				Items = "match.league",
				SelectedItem = match?.League,
				Help = "match.league",
			}, false, false );

			// Home/Away
			AddControl( new DXSelectorField
			{
				Key = "home",
				Title = "match.home",
				Items = "match.home",
				SelectedItem = match?.HomeAway,
				Help = "match.home",
			}, false, false );

			// Statistician
			AddControl( new DXSelectorField( true )
			{
				Key = "statistician",
				Title = "match.stat",
				Objects = season.StatisticianList,
				SelectedObject = season.GetStatistician( match?.StatisticianId ),
				Help = "match.stat",
				ObjectLabel = "statistician",

				AddTapped = OnAddStatistician
			}, false, false );

			// Venue
			AddControl( new DXSelectorField( true )
			{
				Key = "venue",
				Title = "match.venue",
				Objects = season.VenueList,
				SelectedObject = season.GetVenue( match?.VenueId ),
				Help = "match.venue",
				ObjectLabel = "venue",

				AddTapped = OnAddVenue
			}, false, false );

			// Attendance
			AddControl( new DXKeypadField
			{
				Key = "attend",
				Title = "match.attend",
				Number = match?.Attendance,
				MinValue = 0,
				MaxValue = 99999,
				Hint = DXFormControl.HintType.RequiredRange,
				Help = "match.attend"
			}, false, false );

			// Referee 1
			AddControl( new DXTextField
			{
				Key = "ref1",
				Title = "match.ref1",
				Text = match?.Referee1,
				MaxLength = 64,
				Type = DXTextField.TextType.CapitalizeWords,
				Help = "match.ref1"
			}, false, false );

			// Referee 2
			AddControl( new DXTextField
			{
				Key = "ref2",
				Title = "match.ref2",
				Text = match?.Referee2,
				MaxLength = 64,
				Type = DXTextField.TextType.CapitalizeWords,
				Help = "match.ref2"
			}, false, false );

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

			// Ended match eligible to be reopened
			buttons.Custom = match is { IsEnded: true } ? "lock" : null;

			// Image
			SetImage( match?.ImageUrl );

			// Prep for video
			UpdateHeader();

			// Control initialization
			Init();

			// Initialize Save button state
			IsValid();
		}
		catch ( Exception ex )
		{
			Console.WriteLine( ex.Message );
		}
	}

	// Shows/hides custom header video button
	private void UpdateHeader()
	{
		User user = Shell.CurrentUser;
		
		// Requires coach/director
		if ( MatchVideo.CanSync( user ) )
		{
			Match match = data as Match;
			
			CustomHeader = null;

			// Video settings NOT available for paper stats or sample
			if ( !season.IsSample || user.IsAdmin )
			{
				string recording = GetString( "record", true );
				
				// 'From Video' and 'Using RallySync' always available, 'Live/Future Match' ONLY if ended, 'From Paper' never
				if ( (recording == Match.VideoKey) || (recording == Match.RallySyncKey) || 
				     ((recording is Match.LiveKey or Match.FutureKey) && match is { IsEnded: true }) )
				{
					bool synced = (match?.IsSynced == true) || (video?.Sync == true);
					
					CustomHeader = "video";
					CustomHeaderColor =  synced ? DXColors.Negative : DXColors.Dark1;
				}
			}
		}
		
		CustomHeaderBtn?.Reset();
	}

	// 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() && ((videoForm == null) || videoForm.IsValid());
	}

	/* Event Callbacks */

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

		if ( data is Match match )
		{
			// MUST remove first to exclude from record
			season.Matches.Remove( match );

			// Update win/loss record in related objects
			await match.UpdateRecord();

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

			// 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();

		// Required
		string recording = GetString( "record", true );
		DateTimeOffset date = GetDate( "date", true );
		TimeSpan time = GetTime( "time", true );

		DateTimeOffset matchTime = date.Date + time;

		// Optional
		string type = GetString( "type", false );
		string format = GetString( "format", false );
		string league = GetString( "league", false );
		string homeAway = GetString( "home", false );
		string notes = GetString( "notes", false );

		// BoxScore
		int? attend = GetInt( "attend", false );
		string ref1 = GetString( "ref1", false );
		string ref2 = GetString( "ref2", false );

		// References
		Tournament tourn = GetObject( "tournament", false ) as Tournament;
		Statistician stat = GetObject( "statistician", false ) as Statistician;
		Opponent opponent = GetObject( "opponent", true ) as Opponent;
		Venue venue = GetObject( "venue", false ) as Venue;

		// Update existing object
		if ( HasData )
		{
			if ( data is Match match )
			{
				// Possible retroactive opponent change
				if ( HasOpponentChange )
				{
					if ( DXData.HasConnection() )
					{
						await match.UpdateOpponent( opponent );
					}
					else
					{
						DXAlert.ShowNetworkError( "net.err.opponent" );
						return;
					}
				}

				match.Recording = recording;
				match.MatchTime = matchTime;

				match.Type = type;
				match.ScoreFormat = format;
				match.League = league;
				match.HomeAway = homeAway;
				match.Notes = notes;

				// BoxScore
				match.Attendance = attend;
				match.Referee1 = ref1;
				match.Referee2 = ref2;

				// Video (current sub-form config)
				match.Video = video;

				// References
				match.TournamentId = tourn?.UniqueId;
				match.StatisticianId = stat?.UniqueId;
				match.OpponentId = opponent?.UniqueId;
				match.VenueId = venue?.UniqueId;

				Tournament oldTourn = match.Tournament;

				// Possibly remove from tournament
				if ( (oldTourn != null) && !oldTourn.Equals( tourn ) )
				{
					List<Match> matches = oldTourn.Matches;

					matches.Remove( match );
				}

				// Possibly add to/change tournament
				if ( tourn != null )
				{
					Tournament newTourn = season.GetTournament( tourn.UniqueId );
					IList<Match> matches = newTourn.Matches;

					if ( !matches.Contains( match ) )
					{
						newTourn.Matches.Add( match );
					}
				}

				match.Populate( season );

				// Image
				await SaveImage( match );

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

				// Persist
				await match.Update();

				// Refresh scores/result/record
				if ( unlockPending || match.IsEnded || HasOpponentChange )
				{
					await match.UpdateAllResults();
				}
			}
		}
		// Create new object
		else
		{
			// Paper only
			byte? numSets = (recording == Match.PaperKey) ? GetByte( "sets", true ) : null;

			// Match object for this season
			Match match = new()
			{
				Recording = recording,
				MatchTime = matchTime,

				PaperSetCount = numSets,

				Type = type,
				ScoreFormat = format,
				League = league,
				HomeAway = homeAway,
				Notes = notes,

				// BoxScore
				Attendance = attend,
				Referee1 = ref1,
				Referee2 = ref2,

				// Video (current sub-form config)
				Video = video,

				// References
				StatisticianId = stat?.UniqueId,
				OpponentId = opponent?.UniqueId,
				VenueId = venue?.UniqueId,

				// Set parent(s)
				TournamentId = tourn?.UniqueId,
				Tournament = tourn,

				SeasonId = season.UniqueId,
				Season = season
			};

			match.Populate( season );

			// Image
			await SaveImage( match );

			// Add to parent(s)
			tournament?.Matches.Add( match );

			season.Matches.Add( match );

			// Persist
			await match.Create();
		}

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

	/* Unlock */

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

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

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

		CustomFooterBtn.IsDisabled = true;

		buttons.SaveEnabled = true;
	}

	/* Video */

	// Shows video settings form in modal popup
	protected override void OnCustomHeaderTapped()
	{
		string recording = GetString( "record", true );
		
		// Create form
		videoForm = new MatchVideoForm( video, recording )
		{
			Completed = OnVideoCompleted,
			Cancelled = CustomHeaderBtn.Reset
		};
	
		// Show
		videoForm.Show( CustomHeaderBtn );
	}
	
	// User finished editing video settings
	private void OnVideoCompleted( MatchVideo matchVideo )
	{
		video = matchVideo;
	
		bool changes = HasChanges();
		bool valid = IsValid();
	
		// Save only enabled if valid changes
		buttons.SaveEnabled = changes && valid;
	
		UpdateHeader();
	}

	/* Dynamic Fields */

	// Updates date/time fields based on recording type
	private void OnRecordingChanged()
	{
		string recording = GetString( "record", true );

		// Must clear here
		if ( data is Match match )
		{
			match.Video = null;
		}
		
		if ( GetControl( "date", true ) is DXPicker date )
		{
			if ( GetControl( "time", true ) is DXPicker time )
			{
				DXFormControl sets = GetControl( "sets", true );

				// Sets only applicable for paper stats
				if ( sets != null )
				{
					RemoveControl( sets, true );
				}

				// Show/hide video button
				UpdateHeader();

				switch ( recording )
				{
					// Live is always current date/time (disabled)
					case Match.LiveKey:
					{
						date.Date = DateTime.Now;
						time.Time = DateTime.Now.TimeOfDay;

						date.SetState( DXFormControl.ControlState.Normal );
						time.SetState( DXFormControl.ControlState.Normal );

						date.IsDisabled = true;
						time.IsDisabled = true;
						break;
					}
					// Future can only have future dates (enabled)
					case Match.FutureKey:
					{
						date.MinDate = DateTime.Today;
						date.MaxDate = DateTime.MaxValue;

						date.IsDisabled = false;
						time.IsDisabled = false;
						break;
					}
					// Paper/video can only have past dates (enabled)
					case Match.PaperKey:
					case Match.VideoKey:
					{
						date.MinDate = DateTime.MinValue;
						date.MaxDate = DateTime.Now;

						date.IsDisabled = false;
						time.IsDisabled = false;

						if ( !HasData && (recording == Match.PaperKey) )
						{
							HandlePaper();
						}

						break;
					}
				}
			}
		}
	}

	// Adds controls specific to new 'from paper' match
	private void HandlePaper()
	{
		// Number sets
		DXStepper stepper = new()
		{
			Key = "sets",
			Title = "match.sets",
			Number = 3,
			MinValue = 1,
			MaxValue = 5,
			Step = 1,
			IsLooping = true,
			Help = "match.sets"
		};

		stepper.Init();

		InsertControl( stepper, true, 1, true );
	}

	// Opponent field changes based on anonymous state
	private void OnOpponentChanged()
	{
		bool anonymous = GetBool( "anon", true );

		if ( GetControl( "opponent", true ) is DXSelectorField opponent )
		{
			// Anonymous disables opponent, non-anonymous requires opponent
			if ( anonymous )
			{
				opponent.SelectedObject = null;
			}

			opponent.IsDisabled = anonymous;
			opponent.SetState( anonymous ? DXFormControl.ControlState.Normal : DXFormControl.ControlState.Error );

			// Update save button
			IsValid();

			HasOpponentChange = true;
		}
	}

	/* Add Selectors */

	// Adding new Opponent
    private void OnAddOpponent()
	{
		ShowForm( new OpponentForm( null, season ) );
    }

    // Adding new Tournament
    private void OnAddTournament()
    {
        ShowForm( new TournamentForm( null, season ) );
    }

    // Adding new Statistician
    private void OnAddStatistician()
    {
        ShowForm( new StatisticianForm( null, season ) );
    }

    // Adding new Venue
    private void OnAddVenue()
    {
        ShowForm( new VenueForm( null, season ) );
    }

	// Common functionality for showing data form from add selector
    private static void ShowForm( DXForm form )
    {
        DXSpinner.Start();

        // Open in create mode
        Shell.Instance.ShowForm2( form );
    }

	// User added new object to selector field
    public override void OnObjectAdded( string key, DXModel model )
    {
		DXSelectorField control = null;

		switch ( key )
		{
			// Opponent
			case "opponent":
			{
				control = GetControl( key, true ) as DXSelectorField;
				control!.Objects = season.OpponentList;
				break;
			}
			// Tournament
			case "tournament":
			{
				control = GetControl( key, false ) as DXSelectorField;
				control!.Objects = season.TournamentList;
				break;
			}
			// Statistician
			case "statistician":
			{
				control = GetControl( key, false ) as DXSelectorField;
				control!.Objects = season.StatisticianList;
				break;
			}
			// Venue
			case "venue":
			{
				control = GetControl( key, false ) as DXSelectorField;
				control!.Objects = season.VenueList;
				break;
			}
		}

		// Select new object
		if ( control != null )
		{
			control.SelectedObject = model;
			control.SetState( DXFormControl.ControlState.Normal );
		}
    }
}

//
