@typeparam TItem
@if (Filter != null && Filter.Filters != null)
{
    <RadzenSelectBar @bind-Value=Filter.LogicalFilterOperator Change="@((LogicalFilterOperator args) => { InvokeAsync(ChangeState); InvokeAsync(ApplyFilter); })" Size="ButtonSize.Small">
        <Items>
            <RadzenSelectBarItem Text="@(DataFilter?.AndOperatorText ?? "")" Value="LogicalFilterOperator.And" title="@(DataFilter?.AndOperatorText ?? "")" />
            <RadzenSelectBarItem Text="@(DataFilter?.OrOperatorText ?? "")" Value="LogicalFilterOperator.Or" title="@(DataFilter?.OrOperatorText ?? "")" />
        </Items>
    </RadzenSelectBar>

    <RadzenButton title="@(DataFilter?.RemoveFilterText ?? "")" class="rz-datafilter-item-clear" Icon="clear" Click="@RemoveFilter" Variant="Variant.Text" Size="ButtonSize.Small" ButtonStyle="ButtonStyle.Dark"/>

    <ul class="rz-datafilter-group">
        @foreach(var filter in Filter.Filters)
        {
            <li class="rz-datafilter-item @(filter.Filters != null ? "rz-datafilter-group-item" : "")">
                <RadzenDataFilterItem DataFilter="@this.DataFilter" Filter="@filter" Parent=@this />
            </li>
        }
        <li class="rz-datafilter-item rz-datafilter-bar">
            <RadzenSplitButton Icon="add" Click="@(args => AddFilter(args?.Value == "group"))" Size="ButtonSize.Small" Variant="Variant.Flat" ButtonStyle="ButtonStyle.Primary" Shade="Shade.Lighter">
                <RadzenSplitButtonItem Icon="add" Text="@(DataFilter?.AddFilterText ?? "")" />
                <RadzenSplitButtonItem Icon="playlist_add" Value="group" Text="@(DataFilter?.AddFilterGroupText ?? "")" />
            </RadzenSplitButton>
        </li>
    </ul>
}
else if (Filter != null)
{
    <RadzenDropDown @bind-Value="@Filter.Property" Data="@(DataFilter?.properties)" TextProperty="Title" ValueProperty="Property" TValue="string"
                    DisabledProperty="@(DataFilter?.UniqueFilters == true ? nameof(property.IsSelected) : null)"
                    Change="@OnPropertyChange" AllowFiltering="@(DataFilter?.AllowColumnFiltering ?? false)" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" class="rz-datafilter-property">
        <Template>
            @{
                var property = (RadzenDataFilterProperty<TItem>)context;
            }
            @(property.Title ?? property.Property)
        </Template>
    </RadzenDropDown>
    if (property != null)
    {
        <RadzenDropDown @onclick:preventDefault="true" Data="@(property.GetFilterOperators().Select(t => new DropDownItem<FilterOperator> { Text = property.GetFilterOperatorText(t), Value = t }))"
                TextProperty="Text" ValueProperty="Value" TValue="FilterOperator?" @bind-Value="@Filter.FilterOperator" Change="@OnOperatorChange" class="rz-datafilter-operator" />
        @if (property.FilterTemplate != null)
        {
            <div class="rz-datafilter-editor" style="display:flex">
                @property.FilterTemplate(Filter)
            </div>
        }
        else if (PropertyAccess.IsNullableEnum(property.FilterPropertyType) || PropertyAccess.IsEnum(property.FilterPropertyType))
        {
            <RadzenDropDown Disabled="@IsOperatorNullOrEmpty()" AllowClear="false" AllowFiltering="false" TValue="@object" class="rz-datafilter-editor"
                            @bind-Value=@Filter.FilterValue Multiple="false" Placeholder="@(DataFilter?.EnumFilterSelectText ?? "")" TextProperty="Text" ValueProperty="Value"
                            Data=@EnumExtensions.EnumAsKeyValuePair(property.FilterPropertyType != null ? (Nullable.GetUnderlyingType(property.FilterPropertyType!)! ?? property.FilterPropertyType) : typeof(object)) Change=@(args => { _ = InvokeAsync(ApplyFilter); }) />
        }
        else if (property.FilterPropertyType != null && PropertyAccess.IsNumeric(property.FilterPropertyType))
        {
            @(DrawNumericFilter())
        }
        else if (PropertyAccess.IsDate(property.FilterPropertyType))
        {
            <RadzenDatePicker Disabled="@IsOperatorNullOrEmpty()" @bind-Value=@Filter.FilterValue TValue="@object" ShowTime="true" class="rz-datafilter-editor"
                              ShowTimeOkButton="true" DateFormat="@getFilterDateFormat()" Change=@(args => InvokeAsync(ApplyFilter)) />
        }
        else if (property.FilterPropertyType == typeof(bool) || property.FilterPropertyType == typeof(bool?))
        {
            <RadzenCheckBox Disabled="@IsOperatorNullOrEmpty()" TriState="true" TValue="@object" @bind-Value=@Filter.FilterValue Change=@(args => InvokeAsync(ApplyFilter)) class="rz-datafilter-check" />
        }
        else
        {
            <RadzenTextBox Disabled="@IsOperatorNullOrEmpty()" class="rz-datafilter-editor" Value="@($"{Filter.FilterValue}")" Change=@(args => { Filter.FilterValue = args; InvokeAsync(ApplyFilter); }) />
        }

        <RadzenButton title="@(DataFilter?.RemoveFilterText ?? "")" class="rz-datafilter-item-clear" Icon="clear" Click="@RemoveFilter" Variant="Variant.Text" Size="ButtonSize.Small" ButtonStyle="ButtonStyle.Dark"/>
    }
}
@code {
    [Parameter]
    public RadzenDataFilter<TItem>? DataFilter { get; set; }

    [Parameter]
    public RadzenDataFilterItem<TItem>? Parent { get; set; }

    CompositeFilterDescriptor? _filter;
    [Parameter]
    public CompositeFilterDescriptor? Filter
    {
        get
        {
            return _filter;
        }
        set
        {
            _filter = value;

            if (property == null && Filter != null && Filter.Filters == null)
            {
                if (Filter.Property != null)
                {
                    property = DataFilter?.properties.FirstOrDefault(f => object.Equals(f.Property, Filter.Property)); 
                }
                else if (property == null && DataFilter?.UniqueFilters == true)
                {
                    property = DataFilter?.properties.FirstOrDefault(f => f.IsSelected == false);
                }
                else
                {
                    property = DataFilter?.properties.FirstOrDefault();
                }

                if (property != null)
                {
                    property.FilterValueChange -= OnFilterValueChange;
                    property.FilterValueChange += OnFilterValueChange;

                    if (DataFilter?.UniqueFilters == true)
                    {
                        property.IsSelected = true;
                    }

                    Filter.Property = property.Property;
                    Filter.FilterProperty = property.FilterProperty;
                    Filter.Type = property.FilterPropertyType;

                    if (Filter.FilterOperator == null)
                    {
                        Filter.FilterOperator = property.GetFilterOperator();
                    }

                    else if (!property.GetFilterOperators().Contains(Filter.FilterOperator.Value))
                    {
                        Filter.FilterOperator = property.GetFilterOperators().FirstOrDefault();
                    }

                    var v = property.GetFilterValue();
                    if (v != null)
                    {
                        Filter.FilterValue = v;
                    }
                }
            }
        }
    }

    void OnFilterValueChange(object value)
    {
        if (property != null && Filter != null)
        {
            Filter.FilterValue = property.GetFilterValue();
        }
    }

    RadzenDataFilterProperty<TItem>? property;

    async Task ApplyFilter()
    {
        if (DataFilter?.Auto == true)
        {
            await DataFilter.Filter();
        }
    }

    async Task OnPropertyChange(object p)
    {
        if (Filter == null) 
        {
            await Task.CompletedTask;
            return;
        }

        property?.FilterValueChange -= OnFilterValueChange;
        if (property != null)
        {
            property.IsSelected = false;
        }

        property = DataFilter?.properties.Where(c => object.Equals(c.Property, p)).FirstOrDefault();

        if (property == null)
        {
            await ApplyFilter();
            return;
        }

        property.FilterValueChange += OnFilterValueChange;
        if (DataFilter?.UniqueFilters == true)
        {
            property.IsSelected = true;
        }
        Filter.FilterValue = null;

        Filter.Type = property.FilterPropertyType;

        var defaultOperator = typeof(System.Collections.IEnumerable).IsAssignableFrom(property.FilterPropertyType) ? FilterOperator.Contains : default(FilterOperator);

        if (property.GetFilterOperators().Any(o => o == property.FilterOperator))
        {
            Filter.FilterOperator = property.FilterOperator;
        }
        else if (property.GetFilterOperators().Contains(defaultOperator))
        {
            Filter.FilterOperator = defaultOperator;
        }
        else
        {
            Filter.FilterOperator = property.GetFilterOperators().FirstOrDefault();
        }

        await ApplyFilter();
    }

    bool IsOperatorNullOrEmpty()
    {
        if (Filter != null)
        {
            return Filter.FilterOperator == FilterOperator.IsEmpty ||  Filter.FilterOperator == FilterOperator.IsNotEmpty ||
                    Filter.FilterOperator == FilterOperator.IsNull || Filter.FilterOperator == FilterOperator.IsNotNull;
        }

        return false;
    }

    async Task OnOperatorChange(object p)
    {
        if (Filter == null)
        {
            await Task.CompletedTask;
            return;
        }

        if (IsOperatorNullOrEmpty())
        {
            Filter.FilterValue = null;
        }

        await ApplyFilter();
    }

    async Task AddFilter(bool isGroup)
    {
        if (Filter == null) 
        {
            await Task.CompletedTask;
            return;
        }

        if (DataFilter?.UniqueFilters == true && DataFilter.properties.All(f => f.IsSelected))
        {
            return;
        }
        if (isGroup)
        {
            Filter.Filters = (Filter.Filters ?? Enumerable.Empty<CompositeFilterDescriptor>()).Concat(new CompositeFilterDescriptor[]
                {
                    new CompositeFilterDescriptor()
                    {
                        Filters = Enumerable.Empty<CompositeFilterDescriptor>()
                    }
                }
            );
        }
        else
        {
            Filter.Filters = (Filter.Filters ?? Enumerable.Empty<CompositeFilterDescriptor>()).Concat(new CompositeFilterDescriptor[] { new CompositeFilterDescriptor() });
        }

        if (DataFilter?.Auto == true)
        {
            await DataFilter.Filter();
        }
    }

    async Task RemoveFilter()
    {
        if (property != null)
        {
            property.IsSelected = false;
        }
        property = null;

        if (Parent != null)
        {
            if (Parent.Filter != null)
            {
                Parent.Filter.Filters = Parent.Filter.Filters?.Where(f => f != Filter).ToList() ?? new List<CompositeFilterDescriptor>();
            }
            await Parent.ChangeState();
        }
        else
        {
            if (DataFilter != null)
            {
                DataFilter.Filters = DataFilter.Filters.Where(f => f != Filter).ToList();
                await DataFilter.ChangeState();
            }
        }

        await ApplyFilter();
    }

    internal async Task ChangeState()
    {
        await InvokeAsync(StateHasChanged);
    }

    RenderFragment? DrawNumericFilter()
    {
        if (property is null)
            return null;

        var filterPropertyType = property.FilterPropertyType;
        if (filterPropertyType is null)
            return null;

        // Ensure Nullable<T> so RadzenNumeric<> can bind nulls
        var type = Nullable.GetUnderlyingType(filterPropertyType) is not null
            ? filterPropertyType
            : typeof(Nullable<>).MakeGenericType(filterPropertyType);

        return builder =>
        {
            var numericType = typeof(RadzenNumeric<>).MakeGenericType(type);

            builder.OpenComponent(0, numericType);

            // Avoid nullable warnings: Filter might be null
            builder.AddAttribute(1, "Value", Filter?.FilterValue);
            builder.AddAttribute(2, "class", "rz-datafilter-editor");
            builder.AddAttribute(3, "Disabled", IsOperatorNullOrEmpty());

            Action<object?> onChanged = args =>
            {
                if (Filter is null)
                    return;

                Filter.FilterValue = args;
                _ = InvokeAsync(ApplyFilter);
            };

            var createMethod = typeof(NumericFilterEventCallback).GetMethod("Create");
            var actionMethod = typeof(NumericFilterEventCallback).GetMethod("Action");

            if (createMethod is not null && actionMethod is not null)
            {
                var genericCreate = createMethod.MakeGenericMethod(type);
                var genericAction = actionMethod.MakeGenericMethod(type);

                var typedActionObj = genericAction.Invoke(this, new object?[] { onChanged });
                if (typedActionObj is not null)
                {
                    var valueChangedObj = genericCreate.Invoke(this, new object?[] { this, typedActionObj });
                    if (valueChangedObj is not null)
                    {
                        builder.AddAttribute(4, "ValueChanged", valueChangedObj);
                    }
                }
            }

            builder.CloseComponent();
        };
    }

    internal class NumericFilterEventCallback
    {
        public static EventCallback<T> Create<T>(object receiver, Action<T> action)
        {
            return EventCallback.Factory.Create<T>(receiver, action);
        }

        public static Action<T> Action<T>(Action<object?> action)
        {
            return args => action(args);
        }
    }

    internal string getFilterDateFormat()
    {
        if (property != null && !string.IsNullOrEmpty(property.FormatString))
        {
            return property.FormatString.Replace("{0:", "").Replace("}", "");
        }

        return DataFilter?.FilterDateFormat ?? "";
    }
}