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

using CommunityToolkit.Mvvm.Messaging;

using DXLib.UI.Layout;
using DXLib.UI.Gestures;
using DXLib.UI.Form.Message;

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

using DXLib.Utils;

namespace DXLib.UI.Form;

/*
 * Base class for all form controls. The control title, help tooltip button, underline, and error hint text are all
 * handled here. Child classes must implement the input specific control as well as extend/implement the relevant
 * abstract methods. An optional content clear button is also provided.
 */
public abstract class DXFormControl : DXGridGestures
{
	/* Constants */
	public static readonly double DefaultControlHt = DXDevice.IsIOS ? 36 : 41;
	public const double DisabledOpacity = 0.4;

	// Available error hinting options
	public enum HintType
	{
		None,
		Required,
		RequiredMin,
		RequiredMax,
		RequiredRange,
		Email,
		EmailList,
		URL,
		Password,
		Date,
		Duration,
		Custom
	};
	
	// Current mode
	public enum ControlState
	{
		Normal,
		Focused,
		Error
	};

	// Clear button layout
	public static double LeftMargin => DXDevice.IsIOS ? -5 : 0;
	public double RightMargin => HasClear ? (DXDevice.IsIOS ? (DXDevice.IsTablet ? 27 : 15) : 27) : 0;

	/* Events */
	public Action ControlChanged { get; set; }

	/* Properties */
	public string Key { get; set; }
	public virtual bool IsRequired { get => isRequired; set => SetRequired( value ); }

	public bool IsDisabled { get => isDisabled; set => SetDisabled( value ); }
	public new bool IsReadOnly { set => SetReadOnly( value ); }
	public bool IsNormal => currentState == ControlState.Normal;

	// Has control been edited?
	public bool HasChanges { get; set; }

	// Title
	public string Title { get => title; set => SetTitle( DXString.Get( value ) ); }
	public string TitleRaw { set => SetTitle( value ); }

	// Help
	public string Help { set { help = value; helpBtn.IsVisible = !string.IsNullOrEmpty( value ); } }
	public HintType Hint { set => SetHintType( value ); }

	// Tooltip sizing
	public double HelpWd { get; set; }
	public double HelpHt { get; set; }

	// Fields can have clear button
	public bool HasClear { get; set; }

	// Optional control buttons
	public bool HasHelp => (help != null);

	/* Fields */

	// Underlying controls
	private readonly DXLabel titleLbl;
	protected readonly DXIconButton helpBtn;
	protected readonly DXLine line;

	protected DXIconButton clearBtn;
	protected readonly DXLabel hintLbl;

	// State
	private readonly bool custom;

	private bool isRequired;
	private bool isDisabled;

	private string title;
	private string help;

	private ControlState currentState;
	private HintType hintType;

	/* Methods */
	protected DXFormControl( bool custom = false )
	{
		this.custom = custom;

		bool ios = DXDevice.IsIOS;
		
		// Custom control uses same event handling but separate UI
		if ( !custom )
		{
			BackgroundColor = DXColors.Light4;

			// Spacing
			Padding = new Thickness( 0, 10, 0, 0 );
			Margin = 0;

			RowSpacing = 0;
			ColumnSpacing = 10;

			Horizontal = LayoutOptions.Fill;
			Vertical = LayoutOptions.Fill;
			
			IsClippedToBounds = true;

			// Title
			titleLbl = new DXLabel
			{
				Font = DXFonts.Roboto,
				FontSize = 12,
				
				TextColor = DXColors.Dark4,
				
				Horizontal = LayoutOptions.Fill,
				Vertical = LayoutOptions.Fill
			};

			// Help tooltip (added later)
			helpBtn = new DXIconButton
			{
				Resource = DXDevice.IsTablet ? "help" : "help_mobile",
				IconColor = DXColors.Dark4,
				Size = 30,

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

				IsSticky = true,
				ButtonTapped = OnHelpTapped
			};
			
			// Underline (wrapped in layout to force clipping)
			DXVerticalLayout lineLayout = new()
			{
				Margin = 0,
				Padding = 0,
				Spacing = 0,
				
				IsClippedToBounds = true
			};
			
			line = new DXLine
			{
				Length = 500,
				Horizontal = LayoutOptions.Start
			};

			lineLayout.Add( line );

			// Error hint text
			hintLbl = new DXLabel
			{
				Margin = new Thickness( 0, (ios ? 3 : 0), 0, 0 ),

				Font = DXFonts.Roboto,
				FontSize = 12
			};
			
			// 2 columns
			AddStarColumn();					// 0: control
			AddFixedColumn( 26 );				// 1: help
			
			// 4 rows
			AddFixedRow( ios ? 14 : 17 );		// 0: title
			AddFixedRow( DefaultControlHt );	// 1: control
			AddFixedRow( 2 );					// 2: line
			AddFixedRow( 28 );					// 3: hint
			
			// Required to correctly hold space
			HeightRequest = ios ? 80 : 83;
			
			// Add components
			Add( titleLbl, 0, 0 );
			Add( lineLayout, 0, 2, 1, 3 );
			Add( hintLbl, 0, 3 );
			
			// Defaults
			HasClear = false;
			HasChanges = false;

			Hint = HintType.None;
		}
	}

	// Updates title text for this control
	public void SetTitle( string value, bool collapse = true )
	{
		// Not using title
		if ( value == null )
		{
			title = null;

			Remove( titleLbl );

			// Optionally collapse space in layout
			if ( collapse )
			{
				RowDefinitions.RemoveAt( 0 );
			}
		}
		// Update
		else
		{
			title = value;
		}

		titleLbl.Text = title!;
	}

	// Used by subclass to insert concrete control into layout
	protected void AddControl( View view )
	{
		Add( view, 0, 1 );

		// Add clear/help
		AddButtons( 1 );
	}

	// Adds optional clear and help tooltip buttons
	private void AddButtons( int row )
	{
		// Button to clear content (overlays control)
		if ( HasClear )
		{
			clearBtn = new DXIconButton
			{
				IsVisible = false,

				Resource = "cancel",
				IconColor = DXColors.Light1,
				Size = 24,

				Margin = 0,
				Padding = 0,

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

				IsSticky = false,
				ButtonTapped = OnClearTapped
			};

			clearBtn.Init();
			
			Add( clearBtn, 0, row );
		}

		// Help tooltip (separate column)
		if ( HasHelp )
		{
			Add( helpBtn, 1, row );
		}
		else
		{
			ColumnDefinitions.RemoveAt( 1 );
		}
	}

	// Sets background color for this control
	public virtual void SetColor( Color color )
	{
		BackgroundColor = color;
	}

	// Sets whether entry for this control is required or optional
	protected virtual void SetRequired( bool required )
	{
		isRequired = required;

		// Update UI
		if ( !string.IsNullOrEmpty( title ) )
		{
			titleLbl.Text = title;

			// Append '*'
			if ( isRequired && !string.IsNullOrEmpty( title.Trim() ) )
			{
				titleLbl.Text += $" {DXString.Get( "form.asterisk" )}";
			}
		}
	}

	// Enables/disables this control both for input and visually
	public virtual void SetDisabled( bool disabled )
	{
		isDisabled = disabled;
		IsEnabled = !disabled;

		double opacity = isDisabled ? DisabledOpacity : 1.0;

		// Enable/Disable all base controls
		titleLbl.Opacity = opacity;
		line.Opacity = opacity;
		hintLbl.Opacity = isDisabled ? 0.0 : 1.0;
		helpBtn.Opacity = opacity;
	}

	// Marks control read-only, visually same as disabled
	private void SetReadOnly( bool readOnly )
	{
		if ( readOnly )
		{
			IsDisabled = true;
			Opacity = DisabledOpacity;
		}
	}

	// Provides common hint values, control specific values must be set by children
	protected virtual void SetHintType( HintType type )
	{
		hintType = type;

		hintLbl.Text = type switch
		{
			// 'Foo is a required field'
			HintType.Required => DXString.Get( "form.hint.required", Title ),
			
			_ => hintLbl.Text
		};
	}

	// Un-focuses control leaving in error or normal state
	public void LoseFocus()
	{
		SetState( (currentState == ControlState.Error) ? ControlState.Error : ControlState.Normal );
	}

	// Updates error/normal state based on focus state
	public void UpdateState( bool focused )
	{
		SetState( IsValid() ? (focused ? ControlState.Focused : ControlState.Normal) : ControlState.Error );
	}

	// Updates control color and other display values based on specified state
	public void SetState( ControlState state )
	{
		currentState = state;

		Color color;

		double thickness;
		double titleOpacity;
		double hintOpacity;

		switch ( state )
		{
			// Focused (without error)- blue color, title visible
			case ControlState.Focused:
			{
				SetPlaceholder( true );

				color = DXColors.Action;
				thickness = 2;
				titleOpacity = 1.0;
				hintOpacity = 0.0;
				break;
			}
			// Invalid- red color, title and hint message visible
			case ControlState.Error:
			{
				SetPlaceholder( true );

				color = isDisabled ? DXColors.Dark4 : DXColors.Negative;
				thickness = isDisabled ? 1 : 2;
				titleOpacity = isDisabled ? DisabledOpacity : 1.0;
				hintOpacity = ((hintType == HintType.None) || IsDisabled) ? 0.0 : 1.0;
				break;
			}
			// Normal (unfocused, no error)- gray color, title if not empty
			case ControlState.Normal:
			default:
			{
				SetPlaceholder( false );

				color = DXColors.Dark4;
				thickness = 1;
				titleOpacity = HasValue() ? (IsDisabled ? DisabledOpacity : 1.0) : 0.0;
				hintOpacity = 0.0;
				break;
			}
		}

		// Update UI
		titleLbl.TextColor = color;
		line.Color = color;
		hintLbl.TextColor = color;

		line.Thickness = thickness;

		titleLbl.Opacity = titleOpacity;
		hintLbl.Opacity = hintOpacity;
	}

	/* Abstracts */

	// Post-construction initialization
	public abstract void Init();

	// Returns control specific value, must be implemented by subclass
	public abstract object GetObject();

	// Sets placeholder text value, must be implemented by subclass
	public abstract void SetPlaceholder( bool focused );

	// Determines if control has a value, must be implemented by subclass
	public abstract bool HasValue();

	// Determines if current value is valid, must be implemented by subclass
	public abstract bool IsValid();

	/* Event Callbacks */

	// Control gained focus
	protected virtual void OnFocus()
	{
		// Clear button only if value entered
		if ( HasClear )
		{
			clearBtn.IsVisible = HasValue() && !IsDisabled;
		}

		UpdateState( true );

		// Notify parent
		WeakReferenceMessenger.Default.Send( new DXFormFocusedMessage( true ) );
	}

	// Control lost focus
	protected virtual void OnUnfocus()
	{
		// Clear button NA
		if ( HasClear )
		{
			clearBtn.IsVisible = false;
		}

		UpdateState( false );
	}

	// Control value changed
	protected void OnChange( bool focused = true )
	{
		HasChanges = true;

		// Clear button only if value entered
		if ( HasClear )
		{
			clearBtn.IsVisible = HasValue();
		}

		if ( !custom )
		{
			UpdateState( focused );
		}

		// Notify listener (either direct or message, not both)
		if ( ControlChanged == null )
		{
			WeakReferenceMessenger.Default.Send( new DXFormChangedMessage( this ) );
		}
		else
		{
			ControlChanged.Invoke();
		}
	}

	// Control editing completed
	protected void OnDone()
	{
		UpdateState( false );
	}

	// User tapped content clear button
	protected virtual void OnClearTapped()
	{
		clearBtn.IsVisible = false;
	}

	// Display help tooltip
	private void OnHelpTapped()
	{
		if ( HasHelp )
		{
			DXTooltip.Show( helpBtn, help, HelpWd, HelpHt );
		}
	}
}

//
