using Microsoft.AspNetCore.Components;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading.Tasks;

namespace Radzen.Blazor
{
    /// <summary>
    /// Base component for RadzenPivotDataGrid Rows, Columns and Aggregates.
    /// </summary>
    /// <typeparam name="TItem">The type of the PivotDataGrid item.</typeparam>
    [UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2026, Justification = TrimMessages.DataTypePreserved)]
    [UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2087, Justification = TrimMessages.DataTypePreserved)]
    public partial class RadzenPivotField<
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] TItem> : ComponentBase, IDisposable
    {
        /// <summary>
        /// Gets or sets the property name.
        /// </summary>
        [Parameter]
        public string? Property { get; set; }

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

        /// <summary>
        /// Gets or sets the sort order.
        /// </summary>
        [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];

        /// <summary>
        /// Gets or sets a value indicating whether this column is sortable.
        /// </summary>
        [Parameter]
        public bool Sortable { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether this field is initially selected (active) in the pivot grid.
        /// When set to <c>false</c>, the field will be available in the picker but not applied on initial load.
        /// </summary>
        [Parameter]
        public bool Selected { get; set; } = true;

        /// <summary>
        /// Gets or sets a value indicating whether this column is filterable.
        /// </summary>
        [Parameter]
        public bool Filterable { get; set; } = true;

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

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

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

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

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

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

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

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

        private SortOrder? internalSortOrder;
        private bool hasInternalSortOrder;

        /// <summary>
        /// Gets the current sort order (internal state).
        /// </summary>
        public SortOrder? GetSortOrder()
        {
            return hasInternalSortOrder ? internalSortOrder : SortOrder;
        }

        /// <summary>
        /// Sets the internal sort order.
        /// </summary>
        internal void SetSortOrderInternal(SortOrder? sortOrder)
        {
            internalSortOrder = sortOrder;
            hasInternalSortOrder = true;
        }

        /// <summary>
        /// Resets the sort order.
        /// </summary>
        internal void ResetSortOrder()
        {
            internalSortOrder = null;
            hasInternalSortOrder = false;
        }

        private object? internalFilterValue;
        private object? internalSecondFilterValue;
        private FilterOperator? internalSecondFilterOperator;
        private LogicalFilterOperator? internalLogicalFilterOperator;

        /// <summary>
        /// Gets the current filter value (internal state).
        /// </summary>
        public object? GetFilterValue()
        {
            return internalFilterValue ?? FilterValue;
        }

        /// <summary>
        /// Sets the internal filter value.
        /// </summary>
        internal void SetFilterValueInternal(object? filterValue)
        {
            internalFilterValue = filterValue;
        }

        /// <summary>
        /// Gets the current second filter value (internal state).
        /// </summary>
        public object? GetSecondFilterValue()
        {
            return internalSecondFilterValue ?? SecondFilterValue;
        }

        /// <summary>
        /// Sets the internal second filter value.
        /// </summary>
        internal void SetSecondFilterValueInternal(object? filterValue)
        {
            internalSecondFilterValue = filterValue;
        }

        /// <summary>
        /// Gets the current filter operator (internal state).
        /// </summary>
        public FilterOperator GetFilterOperator()
        {
            return filterOperator ?? FilterOperator;
        }

        /// <summary>
        /// Sets the internal filter operator.
        /// </summary>
        internal void SetFilterOperatorInternal(FilterOperator? filterOperator)
        {
            this.filterOperator = filterOperator;
        }

        /// <summary>
        /// Gets the current second filter operator (internal state).
        /// </summary>
        public FilterOperator GetSecondFilterOperator()
        {
            return internalSecondFilterOperator ?? SecondFilterOperator;
        }

        /// <summary>
        /// Sets the internal second filter operator.
        /// </summary>
        internal void SetSecondFilterOperatorInternal(FilterOperator? filterOperator)
        {
            internalSecondFilterOperator = filterOperator;
        }

        /// <summary>
        /// Gets the current logical filter operator (internal state).
        /// </summary>
        public LogicalFilterOperator GetLogicalFilterOperator()
        {
            return internalLogicalFilterOperator ?? LogicalFilterOperator;
        }

        /// <summary>
        /// Sets the internal logical filter operator.
        /// </summary>
        internal void SetLogicalFilterOperatorInternal(LogicalFilterOperator? logicalFilterOperator)
        {
            internalLogicalFilterOperator = logicalFilterOperator;
        }

        /// <summary>
        /// Gets the filter property name.
        /// </summary>
        public string? GetFilterProperty()
        {
            return Property;
        }

        /// <summary>
        /// Checks if the field has an active filter.
        /// </summary>
        public bool HasActiveFilter()
        {
            return GetFilterValue() != null || GetSecondFilterValue() != null;
        }

        /// <summary>
        /// Clears all filter values.
        /// </summary>
        internal void ClearFilterValues()
        {
            internalFilterValue = null;
            internalSecondFilterValue = null;
        }

        /// <summary>
        /// Gets the column title.
        /// </summary>
        public string? GetTitle()
        {
            return !string.IsNullOrEmpty(Title) ? Title : Property;
        }

        /// <summary>
        /// Called when initialized.
        /// </summary>
        protected override void OnInitialized()
        {
            if (PivotGrid != null)
            {
                PivotGrid.AddPivotField(this);

                var property = GetFilterProperty();

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

                if (!string.IsNullOrEmpty(property) && Type == null)
                {
                    _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);
                }
            }
        }

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

            filterOperator = value;
        }

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

            secondFilterOperator = value;
        }

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

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

        /// <summary>
        /// Set column filter value.
        /// </summary>
        public void SetFilterValue(object value, bool isFirst = true)
        {
            var filterPropertyType = FilterPropertyType;

            if (filterPropertyType == null)
            {
                if (isFirst)
                {
                    filterValue = value;
                }
                else
                {
                    secondFilterValue = value;
                }
                return;
            }

            if ((filterPropertyType == typeof(DateTimeOffset) || filterPropertyType == typeof(DateTimeOffset?)) && value is DateTime dateTimeValue)
            {
                DateTimeOffset offset = DateTime.SpecifyKind(dateTimeValue, dateTimeValue.Kind);
                value = offset;
            }

            if ((filterPropertyType == typeof(TimeOnly) || filterPropertyType == typeof(TimeOnly?)) && value is string stringValue)
            {
                var v = TimeOnly.Parse(stringValue, CultureInfo.InvariantCulture);
                value = filterPropertyType == typeof(TimeOnly) ? v : (TimeOnly?)v;
            }

            if ((filterPropertyType == typeof(Guid) || filterPropertyType == typeof(Guid?)) && value is string guidString)
            {
                var v = Guid.Parse(guidString);
                value = filterPropertyType == typeof(Guid) ? v : (Guid?)v;
            }

            if (!QueryableExtension.IsEnumerable(value?.GetType() ?? typeof(object)) && (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 = value != null && CanSetCurrentValue(value) ? value :
                    GetFilterOperator() == FilterOperator.IsEmpty || GetFilterOperator() == FilterOperator.IsNotEmpty ? string.Empty : null;
            }
            else
            {
                secondFilterValue = value != null && CanSetCurrentValue(value, false) ? value :
                    GetSecondFilterOperator() == FilterOperator.IsEmpty || GetSecondFilterOperator() == FilterOperator.IsNotEmpty ? string.Empty : null;
            }
        }

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

        FilterOperator? _filterOperator;
        Func<TItem, object>? propertyValueGetter;
        object? filterValue;
        FilterOperator? filterOperator;
        object? secondFilterValue;
        FilterOperator? secondFilterOperator;
        LogicalFilterOperator? logicalFilterOperator;
        Type? _propertyType;
        internal Type? PropertyType => _propertyType;
        Type? _filterPropertyType;

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

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

        /// <summary>
        /// Gets or sets the parent pivot data grid component.
        /// </summary>
        [CascadingParameter]
        public RadzenPivotDataGrid<TItem>? PivotGrid { get; set; }

        /// <summary>
        /// Disposes the component and removes it from the parent pivot grid.
        /// </summary>
        public virtual void Dispose()
        {
            PivotGrid?.RemovePivotField(this);
        }

        /// <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(FilterValue), FilterValue))
            {
                filterValue = parameters.GetValueOrDefault<object>(nameof(FilterValue));

                if (FilterTemplate != null || FilterValueTemplate != null)
                {
                    FilterValue = filterValue;
                    if (PivotGrid != null)
                    {
                        await PivotGrid.Reload();
                    }

                    return;
                }
            }

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

                if (FilterTemplate != null || SecondFilterValueTemplate != null)
                {
                    SecondFilterValue = secondFilterValue;
                    if (PivotGrid != null)
                    {
                        await PivotGrid.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);
        }
    }
} 