using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Radzen;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Radzen.Blazor
{
    /// <summary>
    /// RadzenPickList component.
    /// </summary>
    [UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2026, Justification = TrimMessages.DataTypePreserved)]
    public partial class RadzenPickList<
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] TItem> : RadzenComponent
    {
        /// <summary>
        /// Gets or sets the edit context.
        /// </summary>
        [CascadingParameter]
        public EditContext? EditContext { get; set; }

        /// <summary>
        /// Gets or sets the source expression used to create the FieldIdentifier for source validation.
        /// </summary>
        [Parameter]
        public Expression<Func<IEnumerable<TItem>?>>? SourceExpression { get; set; }

        /// <summary>
        /// Gets or sets the target expression used to create the FieldIdentifier for target validation.
        /// </summary>
        [Parameter]
        public Expression<Func<IEnumerable<TItem>?>>? TargetExpression { get; set; }

        FieldIdentifier? sourceFieldIdentifier;
        FieldIdentifier? targetFieldIdentifier;

        /// <summary>
        /// Gets or sets a value indicating whether it is allowed to move all items.
        /// </summary>
        /// <value><c>true</c> if c allowed to move all items; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool AllowMoveAll { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether it is allowed to move all items from source to target.
        /// </summary>
        /// <value><c>true</c> if c allowed to move all items from source to target; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool AllowMoveAllSourceToTarget { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether it is allowed to move all items from target to source.
        /// </summary>
        /// <value><c>true</c> if c allowed to move all items from target to source; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool AllowMoveAllTargetToSource { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether to move all or only avaialable after filter items.
        /// </summary>
        /// <value><c>true</c> if c allowed to move filtered items only; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool MoveFilteredItemsOnlyOnMoveAll { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether multiple selection is allowed.
        /// </summary>
        /// <value><c>true</c> if multiple selection is allowed; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Multiple { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether selecting all items is allowed.
        /// </summary>
        /// <value><c>true</c> if selecting all items is allowed; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool AllowSelectAll { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether virtualization is enabled for the source and target listboxes.
        /// </summary>
        /// <value><c>true</c> if virtualization is enabled; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool AllowVirtualization { get; set; }
        /// <summary>
        /// Gets or sets a value indicating whether component is disabled.
        /// </summary>
        /// <value><c>true</c> if component is disabled; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Disabled { get; set; }

        /// <summary>
        /// Gets or sets the source header
        /// </summary>
        /// <value>The source header.</value>
        [Parameter]
        public RenderFragment? SourceHeader { get; set; }

        /// <summary>
        /// Gets or sets the target header
        /// </summary>
        /// <value>The target header.</value>
        [Parameter]
        public RenderFragment? TargetHeader { get; set; }

        /// <summary>
        /// Gets or sets the common placeholder
        /// </summary>
        /// <value>The common placeholder.</value>
        [Parameter]
        public string? Placeholder { get; set; }

        /// <summary>
        /// Gets or sets the source placeholder
        /// </summary>
        /// <value>The source placeholder.</value>
        [Parameter]
        public string? SourcePlaceholder { get; set; }

        /// <summary>
        /// Gets or sets the target placeholder
        /// </summary>
        /// <value>The target placeholder.</value>
        [Parameter]
        public string? TargetPlaceholder { get; set; }

        /// <summary>
        /// Gets or sets the text property
        /// </summary>
        /// <value>The text property.</value>
        [Parameter]
        public string? TextProperty { get; set; }

        /// <summary>
        /// Gets or sets the value property
        /// </summary>
        /// <value>The value property.</value>
        [Parameter]
        public string? ValueProperty { get; set; }

        /// <summary>
        /// Gets or sets the disabled property
        /// </summary>
        /// <value>The disabled property.</value>
        [Parameter]
        public string? DisabledProperty { get; set; }

        /// <summary>
        /// Gets or sets the template.
        /// </summary>
        /// <value>The template.</value>
        [Parameter]
        public RenderFragment<TItem>? Template { get; set; }
        
        /// <summary>
        /// Gets or sets the source template. Overrides <see cref="Template"/>.
        /// </summary>
        /// <value>The source template.</value>
        [Parameter]
        public RenderFragment<TItem>? SourceTemplate { get; set; }
        
        /// <summary>
        /// Gets or sets the target template. Overrides <see cref="Template"/>.
        /// </summary>
        /// <value>The target template.</value>
        [Parameter]
        public RenderFragment<TItem>? TargetTemplate { get; set; }

        /// <summary>
        /// Gets or sets the select all text.
        /// </summary>
        /// <value>The select all text.</value>
        [Parameter]
        public string? SelectAllText { get; set; }

        private string? emptyText;

        /// <summary>
        /// Gets or sets the empty text shown when a list has no items.
        /// </summary>
        /// <value>The empty text.</value>
        [Parameter]
        public string EmptyText { get => emptyText ?? Localize(nameof(RadzenStrings.PickList_EmptyText)); set => emptyText = value; }

        /// <summary>
        /// Gets or sets the empty text shown when the source list has no items. Overrides <see cref="EmptyText"/>.
        /// </summary>
        /// <value>The source empty text.</value>
        [Parameter]
        public string? SourceEmptyText { get; set; }

        /// <summary>
        /// Gets or sets the empty text shown when the target list has no items. Overrides <see cref="EmptyText"/>.
        /// </summary>
        /// <value>The target empty text.</value>
        [Parameter]
        public string? TargetEmptyText { get; set; }

        /// <summary>
        /// Gets or sets the empty template shown when a list has no items.
        /// </summary>
        /// <value>The empty template.</value>
        [Parameter]
        public RenderFragment? EmptyTemplate { get; set; }

        /// <summary>
        /// Gets or sets the empty template shown when the source list has no items. Overrides <see cref="EmptyTemplate"/>.
        /// </summary>
        /// <value>The source empty template.</value>
        [Parameter]
        public RenderFragment? SourceEmptyTemplate { get; set; }

        /// <summary>
        /// Gets or sets the empty template shown when the target list has no items. Overrides <see cref="EmptyTemplate"/>.
        /// </summary>
        /// <value>The target empty template.</value>
        [Parameter]
        public RenderFragment? TargetEmptyTemplate { get; set; }

        /// <summary>
        /// Gets or sets the row render callback. Use it to set row attributes.
        /// </summary>
        /// <value>The row render callback.</value>
        [Parameter]
        public Action<PickListItemRenderEventArgs<TItem>>? ItemRender { get; set; }

        void OnSourceItemRender(ListBoxItemRenderEventArgs<object?> args)
        {
            if (ItemRender != null)
            {
                var newArgs = new PickListItemRenderEventArgs<TItem>();
                newArgs.Item = args.Item != null ? (TItem)args.Item : default(TItem);
                ItemRender(newArgs);
                newArgs.Attributes.ToList().ForEach(k => args.Attributes.Add(k.Key, k.Value));
                args.Visible = newArgs.Visible;
                args.Disabled = newArgs.Disabled;
            }
        }

        void OnTargetItemRender(ListBoxItemRenderEventArgs<object?> args)
        {
            if (ItemRender != null)
            {
                var newArgs = new PickListItemRenderEventArgs<TItem>();
                newArgs.Item = args.Item != null ? (TItem)args.Item : default(TItem);
                ItemRender(newArgs);
                newArgs.Attributes.ToList().ForEach(k => args.Attributes.Add(k.Key, k.Value));
                args.Visible = newArgs.Visible;
                args.Disabled = newArgs.Disabled;
            }
        }

        private RenderFragment<dynamic>? SourceListBoxTemplate
        {
            get
            {
                if (SourceTemplate != null)
                {
                    return item => SourceTemplate(item);
                }

                if (Template != null)
                {
                    return item => Template(item);
                }

                return null;
            }
        }

        private RenderFragment<dynamic>? TargetListBoxTemplate
        {
            get
            {
                if (TargetTemplate != null)
                {
                    return item => TargetTemplate(item);
                }

                if (Template != null)
                {
                    return item => Template(item);
                }

                return null;
            }
        }

        /// <summary>
        /// Gets or sets value if filtering is allowed.
        /// </summary>
        /// <value>The allow filtering value.</value>
        [Parameter]
        public bool AllowFiltering { get; set; }

        /// <summary>
        /// Gets or sets value if headers are shown.
        /// </summary>
        /// <value>If headers are shown value.</value>
        [Parameter]
        public bool ShowHeader { get; set; } = true;

        /// <summary>
        /// Gets or sets the buttons spacing
        /// </summary>
        /// <value>The buttons spacing.</value>
        [Parameter]
        public string? ButtonGap { get; set; }

        /// <summary>
        /// Gets or sets the orientation
        /// </summary>
        /// <value>The orientation.</value>
        [Parameter]
        public Orientation Orientation { get; set; } = Orientation.Horizontal;

        /// <summary>
        /// Gets or sets the buttons style
        /// </summary>
        /// <value>The buttons style.</value>
        [Parameter]
        public JustifyContent ButtonJustifyContent { get; set; } = JustifyContent.Center;

        /// <summary>
        /// Gets or sets the buttons style
        /// </summary>
        /// <value>The buttons style.</value>
        [Parameter]
        public ButtonStyle ButtonStyle { get; set; } = ButtonStyle.Primary;

        /// <summary>
        /// Gets or sets the design variant of the buttons.
        /// </summary>
        /// <value>The variant of the buttons.</value>
        [Parameter]
        public Variant ButtonVariant { get; set; } = Variant.Text;

        /// <summary>
        /// Gets or sets the color shade of the buttons.
        /// </summary>
        /// <value>The color shade of the buttons.</value>
        [Parameter]
        public Shade ButtonShade { get; set; } = Shade.Default;

        /// <summary>
        /// Gets or sets the buttons size.
        /// </summary>
        /// <value>The buttons size.</value>
        [Parameter]
        public ButtonSize ButtonSize { get; set; } = ButtonSize.Medium;

        private string? sourceToTargetTitle;

        /// <summary>
        /// Gets or sets the source to target title
        /// </summary>
        /// <value>The source to target title.</value>
        [Parameter]
        public string SourceToTargetTitle { get => sourceToTargetTitle ?? Localize(nameof(RadzenStrings.PickList_SourceToTargetTitle)); set => sourceToTargetTitle = value; }

        private string? selectedSourceToTargetTitle;

        /// <summary>
        /// Gets or sets the selected source to target title
        /// </summary>
        /// <value>The selected source to target title.</value>
        [Parameter]
        public string SelectedSourceToTargetTitle { get => selectedSourceToTargetTitle ?? Localize(nameof(RadzenStrings.PickList_SelectedSourceToTargetTitle)); set => selectedSourceToTargetTitle = value; }

        private string? targetToSourceTitle;

        /// <summary>
        /// Gets or sets the target to source title
        /// </summary>
        /// <value>The target to source title.</value>
        [Parameter]
        public string TargetToSourceTitle { get => targetToSourceTitle ?? Localize(nameof(RadzenStrings.PickList_TargetToSourceTitle)); set => targetToSourceTitle = value; }

        private string? selectedTargetToSourceTitle;

        /// <summary>
        /// Gets or sets the selected target to source  title
        /// </summary>
        /// <value>The selected target to source title.</value>
        [Parameter]
        public string SelectedTargetToSourceTitle { get => selectedTargetToSourceTitle ?? Localize(nameof(RadzenStrings.PickList_SelectedTargetToSourceTitle)); set => selectedTargetToSourceTitle = value; }

        /// <summary>
        /// Gets or sets the source to target icon
        /// </summary>
        /// <value>The source to target icon.</value>
        [Parameter]
        public string SourceToTargetIcon { get; set; } = "keyboard_double_arrow_right";

        /// <summary>
        /// Gets or sets the selected source to target icon
        /// </summary>
        /// <value>The selected source to target icon.</value>
        [Parameter]
        public string SelectedSourceToTargetIcon { get; set; } = "keyboard_arrow_right";

        /// <summary>
        /// Gets or sets the target to source icon
        /// </summary>
        /// <value>The target to source icon.</value>
        [Parameter]
        public string TargetToSourceIcon { get; set; } = "keyboard_double_arrow_left";

        /// <summary>
        /// Gets or sets the selected target to source  icon
        /// </summary>
        /// <value>The selected target to source icon.</value>
        [Parameter]
        public string SelectedTargetToSourceIcon { get; set; } = "keyboard_arrow_left";

        /// <summary>
        /// Gets the final CSS style rendered by the component. Combines it with a <c>style</c> custom attribute.
        /// </summary>
        protected string? GetStyle()
        {
            if (Attributes != null && Attributes.TryGetValue("style", out var style) && !string.IsNullOrEmpty(Convert.ToString(@style, CultureInfo.InvariantCulture)))
            {
                return $"{Style} {@style}";
            }

            return Style;
        }

        /// <inheritdoc />
        protected override string GetComponentCssClass()
        {
            return "rz-picklist";
        }

        /// <summary>
        /// Gets or sets the source collection.
        /// </summary>
        /// <value>The source collection.</value>
        [Parameter]
        public IEnumerable<TItem>? Source { get; set; }

        IEnumerable<TItem>? source;

        /// <summary>
        /// Gets or sets the source changed.
        /// </summary>
        /// <value>The source changed.</value>
        [Parameter]
        public EventCallback<IEnumerable<TItem>> SourceChanged { get; set; }

        /// <summary>
        /// Gets or sets the target collection.
        /// </summary>
        /// <value>The target collection.</value>
        [Parameter]
        public IEnumerable<TItem>? Target { get; set; }

        IEnumerable<TItem>? target;

        /// <summary>
        /// Gets or sets the target changed.
        /// </summary>
        /// <value>The target changed.</value>
        [Parameter]
        public EventCallback<IEnumerable<TItem>> TargetChanged { get; set; }

        /// <summary>
        /// Gets or sets the callback that is invoked when the selected source items change.
        /// </summary>
        /// <value>The selected source items changed callback.</value>
        [Parameter]
        public EventCallback<IEnumerable<TItem>> SelectedSourceChanged { get; set; }

        /// <summary>
        /// Gets or sets the callback that is invoked when the selected target items change.
        /// </summary>
        /// <value>The selected target items changed callback.</value>
        [Parameter]
        public EventCallback<IEnumerable<TItem>> SelectedTargetChanged { get; set; }

        /// <summary>
        /// Gets or sets the callback that is invoked when items are moved between the source and target collections.
        /// Fires after <see cref="SourceChanged"/> and <see cref="TargetChanged"/>, so the bound collections already reflect the move.
        /// </summary>
        /// <value>The move callback.</value>
        [Parameter]
        public EventCallback<PickListMoveEventArgs<TItem>> Move { get; set; }

        object? selectedSourceItems;
        object? selectedTargetItems;

        async Task OnSelectedSourceItemsChanged(object value)
        {
            selectedSourceItems = value;
            await SelectedSourceChanged.InvokeAsync(GetSelectedSources());
        }

        async Task OnSelectedTargetItemsChanged(object value)
        {
            selectedTargetItems = value;
            await SelectedTargetChanged.InvokeAsync(GetSelectedTargets());
        }

        string? sourceSearchText;
        string? targetSearchText;

        /// <summary>
        /// Set parameters as an asynchronous operation.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <returns>A Task representing the asynchronous operation.</returns>
        public override async Task SetParametersAsync(ParameterView parameters)
        {
            var sourceChanged = parameters.DidParameterChange(nameof(Source), Source);
            if (sourceChanged)
            {
                source = parameters.GetValueOrDefault<IEnumerable<TItem>>(nameof(Source));
            }

            var targetChanged = parameters.DidParameterChange(nameof(Target), Target);
            if (targetChanged)
            {
                target = parameters.GetValueOrDefault<IEnumerable<TItem>>(nameof(Target));
            }

            if (parameters.DidParameterChange(nameof(Multiple), Multiple))
            {
                selectedSourceItems = null;
                selectedTargetItems = null;
            }

            await base.SetParametersAsync(parameters);

            if (EditContext != null)
            {
                if (SourceExpression != null && sourceFieldIdentifier == null)
                {
                    sourceFieldIdentifier = FieldIdentifier.Create(SourceExpression);
                }

                if (TargetExpression != null && targetFieldIdentifier == null)
                {
                    targetFieldIdentifier = FieldIdentifier.Create(TargetExpression);
                }
            }
        }

        /// <summary>
        /// Returns a collection of TItem that are selected in the source list.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<TItem> GetSelectedSources() => GetSelectedItems(selectedSourceItems, source);

        /// <summary>
        /// Returns a collection of TItem that are selected in the target list.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<TItem> GetSelectedTargets() => GetSelectedItems(selectedTargetItems, target);

        IEnumerable<TItem> GetSelectedItems(object? selectedItems, IEnumerable<TItem>? data)
        {
            if (selectedItems == null)
            {
                return Enumerable.Empty<TItem>();
            }

            var selected = Multiple
                ? (selectedItems as IEnumerable)?.Cast<object?>().ToList() ?? new List<object?>()
                : new List<object?> { selectedItems };

            if (selected.Count > 0 && selected.All(o => o is TItem))
            {
                return selected.Cast<TItem>();
            }

            if (!string.IsNullOrEmpty(ValueProperty) && data != null)
            {
                var selectedValues = new HashSet<object?>(selected);
                return data.Where(item => selectedValues.Contains(PropertyAccess.GetValue(item, ValueProperty)));
            }

            return Enumerable.Empty<TItem>();
        }

        RadzenListBox<object>? sourceListBox;
        RadzenListBox<object>? targetListBox;
        private async Task Update(bool sourceToTarget, IEnumerable<TItem>? items)
        {
            IEnumerable<TItem> movedItems;

            if (sourceToTarget)
            {
                if (items != null)
                {
                    movedItems = items as IList<TItem> ?? items.ToList();
                    target = (target ?? Enumerable.Empty<TItem>()).Concat(movedItems);
                    source = (source ?? Enumerable.Empty<TItem>()).Except(movedItems);
                }
                else
                {
                    movedItems = (MoveFilteredItemsOnlyOnMoveAll ? sourceListBox?.GetView()?.Cast<TItem>() ?? Enumerable.Empty<TItem>() : source ?? Enumerable.Empty<TItem>()).ToList();
                    target = (target ?? Enumerable.Empty<TItem>()).Concat(movedItems);
                    source = MoveFilteredItemsOnlyOnMoveAll && source != null ? source.Except(movedItems) : null;
                }
            }
            else
            {
                if (items != null)
                {
                    movedItems = items as IList<TItem> ?? items.ToList();
                    source = (source ?? Enumerable.Empty<TItem>()).Concat(movedItems);
                    target = (target ?? Enumerable.Empty<TItem>()).Except(movedItems);
                }
                else
                {
                    movedItems = (MoveFilteredItemsOnlyOnMoveAll ? targetListBox?.GetView()?.Cast<TItem>() ?? Enumerable.Empty<TItem>() : target ?? Enumerable.Empty<TItem>()).ToList();
                    source = (source ?? Enumerable.Empty<TItem>()).Concat(movedItems);
                    target = MoveFilteredItemsOnlyOnMoveAll && target != null ? target.Except(movedItems) : null;
                }
            }

            source = source?.Any() == true ? source : null;
            target = target?.Any() == true ? target : null;

            await SourceChanged.InvokeAsync(source);
            await TargetChanged.InvokeAsync(target);

            if (Move.HasDelegate)
            {
                await Move.InvokeAsync(new PickListMoveEventArgs<TItem>
                {
                    MoveDirectionToTarget = sourceToTarget,
                    Items = movedItems,
                });
            }

            if (EditContext != null)
            {
                if (sourceFieldIdentifier.HasValue)
                {
                    EditContext.NotifyFieldChanged(sourceFieldIdentifier.Value);
                }

                if (targetFieldIdentifier.HasValue)
                {
                    EditContext.NotifyFieldChanged(targetFieldIdentifier.Value);
                }
            }

            selectedSourceItems = null;
            selectedTargetItems = null;

            if (items == null && !MoveFilteredItemsOnlyOnMoveAll)
            {
                sourceSearchText = null;
                targetSearchText = null;
            }

            StateHasChanged();
        }

        private async Task SourceToTarget() => await Update(true, null);

        private async Task SelectedSourceToTarget() => await Update(true, GetSelectedSources());

        private async Task TargetToSource() => await Update(false, null);

        private async Task SelectedTargetToSource() => await Update(false, GetSelectedTargets());
    }
}
