using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Radzen.Blazor
{
    /// <summary>
    /// Defines a column in a RadzenDataGrid, specifying how data is displayed, sorted, filtered, and edited.
    /// RadzenDataGridColumn supports data binding via Property, custom templates, formatting, sorting, filtering, grouping, aggregation, and inline editing.
    /// Must be placed inside a <see cref="RadzenDataGrid{TItem}"/> within the Columns template.
    /// Each column represents one field or computed value from the data source.
    /// Features data binding using Property to bind to a data field or Template for custom content, display properties (Title, FormatString, TextAlign, Width, Frozen for locked scrolling),
    /// Sortable property enabling/disabling sorting, Filterable property with FilterTemplate for custom filter UI and FilterValue for programmatic filtering,
    /// EditTemplate for inline editing with Editable property to control edit permission, GroupProperty for hierarchical data grouping,
    /// FooterTemplate with Sum()/Average()/Count() aggregation functions, child columns for multi-level headers,
    /// and Visible property with responsive breakpoint properties (visible-sm, visible-md, etc.).
    /// Use Template for complete control over cell rendering, or EditTemplate for edit mode cells.
    /// </summary>
    /// <typeparam name="TItem">The type of data items in the parent DataGrid. Must match the grid's TItem type.</typeparam>
    /// <example>
    /// Simple data-bound column:
    /// <code>
    /// &lt;RadzenDataGridColumn TItem="Order" Property="OrderId" Title="Order ID" /&gt;
    /// </code>
    /// Formatted column with custom width:
    /// <code>
    /// &lt;RadzenDataGridColumn TItem="Order" Property="OrderDate" Title="Date" FormatString="{0:d}" Width="120px" /&gt;
    /// </code>
    /// Column with custom template:
    /// <code>
    /// &lt;RadzenDataGridColumn TItem="Order" Title="Actions" Sortable="false" Filterable="false"&gt;
    ///     &lt;Template Context="order"&gt;
    ///         &lt;RadzenButton Icon="edit" Click="@(args =&gt; EditOrder(order))" /&gt;
    ///         &lt;RadzenButton Icon="delete" Click="@(args =&gt; DeleteOrder(order))" /&gt;
    ///     &lt;/Template&gt;
    /// &lt;/RadzenDataGridColumn&gt;
    /// </code>
    /// </example>
    [UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2026, Justification = TrimMessages.DataTypePreserved)]
    [UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2087, Justification = TrimMessages.DataTypePreserved)]
    [UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2090, Justification = TrimMessages.DataTypePreserved)]
    public partial class RadzenDataGridColumn<
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] TItem> : ComponentBase, IDisposable where TItem : notnull
    {
        /// <summary>
        /// Gets or sets the parent RadzenDataGrid that contains this column.
        /// This is set automatically via cascading parameter and provides access to grid functionality.
        /// </summary>
        /// <value>The parent DataGrid component.</value>
        [CascadingParameter]
        public RadzenDataGrid<TItem> Grid { get; set; } = default!;

        /// <summary>
        /// Gets or sets the child columns render fragment for creating composite/hierarchical column headers.
        /// When set, this column becomes a header grouping column, and child columns are rendered beneath it.
        /// Useful for creating multi-level column headers where related columns are grouped under a common header.
        /// </summary>
        /// <value>The render fragment containing child column definitions.</value>
        [Parameter]
        public RenderFragment? Columns { get; set; }

        /// <summary>
        /// Gets or sets the parent column when this column is nested within a composite column structure.
        /// This is set automatically via cascading parameter for child columns in hierarchical headers.
        /// </summary>
        /// <value>The parent column, or null if this is a top-level column.</value>
        [CascadingParameter]
        public RadzenDataGridColumn<TItem>? Parent { get; set; }

        /// <summary>
        /// Gets or sets whether cell values should automatically display as tooltips on hover.
        /// Useful for columns that may contain truncated text, allowing users to see the full value.
        /// </summary>
        /// <value><c>true</c> to show cell data as tooltips; otherwise, <c>false</c>. Default is <c>false</c>.</value>
        [Parameter]
        public bool ShowCellDataAsTooltip { get; set; }

        /// <summary>
        /// Gets or sets whether virtualization is enabled for the CheckBoxList filter dropdown.
        /// When enabled, only visible checkbox items are rendered for better performance with large datasets.
        /// Only applicable when using FilterMode.CheckBoxList.
        /// </summary>
        /// <value><c>true</c> to enable filter list virtualization; otherwise, <c>false</c>. Default is <c>true</c>.</value>
        [Parameter]
        public bool AllowCheckBoxListVirtualization { get; set; } = true;

        /// <summary>
        /// Gets or sets whether the CheckBoxList filter should display all distinct values regardless of filters applied to other columns.
        /// When false (default), the filter list is dynamically filtered based on other active column filters.
        /// Set to true to always show the complete list of distinct values from the original dataset.
        /// </summary>
        /// <value><c>true</c> to show all data in filter list; <c>false</c> to filter the list based on other active filters. Default is <c>false</c>.</value>
        [Parameter]
        public bool AlwaysShowAllCheckBoxListData { get; set; } = false;

        /// <summary>
        /// Gets or sets the filtering UI mode for this column.
        /// Controls whether the column uses simple filter controls (textbox in header), advanced filter menu, CheckBoxList, or no filtering.
        /// If not set, inherits from the parent grid's FilterMode setting.
        /// </summary>
        /// <value>The filter mode, or null to use the grid's default FilterMode. Default is null.</value>
        [Parameter]
        public FilterMode? FilterMode { get; set; }

        internal void RemoveColumn(RadzenDataGridColumn<TItem> column)
        {
            if (Grid.childColumns.Remove(column))
            {
                if (!Grid.disposed)
                {
                    try { InvokeAsync(StateHasChanged); } catch { }
                }
            }
        }

        /// <summary>
        /// Gets the child columns.
        /// </summary>
        /// <value>The child columns.</value>
        public IList<RadzenDataGridColumn<TItem>> ColumnsCollection
        {
            get
            {
                return Grid.childColumns.Where(c => c.Parent == this).ToList();
            }
        }

        internal IEnumerable<RadzenDataGridColumn<TItem>> VisibleColumns => ColumnsCollection.Where(c => c.GetVisible());

        internal int GetLevel()
        {
            int i = 0;
            var p = Parent;
            while (p != null)
            {
                p = p.Parent;
                i++;
            }

            return i;
        }

        internal int GetColSpan(bool isDataCell = false)
        {
            if (!Grid.AllowCompositeDataCells && isDataCell || Columns == null)
            {
                return 1;
            }

            var span = VisibleColumns.Concat(VisibleColumns.SelectManyRecursive(c => c.VisibleColumns)).Sum(c => c.VisibleColumns.Count())
                - VisibleColumns.SelectManyRecursive(c => c.VisibleColumns).Count(c => c.VisibleColumns.Any())
                + VisibleColumns.Where(c => c.VisibleColumns.Count() == 0).Count();

            return span != 0 ? span : VisibleColumns.Count();
        }

        internal int GetRowSpan(bool isDataCell = false)
        {
            if (!Grid.AllowCompositeDataCells && isDataCell)
            {
                return 1;
            }

            if (Columns == null && Parent != null)
            {
                var level = this.GetLevel();
                return level == Grid.deepestChildColumnLevel ? 1 : level < Grid.deepestChildColumnLevel ? Grid.deepestChildColumnLevel : level + 1;
            }

            return Columns == null && Parent == null ? Grid.deepestChildColumnLevel + 1 : 1;
        }

        Type? _propertyType;
        internal Type? PropertyType => _propertyType;

        /// <summary>
        /// Gets or sets the unique identifier.
        /// </summary>
        /// <value>The unique identifier.</value>
        [Parameter]
        public string UniqueID { get; set; } = string.Empty;

        /// <summary>
        /// Called when initialized.
        /// </summary>
        protected override void OnInitialized()
        {
            if (Grid != null)
            {
                Grid.AddColumn(this);
                SetColumnDefaults();
            }
        }

        private void SetColumnDefaults()
        {
            var canSetFilterPropertyType = (FilterMode ?? Grid.FilterMode) == Radzen.FilterMode.CheckBoxList && FilterTemplate == null;

            if (canSetFilterPropertyType)
            {
                if (Type == null)
                {
                    var fp = GetFilterProperty();
                    var pt = !string.IsNullOrEmpty(fp) ?
                            PropertyAccess.GetPropertyType(typeof(TItem), fp) : typeof(object);

                    _filterPropertyType = typeof(IEnumerable<>).MakeGenericType(pt!);
                }

                if (GetFilterOperator() == FilterOperator.Equals)
                {
                    SetFilterOperator(FilterOperator.Contains);
                }

                Grid.FilterPopupRenderMode = PopupRenderMode.OnDemand;
            }

            var property = GetFilterProperty();

            if (!string.IsNullOrEmpty(property) && Type == null)
            {
                _propertyType = PropertyAccess.GetPropertyType(typeof(TItem), property);
            }

            if (!string.IsNullOrEmpty(property) && Type == null && !canSetFilterPropertyType)
            {
                _filterPropertyType = _propertyType;
            }

            if (_filterPropertyType == null)
            {
                _filterPropertyType = Type;
            }
            else if (!string.IsNullOrEmpty(Property))
            {
                propertyValueGetter = PropertyAccess.Getter<TItem, object>(Property);
            }

            if (!string.IsNullOrEmpty(Property) && (typeof(TItem).IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(typeof(TItem).GetGenericTypeDefinition()) ||
                typeof(IDictionary).IsAssignableFrom(typeof(TItem)) || typeof(System.Data.DataRow).IsAssignableFrom(typeof(TItem))))
            {
                propertyValueGetter = PropertyAccess.Getter<TItem, object>(Property);
            }

            if (_filterPropertyType == typeof(string) && filterOperator != FilterOperator.Custom && filterOperator == null && _filterOperator == null)
            {
                SetFilterOperator(FilterOperator.Contains);
            }

            if (!string.IsNullOrEmpty(Property) && !string.IsNullOrEmpty(FilterProperty))
            {
                UniqueID = $"{Property}.{FilterProperty}"; // To be sure the column uniqueID is unique even when filtering on sub property.
            }
            else
            {
                UniqueID = !string.IsNullOrEmpty(Property) ? Property : FilterProperty;
            }

            if (UseDisplayName && !string.IsNullOrEmpty(Property))
            {
                var propInfo = typeof(TItem).GetProperty(Property);
                if (propInfo != null)
                {
                    var displayAttr = propInfo.GetCustomAttributes(typeof(DisplayAttribute), true)
                        .FirstOrDefault() as DisplayAttribute;
                    if (displayAttr?.Name != null)
                    {
                        Title = displayAttr.Name;
                    }
                }
            }
        }
        
        int? orderIndex;

        /// <summary>
        /// Gets or sets the order index.
        /// </summary>
        /// <value>The order index.</value>
        [Parameter]
        public int? OrderIndex { get; set; }

        /// <summary>
        /// Gets the order index.
        /// </summary>
        public int? GetOrderIndex()
        {
            return orderIndex ?? OrderIndex;
        }

        internal void SetOrderIndex(int? value)
        {
            orderIndex = value;
        }

        /// <summary>
        /// Gets or sets the sort order.
        /// </summary>
        /// <value>The sort order.</value>
        [Parameter]
        public SortOrder? SortOrder { get; set; }

        /// <summary>
        /// Gets or sets the sequence of the <see cref="Radzen.SortOrder"/>s to use when sorting the column interactively.
        /// </summary>
        [Parameter]
        public SortOrder?[] SortOrderSequence { get; set; } = [Radzen.SortOrder.Ascending, Radzen.SortOrder.Descending, null];

        bool visible = true;
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is visible.
        /// </summary>
        /// <value><c>true</c> if visible; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Visible
        {
            get
            {
                return visible;
            }
            set
            {
                if (visible != value)
                {
                    visible = value;
                    _visible = visible;
                    if (Grid != null)
                    {
                        Grid.UpdatePickableColumn(this, visible);
                        InvokeAsync(Grid.ChangeState);
                    }
                }
            }
        }

        bool? _visible;

        /// <summary>
        /// Gets if the column is visible or not.
        /// </summary>
        /// <returns>System.Boolean.</returns>
        public bool GetVisible()
        {
            if (Columns != null && ColumnsCollection.Any() && VisibleColumns.Count() == 0)
            {
                return false;
            }

            return _visible ?? Visible;
        }

        internal void SetVisible(bool? value)
        {
            _visible = value;

            if (Grid != null && Pickable)
            {
                Grid.UpdatePickableColumn(this, _visible == true);
            }
        }

        /// <summary>
        /// Gets or sets the header tooltip.
        /// </summary>
        /// <value>The header tooltip.</value>
        [Parameter]
        public string HeaderTooltip { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the title.
        /// </summary>
        /// <value>The title.</value>
        [Parameter]
        public string Title { get; set; } = string.Empty;

        /// <summary>
        /// Indicates whether the column should automatically use the <see cref="DisplayAttribute.Name"/>
        /// of the bound property as the header.
        /// </summary>
        [Parameter]
        public bool UseDisplayName { get; set; }

        string? _title;

        /// <summary>
        /// Gets the column title.
        /// </summary>
        /// <returns>System.String.</returns>
        public string GetTitle()
        {
            return _title ?? Title;
        }

        /// <summary>
        /// Sets the column title.
        /// </summary>
        public void SetTitle(string value)
        {
            _title = value;
        }

        /// <summary>
        /// Gets or sets the title in column picker.
        /// Value of Title is used when ColumnPickerTitle is not set
        /// </summary>
        /// <value>The column picker title.</value>
        [Parameter]
        public string ColumnPickerTitle
        {
            get => _columnPickerTitle ?? Title ?? string.Empty;
            set => _columnPickerTitle = value;
        }

        string? _columnPickerTitle;

        /// <summary>
        /// Gets or sets the property name.
        /// </summary>
        /// <value>The property name.</value>
        [Parameter]
        public string Property { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the sort property name.
        /// </summary>
        /// <value>The sort property name.</value>
        [Parameter]
        public string SortProperty { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets a custom comparer used to order this column when the DataGrid sorts an
        /// in-memory data source. When set, the grid orders rows by this column's sort value
        /// (the value at the sort property) using the supplied comparer instead of the default
        /// member-path ordering — for example, sorting id values by their mapped display text.
        /// It does not apply to server-paged data (LoadData or OData), which sorts on the server,
        /// or to self-referencing (tree) data. Binding a queryable provider (such as Entity
        /// Framework) directly to Data together with a comparer evaluates the comparer in memory.
        /// Note that when a column with a SortComparer takes part in a multi-column sort, the whole
        /// sort is performed in memory — every column's sort key is evaluated in memory, not just
        /// this column's.
        /// </summary>
        [Parameter]
        public IComparer? SortComparer { get; set; }

        /// <summary>
        /// Gets or sets the group property name.
        /// </summary>
        /// <value>The group property name.</value>
        [Parameter]
        public string GroupProperty { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the filter property name.
        /// </summary>
        /// <value>The filter property name.</value>
        [Parameter]
        public string FilterProperty { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the data source for the CheckBoxList filter's options. When set, the built-in
        /// CheckBoxList filter shows these options — using <see cref="FilterLookupTextProperty"/> for the
        /// label (display and in-dropdown search) and <see cref="FilterLookupValueProperty"/> for the
        /// value — instead of deriving distinct values from the grid data. Selected values filter the
        /// column's filter property, so the column's Property should be the underlying value (e.g. an id).
        /// Applies to FilterMode.CheckBoxList. Mutually exclusive with the grid's LoadColumnFilterData
        /// event per column: when FilterLookupData is set it supplies the options and LoadColumnFilterData
        /// is not used for this column.
        /// </summary>
        [Parameter]
        public IEnumerable? FilterLookupData { get; set; }

        /// <summary>
        /// Gets or sets the property of <see cref="FilterLookupData"/> items used as the option label, shown
        /// in the CheckBoxList filter and used for its search. Ignored when <see cref="FilterLookupData"/> is not set.
        /// </summary>
        [Parameter]
        public string? FilterLookupTextProperty { get; set; }

        /// <summary>
        /// Gets or sets the property of <see cref="FilterLookupData"/> items used as the option value — the
        /// value applied to the column filter when an option is selected. Ignored when
        /// <see cref="FilterLookupData"/> is not set.
        /// </summary>
        [Parameter]
        public string? FilterLookupValueProperty { get; set; }

        /// <summary>
        /// Gets or sets whether the CheckBoxList filter's search box is shown when
        /// <see cref="FilterLookupData"/> is set. Default is true. Ignored when FilterLookupData is not set.
        /// </summary>
        [Parameter]
        public bool FilterLookupAllowFiltering { get; set; } = true;

        /// <summary>
        /// Gets or sets the filter value.
        /// </summary>
        /// <value>The filter value.</value>
        [Parameter]
        public object? FilterValue { get; set; }

        /// <summary>
        /// Gets or sets the filter placeholder.
        /// </summary>
        /// <value>The filter placeholder value.</value>
        [Parameter]
        public string FilterPlaceholder { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the custom filter dynamic Linq dictionary.
        /// </summary>
        /// <value>The custom filter dynamic Linq dictionary.</value>
        [Parameter]
        public string CustomFilterExpression { get; set; } = string.Empty;

        /// <summary>
        /// Gets the filter placeholder.
        /// </summary>
        /// <returns>System.String.</returns>
        public string GetFilterPlaceholder()
        {
            return FilterPlaceholder ?? string.Empty;
        }

        /// <summary>
        /// Gets or sets the second filter value.
        /// </summary>
        /// <value>The second filter value.</value>
        [Parameter]
        public object? SecondFilterValue { get; set; }

        /// <summary>
        /// Gets or sets the width.
        /// </summary>
        /// <value>The width.</value>
        [Parameter]
        public string Width { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the min-width.
        /// </summary>
        /// <value>The min-width.</value>
        [Parameter]
        public string MinWidth { get; set; } = string.Empty;


        /// <summary>
        /// Gets or sets the max-width.
        /// </summary>
        /// <value>The max-width.</value>
        [Parameter]
        public string MaxWidth { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the format string.
        /// </summary>
        /// <value>The format string.</value>
        [Parameter]
        public string FormatString { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the CSS class applied to data cells.
        /// </summary>
        /// <value>The CSS class applied to data cells.</value>
        [Parameter]
        public string CssClass { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets a function that calculates the CSS class based on the <typeparamref name="TItem"/> value.
        /// </summary>
        /// <value>The dynamic CSS class applied to data cells.</value>
        [Parameter]
        public Func<RadzenDataGridColumn<TItem>, TItem, string>? CalculatedCssClass { get; set; }

        /// <summary>
        /// Gets or sets the header CSS class applied to header cell.
        /// </summary>
        /// <value>The header CSS class applied to header cell.</value>
        [Parameter]
        public string HeaderCssClass { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the footer CSS class applied to footer cell.
        /// </summary>
        /// <value>The footer CSS class applied to footer cell.</value>
        [Parameter]
        public string FooterCssClass { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the group footer CSS class applied to group footer cell.
        /// </summary>
        /// <value>The group footer CSS class applied to group footer cell.</value>
        [Parameter]
        public string GroupFooterCssClass { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the header white space style.
        /// </summary>
        [Parameter]
        public WhiteSpace HeaderWhiteSpace { get; set; } = WhiteSpace.Truncate;

        /// <summary>
        /// Gets or sets the white space style.
        /// </summary>
        [Parameter]
        public WhiteSpace WhiteSpace { get; set; } = WhiteSpace.Truncate;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is filterable.
        /// </summary>
        /// <value><c>true</c> if filterable; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Filterable { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is sortable.
        /// </summary>
        /// <value><c>true</c> if sortable; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Sortable { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is frozen.
        /// </summary>
        /// <value><c>true</c> if frozen will disable horizontal scroll for the column; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Frozen { get; set; }

        /// <summary>
        /// Gets or sets the frozen position this <see cref="RadzenDataGridColumn{TItem}"/>
        /// </summary>
        /// <value><see cref="FrozenColumnPosition.Left"/> or <see cref="FrozenColumnPosition.Right"/>.</value>
        [Parameter]
        public FrozenColumnPosition FrozenPosition { get; set; } = FrozenColumnPosition.Left;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is resizable.
        /// </summary>
        /// <value><c>true</c> if resizable; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Resizable { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is reorderable.
        /// </summary>
        /// <value><c>true</c> if reorderable; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Reorderable { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is groupable.
        /// </summary>
        /// <value><c>true</c> if groupable; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Groupable { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="RadzenDataGridColumn{TItem}"/> is pickable - listed when DataGrid AllowColumnPicking is set to true.
        /// </summary>
        /// <value><c>true</c> if pickable; otherwise, <c>false</c>.</value>
        [Parameter]
        public bool Pickable { get; set; } = true;

        /// <summary>
        /// Gets or sets the text align.
        /// </summary>
        /// <value>The text align.</value>
        [Parameter]
        public TextAlign TextAlign { get; set; } = TextAlign.Left;

        /// <summary>
        /// Gets or sets the template.
        /// </summary>
        /// <value>The template.</value>
        [Parameter]
        public RenderFragment<TItem>? Template { get; set; }

        /// <summary>
        /// Gets or sets the edit template.
        /// </summary>
        /// <value>The edit template.</value>
        [Parameter]
        public RenderFragment<TItem>? EditTemplate { get; set; }

        /// <summary>
        /// Allows the column to override whether or not this column's the <see cref="EditTemplate" /> is visible at runtime.
        /// </summary>
        [Parameter]
        public Func<string, TItem, bool> IsInEditMode { get; set; } = DefaultIsInEditMode;

        internal static Func<string, TItem, bool> DefaultIsInEditMode { get; set; } = (property, item) => false;

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

        /// <summary>
        /// Gets or sets the footer template.
        /// </summary>
        /// <value>The footer template.</value>
        [Parameter]
        public RenderFragment? FooterTemplate { get; set; }

        /// <summary>
        /// Gets or sets the group footer template.
        /// </summary>
        /// <value>The group footer template.</value>
        [Parameter]
        public RenderFragment<Group>? GroupFooterTemplate { get; set; }

        /// <summary>
        /// Gets or sets the filter template.
        /// </summary>
        /// <value>The filter template.</value>
        [Parameter]
        public RenderFragment<RadzenDataGridColumn<TItem>>? FilterTemplate { get; set; }

        /// <summary>
        /// Gets or sets the filter value template.
        /// </summary>
        /// <value>The filter value template.</value>
        [Parameter]
        public RenderFragment<RadzenDataGridColumn<TItem>>? FilterValueTemplate { get; set; }

        /// <summary>
        /// Gets or sets the second filter value template.
        /// </summary>
        /// <value>The second filter value template.</value>
        [Parameter]
        public RenderFragment<RadzenDataGridColumn<TItem>>? SecondFilterValueTemplate { get; set; }

        /// <summary>
        /// Gets or sets the logical filter operator.
        /// </summary>
        /// <value>The logical filter operator.</value>
        [Parameter]
        public LogicalFilterOperator LogicalFilterOperator { get; set; } = LogicalFilterOperator.And;

        /// <summary>
        /// Gets or sets the mode that determines whether the filter applies to any or all items in a collection.
        /// </summary>
        /// <value>
        /// A <see cref="CollectionFilterMode"/> value indicating whether the filter is satisfied by any or all items.
        /// </value>
        [Parameter]
        public CollectionFilterMode CollectionFilterMode { get; set; }

        /// <summary>
        /// Gets or sets the data type.
        /// </summary>
        /// <value>The data type.</value>
        [Parameter]
        public Type? Type { get; set; }

        /// <summary>
        /// Gets or sets the IFormatProvider used for FormatString.
        /// </summary>
        /// <value>The IFormatProvider.</value>
        [Parameter]
        public IFormatProvider? FormatProvider { get; set; }

        Func<TItem, object>? propertyValueGetter;

        /// <summary>
        /// Gets the value for specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns>System.Object.</returns>
        public virtual object? GetValue(TItem item)
        {
            var value = propertyValueGetter != null && !string.IsNullOrEmpty(Property) && !Property.Contains('.', StringComparison.Ordinal) ? propertyValueGetter(item) : !string.IsNullOrEmpty(Property) ? PropertyAccess.GetValue(item, Property) : "";


            if (FilterPropertyType != null && (PropertyAccess.IsEnum(FilterPropertyType) || PropertyAccess.IsNullableEnum(FilterPropertyType) ||
                ((FilterMode ?? Grid.FilterMode) == Radzen.FilterMode.CheckBoxList && (value as Enum) != null)) && value != null)
            {
                var enumValue = value as Enum;
                if (enumValue != null)
                {
                    value = EnumExtensions.GetDisplayDescription(enumValue, Grid.EnumFilterTranslationFunc);
                }
            }

            return !string.IsNullOrEmpty(FormatString) ? string.Format(FormatProvider ?? Grid?.Culture ?? CultureInfo.CurrentCulture, FormatString, value) : Convert.ToString(value, FormatProvider ?? Grid?.Culture ?? CultureInfo.CurrentCulture);
        }

        internal object GetHeader()
        {
            if (HeaderTemplate != null)
            {
                return HeaderTemplate;
            }
            else if (!string.IsNullOrEmpty(GetTitle()))
            {
                return GetTitle();
            }
            else
            {
                return Property;
            }
        }

        /// <summary>
        /// Gets the cell style.
        /// </summary>
        /// <param name="forCell">if set to <c>true</c> [for cell].</param>
        /// <param name="isHeaderOrFooterCell">if set to <c>true</c> [is header or footer cell].</param>
        /// <param name="isForCol">if set to <c>true</c> [is for col element].</param>
        /// <returns>System.String.</returns>
        public virtual string GetStyle(bool forCell = false, bool isHeaderOrFooterCell = false, bool isForCol = false)
        {
            var style = new List<string>();

            var width = GetWidthOrGridSetting()?.Trim();

            if (!string.IsNullOrEmpty(width))
            {
                style.Add($"width:{width}");
            }

            if (forCell && TextAlign != TextAlign.Left)
            {
                var enumName = Enum.GetName<TextAlign>(TextAlign);
                style.Add($"text-align:{(enumName ?? TextAlign.ToString()).ToLower(CultureInfo.InvariantCulture)};");
            }

            if (forCell && IsFrozen())
            {
                style.Add(GetStackedStyleForFrozen());
            }

            if (!isHeaderOrFooterCell && IsFrozen() || (isHeaderOrFooterCell && Grid.ColumnsCollection.Where(c => c.GetVisible() && c.IsFrozen()).Any()))
            {
                style.Add($"z-index:{(isHeaderOrFooterCell && IsFrozen() ? 2 : 1)}");
            }

            if (!string.IsNullOrEmpty(MinWidth))
            {
                style.Add($"min-width:{MinWidth}");
            }

            if (!string.IsNullOrEmpty(MaxWidth))
            {
                style.Add($"max-width:{MaxWidth}");
            }

            return string.Join(";", style);
        }

        private string GetStackedStyleForFrozen()
        {
            var visibleFrozenColumns = Grid.ColumnsCollection.Where(c => c.GetVisible() && c.IsFrozen() && c.FrozenPosition == FrozenPosition).ToList();
            if (FrozenPosition == FrozenColumnPosition.Left)
            {
                var stackColumns = visibleFrozenColumns.Where((c, i) => visibleFrozenColumns.IndexOf(this) > i);

                return GetStackedStyleForFrozen(stackColumns, "inset-inline-start");
            }
            else
            {
                var stackColumns = visibleFrozenColumns.Where((c, i) => visibleFrozenColumns.IndexOf(this) < i);

                return GetStackedStyleForFrozen(stackColumns, "inset-inline-end");
            }
        }

        private static string GetStackedStyleForFrozen(IEnumerable<RadzenDataGridColumn<TItem>> stackColumns, string position)
        {
            if (!stackColumns.Any())
            {
                return $"{position}:0";
            }

            var widths = new List<string>();
            foreach (var column in stackColumns)
            {
                var w = column.GetWidthOrGridSetting()?.Trim();

                if (string.IsNullOrEmpty(w))
                {
                    widths.Add("200px");
                    continue;
                }

                if (w.StartsWith("calc(", StringComparison.Ordinal) && w.EndsWith(')'))
                {
                    var calcExpression = w.Remove(w.Length - 1).Substring("calc(".Length);
                    widths.Add(calcExpression);
                    continue;
                }

                widths.Add(w);
            }

            if (widths.Count == 1)
            {
                return $"{position}:{widths.First()}";
            }

            return $"{position}:calc({string.Join(" + ", widths)})";
        }

        internal bool IsFrozen()
        {
            return Frozen && Parent == null && Columns == null;
        }

        /// <summary>
        /// Gets the sort property.
        /// </summary>
        /// <returns>System.String.</returns>
        public string GetSortProperty()
        {
            if (!string.IsNullOrEmpty(SortProperty))
            {
                return SortProperty;
            }
            else
            {
                return Property;
            }
        }

        /// <summary>
        /// Gets the value used to sort this column for the given item (the value at the sort property).
        /// </summary>
        internal object? GetSortValue(TItem item)
        {
            var sortProperty = GetSortProperty();
            return string.IsNullOrEmpty(sortProperty) ? null : PropertyAccess.GetValue(item, sortProperty);
        }

        internal void SetSortOrder(SortOrder? order)
        {
            var descriptor = Grid.sorts.Where(d => d.Property == GetSortProperty()).FirstOrDefault();
            if (descriptor == null)
            {
                descriptor = new SortDescriptor() { Property = GetSortProperty() };
            }

            if (order.HasValue)
            {
                SetSortOrderInternal(order.Value);
                descriptor.SortOrder = order.Value;
            }
            else
            {
                SetSortOrderInternal(null);
                if (Grid.sorts.Where(d => d.Property == GetSortProperty()).Any())
                {
                    Grid.sorts.Remove(descriptor);
                }
                descriptor = null;
            }

            if (descriptor != null && !Grid.sorts.Where(d => d.Property == GetSortProperty()).Any())
            {
                Grid.sorts.Add(descriptor);
            }

            sortOrder = new SortOrder?[] { order };
        }

        internal void SetSortOrderInternal(SortOrder? order)
        {
            sortOrder = new SortOrder?[] { order };
        }
        internal void ResetSortOrder()
        {
            sortOrder = Enumerable.Empty<SortOrder?>();
            SortOrder = null;
        }

        /// <summary>
        /// Gets the group property.
        /// </summary>
        /// <returns>System.String.</returns>
        public string GetGroupProperty()
        {
            if (!string.IsNullOrEmpty(GroupProperty))
            {
                return GroupProperty;
            }
            else
            {
                return Property;
            }
        }

        /// <summary>
        /// Gets the filter property.
        /// </summary>
        /// <returns>System.String.</returns>
        public string GetFilterProperty()
        {
            if (!string.IsNullOrEmpty(FilterProperty))
            {
                return FilterProperty;
            }
            else
            {
                return Property;
            }
        }

        Type? _filterPropertyType;

        /// <summary>
        /// Gets the filter property type.
        /// </summary>
        public Type? FilterPropertyType
        {
            get
            {
                return _filterPropertyType;
            }
        }

        IEnumerable<SortOrder?> sortOrder = Enumerable.Empty<SortOrder?>();
        object? filterValue;
        FilterOperator? filterOperator;
        object? secondFilterValue;
        FilterOperator? secondFilterOperator;
        LogicalFilterOperator? logicalFilterOperator;
        CollectionFilterMode? collectionFilterMode;
        string? customFilterExpression;

        /// <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)
        {
            if (parameters.DidParameterChange(nameof(Visible), Visible) ||
                parameters.DidParameterChange(nameof(Title), Title))
            {
                if (Grid != null)
                {
                    Grid.UpdatePickableColumn(this, parameters.GetValueOrDefault<bool>(nameof(Visible)));
                    await Grid.ChangeState();
                }
            }

            if (parameters.DidParameterChange(nameof(OrderIndex), OrderIndex))
            {
                var newOrderIndex = parameters.GetValueOrDefault<int?>(nameof(OrderIndex));
                if (newOrderIndex != orderIndex)
                {
                    SetOrderIndex(newOrderIndex);

                    if (Grid != null)
                    {
                        Grid.UpdateColumnsOrder();
                        await Grid.ChangeState();
                    }
                }
            }

            if (parameters.DidParameterChange(nameof(Pickable), Pickable))
            {
                var newPickable = parameters.GetValueOrDefault<bool>(nameof(Pickable));

                Pickable = newPickable;

                if (Grid != null)
                {
                    Grid.UpdatePickableColumns();
                    await Grid.ChangeState();
                }
            }

            if (parameters.DidParameterChange(nameof(SortOrder), SortOrder))
            {
                var newSortOrder = parameters.GetValueOrDefault<SortOrder?>(nameof(SortOrder));
                sortOrder = new SortOrder?[] { newSortOrder };
                SortOrder = newSortOrder;

                if (Grid != null)
                {
                    var descriptor = Grid.sorts.Where(d => d.Property == GetSortProperty()).FirstOrDefault();
                    if (newSortOrder.HasValue)
                    {
                        if (descriptor != null)
                        {
                            descriptor.SortOrder = newSortOrder.Value;
                        }
                        else
                        {
                            Grid.sorts.Add(new SortDescriptor() { Property = GetSortProperty(), SortOrder = newSortOrder.Value });
                        }
                    }
                    else
                    {
                        if (descriptor != null)
                        {
                            Grid.sorts.Remove(descriptor);
                        }
                    }
                    Grid._view = null;
                    await Grid.Reload();
                }
            }

            if (parameters.DidParameterChange(nameof(FilterValue), FilterValue))
            {
                filterValue = parameters.GetValueOrDefault<object>(nameof(FilterValue));

                FilterValue = filterValue;
                if (Grid != null)
                {
                    Grid.SaveSettings();
                    if (Grid.IsVirtualizationAllowed())
                    {
                        if (Grid.virtualize != null)
                        {
                            await Grid.virtualize.RefreshDataAsync();
                        }
                    }
                    else
                    {
                        await Grid.Reload();
                    }
                }

                if (FilterTemplate != null || FilterValueTemplate != null)
                {
                    return;
                }
            }

            if (parameters.DidParameterChange(nameof(SecondFilterValue), SecondFilterValue))
            {
                secondFilterValue = parameters.GetValueOrDefault<object>(nameof(SecondFilterValue));

                if (FilterTemplate != null || SecondFilterValueTemplate != null)
                {
                    SecondFilterValue = secondFilterValue;
                    if (Grid != null)
                    {
                        Grid.SaveSettings();
                        if (Grid.IsVirtualizationAllowed())
                        {
                            if (Grid.virtualize != null)
                            {
                                await Grid.virtualize.RefreshDataAsync();
                            }
                        }
                        else
                        {
                            await Grid.Reload();
                        }
                    }

                    return;
                }
            }

            if (parameters.DidParameterChange(nameof(CustomFilterExpression), CustomFilterExpression))
            {
                customFilterExpression = parameters.GetValueOrDefault<string>(nameof(CustomFilterExpression));

                if (CustomFilterExpression != null)
                {
                    CustomFilterExpression = customFilterExpression ?? string.Empty;
                    if (Grid != null)
                    {
                        Grid.SaveSettings();
                        if (Grid.IsVirtualizationAllowed())
                        {
                            if (Grid.virtualize != null)
                            {
                                await Grid.virtualize.RefreshDataAsync();
                            }
                        }
                        else
                        {
                            await Grid.Reload();
                        }
                    }

                    return;
                }
            }

            if (parameters.DidParameterChange(nameof(CollectionFilterMode), CollectionFilterMode))
            {
                collectionFilterMode = parameters.GetValueOrDefault<CollectionFilterMode?>(nameof(CollectionFilterMode));

                if (FilterTemplate != null || FilterValueTemplate != null)
                {
                    CollectionFilterMode = collectionFilterMode ?? default(CollectionFilterMode);
                    Grid?.SaveSettings();
                    if (Grid?.IsVirtualizationAllowed() == true)
                    {
                        if (Grid?.virtualize != null)
                        {
                            await Grid.virtualize.RefreshDataAsync();
                        }
                    }
                    else
                    {
                        if (Grid != null)
                        {
                            await Grid.Reload();
                        }
                    }

                    return;
                }
            }

            if (filterOperator == null && (parameters.DidParameterChange(nameof(FilterOperator), FilterOperator) || _filterOperator != null))
            {
                filterOperator = _filterOperator ?? parameters.GetValueOrDefault<FilterOperator>(nameof(FilterOperator));
            }

            if (parameters.DidParameterChange(nameof(SecondFilterValue), SecondFilterValue))
            {
                secondFilterValue = parameters.GetValueOrDefault<object>(nameof(SecondFilterValue));
            }

            if (parameters.DidParameterChange(nameof(SecondFilterOperator), SecondFilterOperator))
            {
                secondFilterOperator = parameters.GetValueOrDefault<FilterOperator>(nameof(SecondFilterOperator));
            }

            if (parameters.DidParameterChange(nameof(LogicalFilterOperator), LogicalFilterOperator))
            {
                logicalFilterOperator = parameters.GetValueOrDefault<LogicalFilterOperator>(nameof(LogicalFilterOperator));
            }

            await base.SetParametersAsync(parameters);
        }

        /// <summary>
        /// Get column sort order.
        /// </summary>
        public SortOrder? GetSortOrder()
        {
            return sortOrder.Any() ? sortOrder.FirstOrDefault() : SortOrder;
        }

        /// <summary>
        /// Get column filter value.
        /// </summary>
        public object? GetFilterValue()
        {
            return filterValue ?? FilterValue;
        }

        internal void ClearFilterValues()
        {
            if (headerCell != null)
            {
                headerCell.filterValues = null;
            }
        }

        /// <summary>
        /// Get column filter operator.
        /// </summary>
        public FilterOperator GetFilterOperator()
        {
            return filterOperator ?? FilterOperator;
        }

        /// <summary>
        /// Get column second filter value.
        /// </summary>
        public object? GetSecondFilterValue()
        {
            return secondFilterValue ?? SecondFilterValue;
        }

        /// <summary>
        /// Get column second filter operator.
        /// </summary>
        public FilterOperator GetSecondFilterOperator()
        {
            return secondFilterOperator ?? SecondFilterOperator;
        }

        /// <summary>
        /// Get column logical filter operator.
        /// </summary>
        public LogicalFilterOperator GetLogicalFilterOperator()
        {
            return logicalFilterOperator ?? LogicalFilterOperator;
        }

        /// <summary>
        /// Get column collection filter mode.
        /// </summary>
        public CollectionFilterMode GetCollectionFilterMode()
        {
            return collectionFilterMode ?? CollectionFilterMode;
        }

        /// <summary>
        /// Get body column class.
        /// </summary>
        /// <returns></returns>
        internal string GetCellClass()
        {
            var enumName = Enum.GetName<WhiteSpace>(WhiteSpace);
            return $"rz-cell-data rz-text-{(enumName ?? WhiteSpace.ToString()).ToLower(CultureInfo.InvariantCulture)}";
        }

        /// <summary>
        /// Get column header class.
        /// </summary>
        /// <returns></returns>
        internal string GetHeaderClass()
        {
            var enumName = Enum.GetName<WhiteSpace>(HeaderWhiteSpace);
            return $"rz-column-title-content rz-text-{(enumName ?? HeaderWhiteSpace.ToString()).ToLower(CultureInfo.InvariantCulture)}";
        }

        /// <summary>
        /// Set column filter value.
        /// </summary>
        public void SetFilterValue(object? value, bool isFirst = true)
        {
            if ((FilterPropertyType == typeof(DateTimeOffset) || FilterPropertyType == typeof(DateTimeOffset?)) && value != null && value is DateTime?)
            {
                DateTimeOffset? offset = DateTime.SpecifyKind((DateTime)value, ((DateTime?)value).Value.Kind);
                value = offset;
            }

            if ((FilterPropertyType == typeof(DateOnly) || FilterPropertyType == typeof(DateOnly?)) && value != null && value is DateTime?)
            {
                var v = PropertyAccess.DateOnlyFromDateTime((DateTime)value);
                value = v;
            }

            if ((FilterPropertyType == typeof(TimeOnly) || FilterPropertyType == typeof(TimeOnly?)) && value != null && value is string)
            {
                var v = TimeOnly.Parse($"{value}", CultureInfo.InvariantCulture);
                value = FilterPropertyType == typeof(TimeOnly) ? v : (TimeOnly?)v;
            }

            if ((FilterPropertyType == typeof(Guid) || FilterPropertyType == typeof(Guid?)) && value != null && value is string)
            {
                var v = Guid.Parse($"{value}");
                value = FilterPropertyType == typeof(Guid) ? v : (Guid?)v;
            }

            if (!QueryableExtension.IsEnumerable(value?.GetType() ?? typeof(object)) && FilterPropertyType != null && (PropertyAccess.IsEnum(FilterPropertyType) || (PropertyAccess.IsNullableEnum(FilterPropertyType))))
            {
                Type enumType = Enum.GetUnderlyingType(Nullable.GetUnderlyingType(FilterPropertyType) ?? FilterPropertyType);
                value = value is not null ? Convert.ChangeType(value, enumType, CultureInfo.InvariantCulture) : null;
            }

            if (isFirst)
            {
                filterValue = CanSetCurrentValue(value) ? value : 
                    GetFilterOperator() == FilterOperator.IsEmpty  || GetFilterOperator() == FilterOperator.IsNotEmpty ? string.Empty : null;
            }
            else
            {
                secondFilterValue = CanSetCurrentValue(value, false) ? value :
                    GetSecondFilterOperator() == FilterOperator.IsEmpty || GetSecondFilterOperator() == FilterOperator.IsNotEmpty ? string.Empty : null;
            }
        }

        /// <summary>
        /// Set column filter value and reload grid.
        /// </summary>
        /// <param name="value">Filter value.</param>
        /// <param name="isFirst"><c>true</c> if FilterValue; <c>false</c> for SecondFilterValue</param>
        public async Task SetFilterValueAsync(object value, bool isFirst = true)
        {
            SetFilterValue(value, isFirst);
            Grid.SaveSettings();
            await Grid.FirstPage(true);
        }

        internal bool CanSetFilterValue(bool isFirst = true)
        {
            var fo = isFirst ? GetFilterOperator() : GetSecondFilterOperator();
            return fo != FilterOperator.IsNull
                    && fo != FilterOperator.IsNotNull
                    && fo != FilterOperator.IsEmpty
                    && fo != FilterOperator.IsNotEmpty;
        }

        internal bool CanSetCurrentValue(object? value, bool isFirst = true)
        {
            return CanSetFilterValue(isFirst) ? !string.IsNullOrEmpty(value?.ToString()) : false;
        }

        internal bool HasCustomFilter()
        {
            return GetFilterOperator() == FilterOperator.Custom && !string.IsNullOrEmpty(GetCustomFilterExpression());
        }

        internal bool HasActiveFilter()
        {
            return GetFilterValue() != null
            || GetSecondFilterValue() != null
            || !CanSetFilterValue()
            || HasCustomFilter();
        }

        /// <summary>
        /// Get custom filter linq.
        /// </summary>
        public string GetCustomFilterExpression()
        {
            return customFilterExpression ?? CustomFilterExpression;
        }

        /// <summary>
        /// Set column custom filter linq.
        /// </summary>
        public void SetCustomFilterExpression(string? value)
        {
            customFilterExpression = value;
        }

        /// <summary>
        /// Set column custom filter linq and reload grid.
        /// </summary>
        /// <param name="value">Filter value.</param>
        public async Task SetCustomFilterExpressionAsync(string? value)
        {
            SetCustomFilterExpression(value);
            Grid.SaveSettings();
            await Grid.FirstPage(true);
        }

        /// <summary>
        /// Sets to default column filter values and operators.
        /// </summary>
        public virtual void ClearFilters()
        {
            var fo = GetFilterOperator() == FilterOperator.Custom
                ? FilterOperator.Custom
                : QueryableExtension.IsEnumerable(FilterPropertyType)
                    ? !string.IsNullOrEmpty(FilterProperty) && FilterProperty != Property ? FilterOperator.In : FilterOperator.Contains
                    : FilterPropertyType == typeof(string) ? FilterOperator.Contains : default(FilterOperator);

            if (FilterOperators?.Contains(fo) == true || FilterOperators == null)
            {
                SetFilterOperator(fo);
            }

            SetSecondFilterOperator(null);

            filterValue = null;
            secondFilterValue = null;

            ClearFilterValues();

            LogicalFilterOperator = default(LogicalFilterOperator);

            SetColumnDefaults();
        }

        FilterOperator? _filterOperator;
        /// <summary>
        /// Gets or sets the filter operator.
        /// </summary>
        /// <value>The filter operator.</value>
        [Parameter]
        public FilterOperator FilterOperator 
        {
            get
            {
                return _filterOperator ?? FilterOperator.Equals;
            }
            set
            {
                _filterOperator = value;
            }
        }

        IEnumerable<FilterOperator>? _filterOperators;
        /// <summary>
        /// Gets or sets the filter operators.
        /// </summary>
        /// <value>The filter operators.</value>
        [Parameter]
        public IEnumerable<FilterOperator>? FilterOperators
        {
            get
            {
                return _filterOperators;
            }
            set
            {
                _filterOperators = value;
            }
        }

        /// <summary>
        /// Gets or sets the second filter operator.
        /// </summary>
        /// <value>The second filter operator.</value>
        [Parameter]
        public FilterOperator SecondFilterOperator { get; set; }

        /// <summary>
        /// Set column filter operator.
        /// </summary>
        public void SetFilterOperator(FilterOperator? value)
        {
            var currentOperator = GetFilterOperator();
            if (value == FilterOperator.IsEmpty || value == FilterOperator.IsNotEmpty || value == FilterOperator.IsNull || value == FilterOperator.IsNotNull)
            {
                filterValue = value == FilterOperator.IsEmpty || value == FilterOperator.IsNotEmpty ? string.Empty : null;
            }
            else if (currentOperator == FilterOperator.IsEmpty || currentOperator == FilterOperator.IsNotEmpty
                || currentOperator == FilterOperator.IsNull || currentOperator == FilterOperator.IsNotNull)
            {
                filterValue = null;
            }

            filterOperator = value;
        }

        /// <summary>
        /// Set column second filter operator.
        /// </summary>
        public void SetSecondFilterOperator(FilterOperator? value)
        {
            var currentOperator = GetSecondFilterOperator();
            if (value == FilterOperator.IsEmpty || value == FilterOperator.IsNotEmpty || value == FilterOperator.IsNull || value == FilterOperator.IsNotNull)
            {
                secondFilterValue = value == FilterOperator.IsEmpty || value == FilterOperator.IsNotEmpty ? string.Empty : null;
            }
            else if (currentOperator == FilterOperator.IsEmpty || currentOperator == FilterOperator.IsNotEmpty
                || currentOperator == FilterOperator.IsNull || currentOperator == FilterOperator.IsNotNull)
            {
                secondFilterValue = null;
            }

            secondFilterOperator = value;
        }

        /// <summary>
        /// Set column logical operator.
        /// </summary>
        public void SetLogicalFilterOperator(LogicalFilterOperator value)
        {
            LogicalFilterOperator = value;
        }

        /// <summary>
        /// Set column collection filter mode.
        /// </summary>
        public void SetCollectionFilterMode(CollectionFilterMode? value)
        {
            collectionFilterMode = value;
        }

        /// <summary>
        /// Closes this column filter popup.
        /// </summary>
        public async Task CloseFilter()
        {
            if (Grid.FilterPopupRenderMode == PopupRenderMode.OnDemand && headerCell != null)
            {
                await headerCell.CloseFilter();
            }
            var jsRuntime = Grid.GetJSRuntime();
            if (jsRuntime != null)
            {
                await jsRuntime.InvokeVoidAsync("Radzen.closePopup", $"{GetColumnPopupID()}");
            }
        }

        internal string GetColumnPopupID()
        {
            var fiterProperty = Property != FilterProperty && !string.IsNullOrEmpty(FilterProperty) ?
                $"{Property}{GetFilterProperty()}" : GetFilterProperty();

            return $"{Grid.PopupID}{(string.IsNullOrEmpty(fiterProperty) ? Grid.allColumns.IndexOf(this).ToString(CultureInfo.InvariantCulture) : fiterProperty)}";
        }

        string? runtimeWidth;

        /// <summary>
        /// Set column width.
        /// </summary>
        public void SetWidth(string? value)
        {
            runtimeWidth = value;

            if (IsFrozen())
            {
                InvokeAsync(() => Grid.ChangeState());
            }
        }

        /// <summary>
        /// Get column width.
        /// </summary>
        public string GetWidth()
        {
            return !string.IsNullOrEmpty(runtimeWidth) ? runtimeWidth : Width;
        }

        /// <summary>
        /// Get column width if it's set, otherwise get a column width set on the grid.
        /// </summary>
        internal string? GetWidthOrGridSetting()
        {
            var internalWidth = GetWidth();
            return !string.IsNullOrWhiteSpace(internalWidth) ? internalWidth : Grid?.ColumnWidth;
        }

        /// <summary>
        /// Get possible column filter operators.
        /// </summary>
        public virtual IEnumerable<FilterOperator> GetFilterOperators()
        {
            if (FilterOperators != null)
            {
                return FilterOperators;
            }

            if (FilterPropertyType != null && (PropertyAccess.IsEnum(FilterPropertyType) || FilterPropertyType == typeof(bool)))
            {
                return new FilterOperator[] { FilterOperator.Equals, FilterOperator.NotEquals };
            }

            if (FilterPropertyType != null && (PropertyAccess.IsNullableEnum(FilterPropertyType) || FilterPropertyType == typeof(bool?)))
            {
                return new FilterOperator[] { FilterOperator.Equals, FilterOperator.NotEquals, FilterOperator.IsNull, FilterOperator.IsNotNull };
            }

            return Enum.GetValues<FilterOperator>().Where(o =>
            {
                if (o == FilterOperator.Custom)
                {
                    return false;
                }

                var isStringOperator = o == FilterOperator.Contains || o == FilterOperator.DoesNotContain
                    || o == FilterOperator.StartsWith || o == FilterOperator.EndsWith || o == FilterOperator.IsEmpty || o == FilterOperator.IsNotEmpty;

                if ((FilterPropertyType == typeof(string) || !QueryableExtension.IsEnumerable(FilterPropertyType)) &&
                    (o == FilterOperator.In || o == FilterOperator.NotIn))
                {
                    return false;
                }

                if (o == FilterOperator.IsNull || o == FilterOperator.IsNotNull)
                {
                    return FilterPropertyType == typeof(string) || QueryableExtension.IsEnumerable(FilterPropertyType) ||
                        FilterPropertyType != null && FilterPropertyType.IsGenericType && FilterPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
                }

                return FilterPropertyType == typeof(string) || QueryableExtension.IsEnumerable(FilterPropertyType) ? isStringOperator
                      || o == FilterOperator.Equals || o == FilterOperator.NotEquals
                      || o == FilterOperator.IsNull || o == FilterOperator.IsNotNull
                    : !isStringOperator;
            });
        }

        /// <summary>
        /// Get filter operator text
        /// </summary>
        public string? GetFilterOperatorText(FilterOperator filterOperator)
        {
            switch (filterOperator)
            {
                case FilterOperator.Custom:
                    return Grid?.CustomText;
                case FilterOperator.Contains:
                    return Grid?.ContainsText;
                case FilterOperator.DoesNotContain:
                    return Grid?.DoesNotContainText;
                case FilterOperator.EndsWith:
                    return Grid?.EndsWithText;
                case FilterOperator.Equals:
                    return Grid?.EqualsText;
                case FilterOperator.GreaterThan:
                    return Grid?.GreaterThanText;
                case FilterOperator.GreaterThanOrEquals:
                    return Grid?.GreaterThanOrEqualsText;
                case FilterOperator.LessThan:
                    return Grid?.LessThanText;
                case FilterOperator.LessThanOrEquals:
                    return Grid?.LessThanOrEqualsText;
                case FilterOperator.StartsWith:
                    return Grid?.StartsWithText;
                case FilterOperator.NotEquals:
                    return Grid?.NotEqualsText;
                case FilterOperator.IsNull:
                    return Grid?.IsNullText;
                case FilterOperator.IsEmpty:
                    return Grid?.IsEmptyText;
                case FilterOperator.IsNotNull:
                    return Grid?.IsNotNullText;
                case FilterOperator.IsNotEmpty:
                    return Grid?.IsNotEmptyText;
                default:
                    return $"{filterOperator}";
            }
        }

        internal string GetFilterOperatorSymbol(FilterOperator filterOperator)
        {
            var symbol = Grid.FilterCaseSensitivity == FilterCaseSensitivity.CaseInsensitive ? "a" : "A";
            switch (filterOperator)
            {
                case FilterOperator.Contains:
                    return $"*{symbol}*";
                case FilterOperator.DoesNotContain:
                    return $"*{symbol}*";
                case FilterOperator.StartsWith:
                    return $"{symbol}**";
                case FilterOperator.EndsWith:
                    return $"**{symbol}";
                case FilterOperator.Equals:
                    return "=";
                case FilterOperator.GreaterThan:
                    return ">";
                case FilterOperator.GreaterThanOrEquals:
                    return "≥";
                case FilterOperator.LessThan:
                    return "<";
                case FilterOperator.LessThanOrEquals:
                    return "≤";
                case FilterOperator.NotEquals:
                    return "≠";
                case FilterOperator.IsNull:
                    return "∅";
                case FilterOperator.IsNotNull:
                    return "!∅";
                case FilterOperator.IsEmpty:
                    return "= ''";
                case FilterOperator.IsNotEmpty:
                    return "≠ ''";
                case FilterOperator.In:
                    return "∈";
                case FilterOperator.NotIn:
                    return "∉";
                default:
                    return $"{filterOperator}";
            }
        }

        /// <summary>
        /// Gets value indicating if the user can specify time in DateTime column filter.
        /// </summary>
        public virtual bool ShowTimeForDateTimeFilter()
        {
#if NET6_0_OR_GREATER
            if (FilterPropertyType == typeof(DateOnly))
            {
                return false;
            }
#endif
            return true;
        }

        /// <summary>
        /// Gets value indicating if up and down buttons are displayed in numeric column filter.
        /// </summary>
        public virtual bool ShowUpDownForNumericFilter()
        {
            return true;
        }

        /// <summary>
        /// Gets an OData expression to filter by this column.
        /// </summary>
        /// <param name="second">Whether to use <see cref="SecondFilterValue"/> instead of <see cref="FilterValue"/></param>
        /// <returns>An OData expression to filter by this column.</returns>
        public string GetColumnODataFilter(bool second = false)
        {
            return GetColumnODataFilter(second ? GetSecondFilterValue() : GetFilterValue(), second ? GetSecondFilterOperator() : GetFilterOperator());
        }

        /// <summary>
        /// Gets an OData expression to filter by this column.
        /// </summary>
        /// <param name="filterValue">The specific value to filter by</param>
        /// <param name="filterOperator">The operator used to compare to <paramref name="filterValue"/></param>
        /// <returns>An OData expression to filter by this column.</returns>
        protected virtual string GetColumnODataFilter(object? filterValue, FilterOperator filterOperator)
        {
            return QueryableExtension.GetColumnODataFilter(this, filterValue, filterOperator);
        }

        /// <summary>
        /// Disposes this instance.
        /// </summary>
        public void Dispose()
        {
            Grid?.RemoveColumn(this);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Gets the column sort descriptor index indicating order of applied column sort in case of multiple sorting.
        /// </summary>
        public int? GetSortIndex()
        {
            var descriptor = Grid.sorts.Where(s => s.Property == GetSortProperty()).FirstOrDefault();
            if (descriptor != null)
            {
                return Grid.sorts.IndexOf(descriptor);
            }

            return null;
        }

        internal string getSortIndexAsString()
        {
            var index = GetSortIndex();
            return index != null ? $"{GetSortIndex() + 1}" : "";
        }

        internal RadzenDataGridHeaderCell<TItem>? headerCell;
    }
}
