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

using CommunityToolkit.Maui.Core.Platform;

using DXLib.Email;
using DXLib.Data;
using DXLib.Utils;

#if IOS
	using UIKit;
	using Foundation;
#elif ANDROID
	using Microsoft.Maui.Controls.Compatibility.Platform.Android;
#endif

namespace DXLib.UI.Form.Control;

/*
 * A form control used to input alphanumeric text with basic validation. Can also be used for numeric input if leading
 * zeroes are required.
 */
public class DXTextField : DXFormControl
{
	/* Constants */

	// Textfield formatting options
	public enum TextType
	{
		None,
		CapitalizeWords,
		AllCaps,
		Numbers,
		Email,
		EmailList,
		Password,
		URL
	};

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

	/* Properties */
	public TextType Type { get => textType; set => SetTextType( value ); }
	public string Text { get => textField.Text; set => textField.Text = value; }

	public int MinLength { get; set; }
	public int MaxLength { get => textField.MaxLength; set => textField.MaxLength = value; }

	public bool IsEmpty => string.IsNullOrEmpty( textField.Text );

	// Allows data forms to mark input as invalid
	public bool ForceInvalid { get; set; }

	// Used to control callback for internal text entry
	public bool SuppressEvent { get; set; }

	/* Fields */
	protected readonly Entry textField;

	// State
	private TextType textType;

	/* Methods */
	public DXTextField()
	{
		HasClear = true;
		MinLength = 0;
		
		// Configure underlying control
		textField = new Entry
		{
			Margin = 0,

			Keyboard = Keyboard.Text,		// Default
			ReturnType = ReturnType.Done,

			IsSpellCheckEnabled = false,
			IsTextPredictionEnabled = false,

			TextColor = DXColors.Dark1,
			PlaceholderColor = DXColors.Dark4,

			FontFamily = DXFonts.Roboto,
			FontAttributes = FontAttributes.None,
			FontSize = 17,

			HeightRequest = DefaultControlHt,
			
			HorizontalOptions = LayoutOptions.Fill,
			VerticalOptions = LayoutOptions.Fill
		};

		// Swallow initial population event
		SuppressEvent = true;

		// Register for events
		textField.Focused += OnFocused;
		textField.Unfocused += OnUnfocused;

		textField.TextChanged += OnTextChanged;
		textField.Completed += OnCompleted;
	}

	// Configures keyboard based on text formatting request
	protected virtual void SetTextType( TextType type )
	{
		textType = type;

		switch ( textType )
		{
			// No auto formatting
			case TextType.None:
			{
				textField.Keyboard = Keyboard.Create( KeyboardFlags.None );
				break;
			}
			// All caps
			case TextType.AllCaps:
			{
				textField.Keyboard = Keyboard.Create( KeyboardFlags.CapitalizeCharacter );
				break;
			}
			// Names, etc
			case TextType.CapitalizeWords:
			{
				textField.Keyboard = Keyboard.Create( KeyboardFlags.CapitalizeWord );
				break;
			}
			// Numbers as string (support leading zeroes)
			case TextType.Numbers:
			{
				textField.Keyboard = Keyboard.Telephone;
				break;
			}
			// Email address
			case TextType.Email:
			{
				textField.Keyboard = Keyboard.Email;
				break;
			}
			// List of emails (requires comma)
			case TextType.EmailList:
			{
				textField.Keyboard = Keyboard.Create( KeyboardFlags.CapitalizeNone );
				break;
			}
			// Password
			case TextType.Password:
			{
				textField.Keyboard = Keyboard.Create( KeyboardFlags.CapitalizeNone );
				textField.IsPassword = true;
				break;
			}
			// Web address
			case TextType.URL:
			{
				textField.Keyboard = Keyboard.Url;
				break;
			}

			default: break;
		}
	}

	// Configures textfield specific error hint text
	protected override void SetHintType( HintType type )
	{
		base.SetHintType( type );

		switch ( type )
		{
			// 'Foo must be minimum X character(s)'
			case HintType.RequiredMin:
			{
				hintLbl.Text = DXString.Get( "form.hint.text.minimum", Title, MinLength );
				hintLbl.Text += GetHintExtension( MinLength );
				break;
			}
			// 'Foo must be X-Y character(s)'
			case HintType.RequiredRange:
			{
				hintLbl.Text = DXString.Get( "form.hint.text.range", Title, MinLength, MaxLength );
				hintLbl.Text += GetHintExtension( MaxLength );
				break;
			}
			// 'Please enter a valid email address'
			case HintType.Email:
			{
				hintLbl.Text = DXString.Get( "form.hint.email" );
				break;
			}
			// 'Please enter valid email addresses'
			case HintType.EmailList:
			{
				hintLbl.Text = DXString.Get( "form.hint.emails" );
				break;
			}
			// 'Both password entries must match'
			case HintType.Password:
			{
				hintLbl.Text = DXString.Get( "form.hint.pswd" );
				break;
			}
			// 'Please enter a valid URL'
			case HintType.URL:
			{
				hintLbl.Text = DXString.Get( "form.hint.url" );
				break;
			}

			// Unused
			case HintType.None:
			case HintType.Required:
			case HintType.RequiredMax:
			case HintType.Date:
			case HintType.Duration:
			default: break;
		}
	}

	// Handles pluralization of hint text
	private string GetHintExtension( int count )
	{
		return (textType == TextType.Numbers)
			? DXString.Get( (count == 1) ? "form.hint.digit" : "form.hint.digits" )
			: DXString.Get( (count == 1) ? "form.hint.character" : "form.hint.characters" );
	}

	// Disables text field specific controls
	public override void SetDisabled( bool disabled )
	{
		base.SetDisabled( disabled );

		textField.Opacity = disabled ? DisabledOpacity : 1.0;
	}

	// Hides native keyboard for this text field
	public async Task HideKeyboard()
	{
		await textField.HideKeyboardAsync( CancellationToken.None );	
	}
	
	/* Abstracts */

	// Insert in parent layout
	public override void Init()
	{
		AddControl( textField );
	}

	// Returns underlying text value
	public override object GetObject()
	{
		return string.IsNullOrEmpty( textField.Text ) ? null : textField.Text.Trim();
	}

	// Placeholder only visible when not focused with no value
	public override void SetPlaceholder( bool focused )
	{
		textField.Placeholder = ((focused || HasValue()) ? null : Title)!;
	}

	// Has value if text has been entered
	public override bool HasValue()
	{
		return !string.IsNullOrEmpty( textField.Text );
	}

	// Only valid if optional or minimum text entered
	public override bool IsValid()
	{
		string text = textField.Text;

		bool valid = (!IsRequired && string.IsNullOrEmpty( text ));
		bool hasValue = HasValue();

		return Type switch
		{
			// Special validation for some fields
			TextType.Email => valid || (hasValue && DXEmailUtils.IsValid( text )),
			TextType.EmailList => valid || (hasValue && DXEmailUtils.IsValidList( text )),
			TextType.URL => valid || (hasValue && DXData.IsValidUri( text )),

			// Normal validation
			_ => !IsRequired || (!ForceInvalid && (hasValue && text.Length >= MinLength))
		};
	}

	/* Event Callbacks */

	// Control gained focus
	private void OnFocused( object sender, FocusEventArgs args )
	{
		OnFocus();

		// WORKAROUND: Only 1 tap to show keyboard on Android
		#if ANDROID
			textField.ShowKeyboardAsync( CancellationToken.None );
		#endif
		
		textField.Margin = new Thickness( 0, 0, RightMargin, 0 );
	}

	// Control lost focus
	private void OnUnfocused( object sender, FocusEventArgs args )
	{
		OnUnfocus();

		textField.Margin = 0;
	}

	// Text editing occurred
	protected virtual void OnTextChanged( object sender, TextChangedEventArgs args )
	{
		if ( !SuppressEvent )
		{
			OnChange();

			// Optional direct callback
			TextChanged?.Invoke();
		}

		SuppressEvent = false;
	}

	// Editing done
	protected void OnCompleted( object sender, EventArgs args )
	{
		OnDone();
	}

	// User cleared current text value
	protected override void OnClearTapped()
	{
		Text = null;

		base.OnClearTapped();
	}
}

/*
 * MAUI Handler for device specific Entry customization.
 */
public static class EntryHandler
{
	// Removes Entry border
	public static void RemoveBorder()
	{
		Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping( "Borderless", ( handler, view ) =>
		{
			#if IOS
				handler.PlatformView.BackgroundColor = UIKit.UIColor.Clear;
				handler.PlatformView.Layer.BorderWidth = 0;
				handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None;
			#elif ANDROID
				handler.PlatformView.Background = null;
				handler.PlatformView.SetBackgroundColor( Android.Graphics.Color.Transparent );
				handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf( Colors.Transparent.ToAndroid() );
			#endif
		});
	}
}

//
