using System.Globalization;
using Connected.Annotations;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace Connected.Components;

public abstract class InputBase<T> : FormComponent<T, string>
{
   private bool _isDirty;

   protected InputBase() : base(new DefaultConverter<T>()) { }

   /// <summary>
   /// If true, the input element will be disabled.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool Disabled { get; set; }

   /// <summary>
   /// If true, the input will be read-only.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool ReadOnly { get; set; }

   /// <summary>
   /// If true, the input will take up the full width of its container.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Appearance)]
   public bool FullWidth { get; set; }

   /// <summary>
   /// If true, the input will update the Value immediately on typing.
   /// If false, the Value is updated only on Enter.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool Immediate { get; set; }

   /// <summary>
   /// If true, the input will not have an underline.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Appearance)]
   public bool DisableUnderLine { get; set; }

   /// <summary>
   /// The HelperText will be displayed below the text field.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public string HelperText { get; set; }

   /// <summary>
   /// If true, the helper text will only be visible on focus.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool HelperTextOnFocus { get; set; }

   /// <summary>
   /// Glyph that will be used if Adornment is set to Start or End.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public string AdornmentIcon { get; set; }

   /// <summary>
   /// Text that will be used if Adornment is set to Start or End, the Text overrides Glyph.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public string AdornmentText { get; set; }

   /// <summary>
   /// The Adornment if used. By default, it is set to None.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public Adornment Adornment { get; set; } = Adornment.None;

   /// <summary>
   /// The validation is only triggered if the user has changed the input value at least once. By default, it is false
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool OnlyValidateIfDirty { get; set; } = false;

   /// <summary>
   /// The color of the adornment if used. It supports the theme colors.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Appearance)]
   public ThemeColor AdornmentColor { get; set; } = ThemeColor.Default;

   /// <summary>
   /// The aria-label of the adornment.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Appearance)]
   public string AdornmentAriaLabel { get; set; } = string.Empty;

   /// <summary>
   /// The Glyph Size.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Appearance)]
   public Size IconSize { get; set; } = Size.Medium;

   /// <summary>
   /// Button click event if set and Adornment used.
   /// </summary>
   [Parameter] public EventCallback<MouseEventArgs> OnAdornmentClick { get; set; }

   /// <summary>
   /// Variant to use.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Appearance)]
   public Variant Variant { get; set; } = Variant.Text;

   /// <summary>
   ///  Will adjust vertical spacing.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Appearance)]
   public Margin Margin { get; set; } = Margin.None;

   /// <summary>
   /// The short hint displayed in the input before the user enters a value.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public string Placeholder { get; set; }

   /// <summary>
   /// If set, will display the counter, value 0 will display current count but no stop count.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Validation)]
   public int? Counter { get; set; }

   /// <summary>
   /// Maximum number of characters that the input will accept
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Validation)]
   public int MaxLength { get; set; } = 524288;

   /// <summary>
   /// If string has value the label text will be displayed in the input, and scaled down at the top if the input has value.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public string Label { get; set; }

   /// <summary>
   /// If true the input will focus automatically.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool AutoFocus { get; set; }

   /// <summary>
   ///  A multiline input (textarea) will be shown, if set to more than one line.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public int Lines { get; set; } = 1;

   /// <summary>
   ///  The text to be displayed.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Data)]
   public string Text { get; set; }

   /// <summary>
   /// When TextUpdateSuppression is true (which is default) the text can not be updated by bindings while the component is focused in BSS (not WASM).
   /// This solves issue #1012: Textfield swallowing chars when typing rapidly
   /// If you need to update the input's text while it is focused you can set this parameter to false.
   /// Note: on WASM text update suppression is not active, so this parameter has no effect.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool TextUpdateSuppression { get; set; } = true;

   /// <summary>
   ///  Hints at the type of data that might be entered by the user while editing the input
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public virtual InputMode InputMode { get; set; } = InputMode.text;

   /// <summary>
   /// The pattern attribute, when specified, is a regular expression which the input's value must match in order for the value to pass constraint validation. It must be a valid JavaScript regular expression
   /// Not Supported in multline input
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Validation)]
   public virtual string Pattern { get; set; }

   /// <summary>
   /// Derived classes need to override this if they can be something other than text
   /// </summary>
   internal virtual InputType GetInputType() { return InputType.Text; }

   protected virtual async Task SetTextAsync(string text, bool updateValue = true)
   {
      if (Text != text)
      {
         Text = text;
         if (!string.IsNullOrWhiteSpace(Text))
            Modified = true;
         if (updateValue)
            await UpdateValuePropertyAsync(false);
         await TextChanged.InvokeAsync(Text);
      }
   }

   /// <summary>
   /// Text change hook for descendants. Called when Text needs to be refreshed from current Value property.
   /// </summary>
   protected virtual Task UpdateTextPropertyAsync(bool updateValue)
   {
      return SetTextAsync(Converter.Convert(Value), updateValue);
   }

   /// <summary>
   /// Focuses the element
   /// </summary>
   /// <returns>The ValueTask</returns>
   public virtual ValueTask FocusAsync() { return new ValueTask(); }

   public virtual ValueTask BlurAsync() { return new ValueTask(); }

   public virtual ValueTask SelectAsync() { return new ValueTask(); }

   public virtual ValueTask SelectRangeAsync(int pos1, int pos2) { return new ValueTask(); }

   /// <summary>
   /// Fired when the text value changes.
   /// </summary>
   [Parameter] public EventCallback<string> TextChanged { get; set; }

   /// <summary>
   /// Fired when the element loses focus.
   /// </summary>
   [Parameter] public EventCallback<FocusEventArgs> OnBlur { get; set; }

   /// <summary>
   /// Fired when the element changes internally its text value.
   /// </summary>
   [Parameter]
   public EventCallback<ChangeEventArgs> OnInternalInputChanged { get; set; }

   protected bool _isFocused;

   protected internal virtual void OnBlurred(FocusEventArgs obj)
   {
      _isFocused = false;

      if (!OnlyValidateIfDirty || _isDirty)
      {
         Modified = true;
         BeginValidateAfter(OnBlur.InvokeAsync(obj));
      }
   }

   /// <summary>
   /// Fired on the KeyDown event.
   /// </summary>
   [Parameter] public EventCallback<KeyboardEventArgs> OnKeyDown { get; set; }

   protected virtual void InvokeKeyDown(KeyboardEventArgs obj)
   {
      _isFocused = true;
      OnKeyDown.InvokeAsync(obj).AndForget();
   }

   /// <summary>
   /// Prevent the default action for the KeyDown event.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool KeyDownPreventDefault { get; set; }


   /// <summary>
   /// Fired on the KeyPress event.
   /// </summary>
   [Parameter] public EventCallback<KeyboardEventArgs> OnKeyPress { get; set; }

   protected virtual void InvokeKeyPress(KeyboardEventArgs obj)
   {
      OnKeyPress.InvokeAsync(obj).AndForget();
   }

   /// <summary>
   /// Prevent the default action for the KeyPress event.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool KeyPressPreventDefault { get; set; }

   /// <summary>
   /// Fired on the KeyUp event.
   /// </summary>
   [Parameter] public EventCallback<KeyboardEventArgs> OnKeyUp { get; set; }

   protected virtual void InvokeKeyUp(KeyboardEventArgs obj)
   {
      _isFocused = true;
      OnKeyUp.InvokeAsync(obj).AndForget();
   }

   /// <summary>
   /// Prevent the default action for the KeyUp event.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public bool KeyUpPreventDefault { get; set; }

   /// <summary>
   /// Fired when the Value property changes.
   /// </summary>
   [Parameter]
   public EventCallback<T> ValueChanged { get; set; }

   /// <summary>
   /// The value of this input element.
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Data)]
   public T Value
   {
      get => _value;
      set => _value = value;
   }

   protected virtual async Task SetValueAsync(T value, bool updateText = true)
   {
      if (!EqualityComparer<T>.Default.Equals(Value, value))
      {
         _isDirty = true;
         Value = value;
         if (updateText)
            await UpdateTextPropertyAsync(false);
         await ValueChanged.InvokeAsync(Value);
         BeginValidate();
         FieldChanged(Value);
      }
   }

   /// <summary>
   /// Value change hook for descendants. Called when Value needs to be refreshed from current Text property.
   /// </summary>
   protected virtual Task UpdateValuePropertyAsync(bool updateText)
   {
      return SetValueAsync(Converter.ConvertBack(Text), updateText);
   }

   protected override bool SetConverter(Converter<T, string> value)
   {
      var changed = base.SetConverter(value);
      if (changed)
         UpdateTextPropertyAsync(false).AndForget();      // refresh only Text property from current Value

      return changed;
   }

   protected override bool SetCulture(CultureInfo value)
   {
      var changed = base.SetCulture(value);
      if (changed)
         UpdateTextPropertyAsync(false).AndForget();      // refresh only Text property from current Value

      return changed;
   }

   /// <summary>
   /// Conversion format parameter for ToString(), can be used for formatting primitive types, DateTimes and TimeSpans
   /// </summary>
   [Parameter]
   [Category(CategoryTypes.FormComponent.Behavior)]
   public string Format
   {
      get => ((ToStringConverter<T>)Converter).Format;
      set => SetFormat(value);
   }

   protected virtual bool SetFormat(string value)
   {
      var changed = Format != value;
      if (changed)
      {
         ((ToStringConverter<T>)Converter).Format = value;
         UpdateTextPropertyAsync(false).AndForget();      // refresh only Text property from current Value
      }
      return changed;
   }

   protected override Task ValidateValue()
   {
      if (SubscribeToParentForm)
         return base.ValidateValue();

      return Task.CompletedTask;
   }

   protected override async Task OnInitializedAsync()
   {
      await base.OnInitializedAsync();

      // Because the way the Value setter is built, it won't cause an update if the incoming Value is
      // equal to the initial value. This is why we force an update to the Text property here.
      if (typeof(T) != typeof(string))
         await UpdateTextPropertyAsync(false);

      if (Label == null && For != null)
         Label = For.GetLabelString();
   }

   public virtual void ForceRender(bool forceTextUpdate)
   {
      _forceTextUpdate = true;
      UpdateTextPropertyAsync(false).AndForget();
      StateHasChanged();
   }

   protected bool _forceTextUpdate;

   public override async Task SetParametersAsync(ParameterView parameters)
   {
      await base.SetParametersAsync(parameters);

      var hasText = parameters.Contains<string>(nameof(Text));
      var hasValue = parameters.Contains<T>(nameof(Value));

      // Refresh Value from Text
      if (hasText && !hasValue)
         await UpdateValuePropertyAsync(false);

      // Refresh Text from Value
      if (hasValue && !hasText)
      {
         var updateText = true;
         if (_isFocused && !_forceTextUpdate)
         {
            // Text update suppression, only in BSS (not in WASM).
            // This is a fix for #1012
            if (RuntimeLocation.IsServerSide && TextUpdateSuppression)
               updateText = false;
         }
         if (updateText)
         {
            _forceTextUpdate = false;
            await UpdateTextPropertyAsync(false);
         }
      }
   }

   protected override async Task OnAfterRenderAsync(bool firstRender)
   {
      //Only focus automatically after the first render cycle!
      if (firstRender && AutoFocus)
      {
         await FocusAsync();
      }
   }

   protected override void OnParametersSet()
   {
      if (SubscribeToParentForm)
         base.OnParametersSet();
   }

   protected override void ResetValue()
   {
      SetTextAsync(null, updateValue: true).AndForget();
      _isDirty = false;
      base.ResetValue();
   }
}
