using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;

using CommonHelpers.WASM;
using AFXUI;

namespace BookRegistry.Client.Shared {

//**************************************************************************************************
/// <summary>
/// Drop-down list control (application components lib.)
/// </summary>
public partial class ADropDownList: AComponent {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public ElementReference? SelectElement { get; protected set; }
protected AErrorFrame? ErrorFrame;
protected AResetControl? ResetControl;
protected AUpResetControl? UpResetControl;
//==================================================================================================

public static string? DefaultSelectClasses;// = "form-control-sm";
public static string? DefaultSelectStyle;
public static string? DefaultContainerStyle = "display: inline";
public static ElementPresence DefaultResetPresence = ElementPresence.None;

public static string ResetHRefValue => "#reset-item";
public static string ResetHRefTitle => "Reset list item";

[Parameter] public string? ContainerId { get; set; }
[Parameter] public string? ContainerClasses { get; set; }
[Parameter] public string? ContainerStyle_0 { get; set; } = DefaultContainerStyle;
[Parameter] public string? ContainerStyle { get; set; }
[Parameter] public string? SelectId { get; set; }
[Parameter] public string? SelectClasses_0 { get; set; } = DefaultSelectClasses;
[Parameter] public string? SelectClasses { get; set; }
[Parameter] public string? SelectStyle_0 { get; set; } = DefaultSelectStyle;
[Parameter] public string? SelectStyle { get; set; }
[Parameter] public string? OptionClasses { get; set; }
[Parameter] public string? OptionStyle { get; set; }
[Parameter] public string? SelectedOptionClasses { get; set; }
[Parameter] public string? SelectedOptionStyle { get; set; }
[Parameter] public string? ResetHRefId { get; set; }
[Parameter] public string? ResetHRefClasses { get; set; }
[Parameter] public string? ResetHRefStyle { get; set; }
[Parameter] public string? Title { get; set; }
[Parameter] public string? AccessKey { get; set; }
[Parameter] public int? TabIndex { get; set; }
[Parameter] public bool Enabled { get; set; } = true;
[Parameter] public string? ItemCaptionFieldName { get; set; }
[Parameter] public ElementPresence ResetPresence { get; set; } = DefaultResetPresence;
[Parameter] public ResetControlType ResetControlType { get; set; }
[Parameter] public IReadOnlyList<object>? InitialItems { get; set; }
[Parameter] public bool SelectFirstItemOnInit { get; set; }
[Parameter] public bool? AllowResetFromKeyboard { get; set; }

[Parameter] public Action? ON_SelectIndex { get; set; }
[Parameter] public Action? ON_Change { get; set; }
[Parameter] public Action<KeyboardEventArgs>? ON_KeyDown { get; set; }
[Parameter] public Action? ON_Focus { get; set; }
[Parameter] public Action? ON_Blur { get; set; }

[Parameter] public EventCallback ON_SelectIndex_A { get; set; }
[Parameter] public EventCallback ON_Change_A { get; set; }
[Parameter] public EventCallback<KeyboardEventArgs> ON_KeyDown_A { get; set; }
[Parameter] public EventCallback ON_Focus_A { get; set; }
[Parameter] public EventCallback ON_Blur_A { get; set; }

[Parameter] public InputValidationCallback_SelectedIdx? InputValidationCallback { get; set; }

[Parameter] public string? InitialErrorMessage { get; set; }

[Parameter(CaptureUnmatchedValues=true)]
public Dictionary<string,object>? Attributes { get; set; }

public AResetControl? OuterResetControl { get; set; }
public AUpResetControl? OuterUpResetControl { get; set; }

//--------------------------------------------------------------------------------------------------

public IReadOnlyList<object> Items {
   get => _Items_;
   set => SetItems( value );
}

public int ItemCount => Items.Count;
public bool HasItems => ItemCount > 0;

/// <exception cref="ArgumentOutOfRangeException" />
[HOptionalThroughMinusOne]
public int SelectedIndex {
   get => _SelectedIndex_;
   set => SetSelectedIndex( value );
}

public bool IsSelected => SelectedIndex >= 0;

public bool SetSelectedIndex(int index,bool validateInputIfChanged=true)
{
   int selectItemIdx = ToSelectItemIndex( index );
   JS.Invoke( "ADropDownList_SetSelectedIndex", SelectElement, selectItemIdx );
   int newIndex = selectItemIdx - 1;
   bool indexChange = newIndex != _SelectedIndex_;
   _SelectedIndex_ = newIndex;
   if ( indexChange && validateInputIfChanged )
      ValidateInput();
   if ( indexChange )
      OnSelectIndex_Invoke();
   return index >= 0;
}
public void Reset(bool validateInputIfChanged=true) =>
   SetSelectedIndex( -1, validateInputIfChanged );

public bool SelectFirstItem(bool validateInputIfChanged=true)
{
   if ( HasItems )
      return SetSelectedIndex( 0, validateInputIfChanged );
   return false;
}

//--------------------------------------------------------------------------------------------------

public object? SelectedItem =>
   IsSelected ? Items[ SelectedIndex ] : null;

/// <exception cref="HArgumentTypeException" />
public T? GetSelectedItem<T>() where T: class =>
   IsSelected ? GetItem<T>( SelectedIndex ) : null;
/// <exception cref="HArgumentTypeException" />
public T? GetSelectedItemValue<T>() where T: struct =>
   IsSelected ? (T?)GetItem<T>( SelectedIndex ) : null;

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public string? ErrorMessage {
   get => ErrorFrameNN.Message;
   set => ErrorFrameNN.Message = value;
}
/// <exception cref="InvalidOperationException" />
public bool HasError => ErrorFrameNN.HasError;

//==================================================================================================

public ADropDownList() =>
   AvoidThickRendering = true;

//==================================================================================================

public object GetItemObject(int itemIdx)
{
   if ( !HOps.IsBetween( itemIdx, 0, ItemCount-1 ) )
      throw new ArgumentOutOfRangeException(string.Format(
         "Extracted item index ({0}) is not in allowed range: [{1}..{2}] (\"ADropDownList\").",
         itemIdx, 0, ItemCount-1 ));
   return Items[ itemIdx ];
}

/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="HArgumentTypeException" />
public T GetItem<T>(int itemIdx)
{
   object itemObject = GetItemObject( itemIdx );
   if (!( itemObject is T ))
      throw new HArgumentTypeException(
         "Wrong generic type was specified for item extraction (\"ADropDownList\")." );
   return (T)itemObject;
}

/// <exception cref="ArgumentOutOfRangeException" />
public string? GetItemCaption(int itemIdx)
{
   object itemObject = Items[ itemIdx ];
   if ( ItemCaptionFieldName == null )
      return itemObject.ToString();
   object? propertyValue = HPropertyInfo.GetInstanceValue( itemObject, ItemCaptionFieldName );
   return propertyValue!=null ? propertyValue.ToString() : itemObject.ToString();
}

//--------------------------------------------------------------------------------------------------

public bool SetItems( IReadOnlyList<object>? items, bool selectFirstItem=false )
{
   items ??= new object[0];
   int selectedIdx = selectFirstItem &&  HasItems ? 0 : -1;
   return SetItems( items, selectedIdx );
}
public bool SetItems( IReadOnlyList<object>? items,
                      [HOptionalThroughMinusOne] int selectedIdx )
{
   _Items_ = items ?? new object[0];
   if ( !HOps.IsBetween( selectedIdx, -1, ItemCount-1 ) )
      selectedIdx = -1;
   _SelectedIndex_ = selectedIdx;
   UpdateForRendering();
   ResetError();
   return selectedIdx >= 0;
}

public bool CopyItemsFrom( IReadOnlyList<object>? sourceItems, bool selectFirstItem=false ) =>
   SetItems( sourceItems!=null ? sourceItems.ToArray() : new object[0],
             selectFirstItem );
public bool CopyItemsFrom( IReadOnlyList<object>? sourceItems,
                           [HOptionalThroughMinusOne] int selectedIdx ) =>
   SetItems( sourceItems!=null ? sourceItems.ToArray() : new object[0],
             selectedIdx );

public void Clear() =>
   Items = new object[0];

//--------------------------------------------------------------------------------------------------

/// <exception cref="InvalidOperationException" />
public void SetError(string message) => ErrorFrameNN.SetError( message );
/// <exception cref="InvalidOperationException" />
public void ResetError() => ErrorFrameNN.ResetError();

public bool ValidateInput() =>
   (ErrorMessage = InputValidationCallback?.Invoke(SelectedIndex)) == null;

//--------------------------------------------------------------------------------------------------

public bool Focus() =>
   Enabled ? SelectElement.Focus() : false;

public bool DeferredFocus() =>
   Enabled ? SelectElement.DeferredFocus() : false;
public bool DeferredFocus(int milliseconds) =>
   Enabled ? SelectElement.DeferredFocus( milliseconds ) : false;

//==================================================================================================

protected int SelectedIndexValue =>
   _SelectedIndex_;

protected bool IsIndexInValidRange(int virtualItemIdx) =>
   HOps.IsBetween( virtualItemIdx, -1, ItemCount-1 );
/// <exception cref="ArgumentOutOfRangeException" />
protected void VerifyAssignedIndex(int virtualIndexToSet) =>
   ToSelectItemIndex( virtualIndexToSet );
/// <exception cref="ArgumentOutOfRangeException" />
protected int ToSelectItemIndex(int virtualIndexToSet)
{
   if ( !IsIndexInValidRange( virtualIndexToSet ) )
      throw new ArgumentOutOfRangeException(string.Format(
         "Assigned item index ({0}) is not in allowed range: [{1}..{2}] (\"ADropDownList\").",
         virtualIndexToSet, -1, ItemCount-1 ));
   return virtualIndexToSet + 1;
}

//--------------------------------------------------------------------------------------------------

protected string SelectElement_EDClass =>
   Enabled ? "a_dropdown_list_EnabledSELECT" : "a_dropdown_list_DisabledSELECT";

protected string? ResetControlStyle =>
   ResetPresence != ElementPresence.PlaceOnly ?
      ResetHRefStyle : "visibility: hidden" +
      (!string.IsNullOrWhiteSpace( ResetHRefStyle ) ? "; "+ResetHRefStyle : "");

/// <exception cref="InvalidOperationException" />
protected AErrorFrame ErrorFrameNN =>
   ErrorFrame ?? throw new InvalidOperationException(
      "\"ErrorFrameNN\"-property is not available (\"ADropDownList\").");

//--------------------------------------------------------------------------------------------------

protected override void OnAfterRender(bool firstRender) =>
go_nt(()=>{
   base.OnAfterRender( firstRender );
   if ( ResetControl != null )
      ResetControl.Target = this;
   if ( UpResetControl != null )
      UpResetControl.Target = this;
});

protected override void On_FirstParametersSet()
{
   if ( InitialItems != null )
      _Items_ = InitialItems;
   _SelectedIndex_ = SelectFirstItemOnInit && HasItems ? 0 : -1;
}

//==================================================================================================

[JSInvokable]
public static void wasm_ADropDownList_OnBlur(string uniqueId) =>
   GetByUniqueId<ADropDownList>( uniqueId )?.SelectElement_OnBlur();

//==================================================================================================

void SelectElement_OnChange() =>
go_nt(()=>{
   _SelectedIndex_ =
      Math.Max( JS.Invoke<int>( "ADropDownList_GetSelectedIndex", SelectElement ) - 1, -1 );
   OnSelectIndex_Invoke();
   ValidateInput();
   OnChange_Invoke();
});

void SelectElement_OnKeyDown(KeyboardEventArgs e) =>
go_nt(()=>{
   if ( !Enabled )
      return;
   if ( UInput.IsPureKey( e, "Delete", "Del" ) )
   {
      bool
         resetRestricted = AllowResetFromKeyboard.HasValue ? !AllowResetFromKeyboard.Value : false,
         resetAllowedExplicitly = AllowResetFromKeyboard ?? false;
      ResetControlType?
         activeResetType = null;
      if ( ResetPresence == ElementPresence.Available &&
           ResetControlType == ResetControlType.Reset ||
           (OuterResetControl?.Enabled ?? false) )
         activeResetType = ResetControlType.Reset;
      if ( ResetPresence == ElementPresence.Available &&
           ResetControlType == ResetControlType.UpReset ||
           (OuterUpResetControl?.Enabled ?? false) )
         activeResetType = ResetControlType.UpReset;
      if ( activeResetType.HasValue && !resetRestricted || resetAllowedExplicitly )
         if ( activeResetType == ResetControlType.Reset )
            ResetManually();
         else
         if ( activeResetType == ResetControlType.UpReset )
            UpResetManually();
   }
   OnKeyDown_Invoke( e );
});

void SelectElement_OnFocus() =>
go_nt(()=>{
   OnFocus_Invoke();
});
void SelectElement_OnBlur() =>
go_nt(()=>{
   OnBlur_Invoke();
});

//--------------------------------------------------------------------------------------------------

void ResetManually()
{
   bool wasSelected = IsSelected;
   Reset();
   if ( wasSelected )
      OnChange_Invoke();
}
bool ResetClicked()
{
   if ( !Enabled )
      return false;
   ResetManually();
   SelectElement.Focus();
   return true;
}

void UpResetManually()
{
   int selectedIdx = SelectedIndex;
   SelectFirstItem();
   if ( SelectedIndex != selectedIdx )
      OnChange_Invoke();
}
bool UpResetClicked()
{
   if ( !Enabled )
      return false;
   UpResetManually();
   SelectElement.Focus();
   return true;
}

//==================================================================================================

public override bool IsEnabled =>
   Enabled && SelectElement.IsEnabled();

//--------------------------------------------------------------------------------------------------

public override ElementReference? MainElement { get => SelectElement; }
public override ElementReference? AdditionalElement
{
   get {
      switch ( ResetControlType )
      {
      case ResetControlType.Reset  : return ResetControl?.MainElement;
      case ResetControlType.UpReset: return UpResetControl?.MainElement;
      default: return null;
      }
   }
}

//==================================================================================================

public override string? Interop_ErrorMessage => ErrorMessage;

public override AResetControl? Interop_OuterResetControl
{
   get => OuterResetControl;
   set => OuterResetControl = value;
}
public override AUpResetControl? Interop_OuterUpResetControl
{
   get => OuterUpResetControl;
   set => OuterUpResetControl = value;
}

public override bool Interop_Focus() =>
   Focus();
public override bool Interop_DeferredFocus() =>
   DeferredFocus();
public override bool Interop_Reset() =>
   ResetClicked();
public override bool Interop_UpReset() =>
   UpResetClicked();

//==================================================================================================

void OnSelectIndex_Invoke()
{
   ON_SelectIndex?.Invoke();
   ON_SelectIndex_A.InvokeAsync( null );
}
void OnChange_Invoke()
{
   ON_Change?.Invoke();
   ON_Change_A.InvokeAsync( null );
}
void OnKeyDown_Invoke(KeyboardEventArgs e)
{
   ON_KeyDown?.Invoke( e );
   ON_KeyDown_A.InvokeAsync( e );
}
void OnFocus_Invoke()
{
   ON_Focus?.Invoke();
   ON_Focus_A.InvokeAsync( null );
}
void OnBlur_Invoke()
{
   ON_Blur?.Invoke();
   ON_Blur_A.InvokeAsync( null );
}

//==================================================================================================

IReadOnlyList<object> _Items_ = new object[0];
int _SelectedIndex_ = -1;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // ADropDownList
//**************************************************************************************************

} // BookRegistry.Client.Shared
