using DbFirst.BlazorWebApp.Models.Grid;
using DbFirst.BlazorWebApp.Services;
using DevExpress.Blazor;
using DevExpress.Data.Filtering;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;

namespace DbFirst.BlazorWebApp.Components;

public abstract class BandGridBase<TItem> : ComponentBase
{
    [Inject] protected BandLayoutService BandLayoutService { get; set; } = default!;

    // --- Abstract: jedes Grid definiert diese selbst ---
    protected abstract string LayoutKey { get; }
    protected abstract List<ColumnDefinition> ColumnDefinitions { get; }

    // --- Band-Layout Felder ---
    protected BandLayout bandLayout = new();
    protected Dictionary<string, string> columnBandAssignments = new();
    protected List<BandOption> bandOptions = new();
    protected Dictionary<string, ColumnDefinition> columnLookup = new();
    protected string? layoutUser;
    protected bool gridLayoutApplied;
    protected IGrid? gridRef;

    // --- Datumsfilter-Zustand ---
    private readonly Dictionary<string, DateTime?> _filterFrom = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, DateTime?> _filterTo = new(StringComparer.OrdinalIgnoreCase);

    // Stabile Referenzen: werden einmal pro FieldName erstellt und wiederverwendet
    private readonly Dictionary<string, EventCallback<DateTime?>> _fromCallbacks = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, EventCallback<DateTime?>> _toCallbacks = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, RenderFragment<GridDataColumnFilterMenuTemplateContext>> _dateFilterTemplates = new(StringComparer.OrdinalIgnoreCase);
    private readonly Dictionary<string, GridDataColumnFilterMenuTemplateContext> _filterContexts = new(StringComparer.OrdinalIgnoreCase);

    // --- SizeMode ---
    protected SizeMode _sizeMode = SizeMode.Medium;
    protected static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();

    protected string? errorMessage;
    protected string? infoMessage;
    protected bool isLoading;
    protected bool hasLoaded;
    protected EditContext? editContext;
    protected ValidationMessageStore? validationMessageStore;
    protected string popupHeaderText = "Edit";
    protected int _focusedVisibleIndex;

    private const string LayoutType = "GRID_BANDS";

    // --- Lifecycle ---
    protected async Task InitializeBandLayoutAsync()
    {
        columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
        layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
        bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
        columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
        ApplyColumnLayoutFromStorage();
        _sizeMode = bandLayout.SizeMode;
        UpdateBandOptions();
    }

    protected async Task ApplyGridLayoutAfterRenderAsync()
    {
        if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
        {
            gridRef.LoadLayout(bandLayout.GridLayout);
            gridLayoutApplied = true;
            await InvokeAsync(StateHasChanged);
        }
    }

    // --- Layout speichern / zurücksetzen ---
    protected async Task SaveLayoutAsync()
    {
        if (string.IsNullOrWhiteSpace(layoutUser)) return;
        CaptureColumnLayoutFromGrid();
        await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
    }

    protected async Task ResetLayoutAsync()
    {
        if (string.IsNullOrWhiteSpace(layoutUser)) return;
        await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
        bandLayout = new BandLayout();
        columnBandAssignments.Clear();
        UpdateBandOptions();
        foreach (var column in ColumnDefinitions)
            column.Width = null;
        columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
        _sizeMode = SizeMode.Medium;
        gridRef?.LoadLayout(new GridPersistentLayout());
        gridLayoutApplied = false;
    }

    private void CaptureColumnLayoutFromGrid()
    {
        if (gridRef == null) return;
        var layout = gridRef.SaveLayout();
        bandLayout.GridLayout = layout;
        bandLayout.SizeMode = _sizeMode;
        var orderedColumns = layout.Columns
            .Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
            .OrderBy(c => c.VisibleIndex)
            .ToList();
        bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
        bandLayout.ColumnWidths = orderedColumns
            .Where(c => !string.IsNullOrWhiteSpace(c.Width))
            .ToDictionary(c => c.FieldName, c => c.Width, StringComparer.OrdinalIgnoreCase);
    }

    private void ApplyColumnLayoutFromStorage()
    {
        foreach (var column in ColumnDefinitions)
        {
            if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
                column.Width = width;
        }
        columnLookup = ColumnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
    }

    // --- Band-Methoden ---
    protected bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);

    protected virtual bool ShowCommandColumn => true;

    protected void AddBand()
    {
        bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
        UpdateBandOptions();
    }

    protected async Task RemoveBand(BandDefinition band)
    {
        bandLayout.Bands.Remove(band);
        foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
            columnBandAssignments.Remove(key);
        UpdateBandOptions();
        await SyncBandsFromAssignments();
    }

    protected void UpdateBandCaption(BandDefinition band, string value)
    {
        band.Caption = value;
        UpdateBandOptions();
    }

    protected async Task UpdateColumnBand(string fieldName, string? bandId)
    {
        if (string.IsNullOrWhiteSpace(bandId))
            columnBandAssignments.Remove(fieldName);
        else
            columnBandAssignments[fieldName] = bandId;
        await SyncBandsFromAssignments();
    }

    protected string GetColumnBand(string fieldName)
        => columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;

    protected async Task SyncBandsFromAssignments()
    {
        foreach (var band in bandLayout.Bands)
        {
            band.Columns = ColumnDefinitions
                .Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
                .Select(c => c.FieldName)
                .ToList();
        }
        await InvokeAsync(StateHasChanged);
    }

    protected void UpdateBandOptions()
    {
        bandOptions = [new() { Id = string.Empty, Caption = "Ohne Band" }];
        bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
    }

    // --- SizeMode ---
    protected string FormatSizeText(SizeMode size) => size switch
    {
        SizeMode.Small => "Klein",
        SizeMode.Medium => "Mittel",
        SizeMode.Large => "Groß",
        _ => size.ToString()
    };

    protected void OnSizeChange(DropDownButtonItemClickEventArgs args)
    {
        _sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
    }

    // --- RenderColumns / BuildDataColumn ---
    protected RenderFragment RenderColumns() => builder =>
    {
        var seq = 0;
        if (ShowCommandColumn)
        {
            builder.OpenComponent<DxGridCommandColumn>(seq++);
            builder.AddAttribute(seq++, "Width", "120px");
            builder.CloseComponent();
        }

        var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
        foreach (var column in ColumnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
            BuildDataColumn(builder, ref seq, column);

        foreach (var band in bandLayout.Bands)
        {
            if (band.Columns.Count == 0) continue;
            builder.OpenComponent<DxGridBandColumn>(seq++);
            builder.AddAttribute(seq++, "Caption", band.Caption);
            builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
            {
                var bandSeq = 0;
                foreach (var columnName in band.Columns)
                {
                    if (columnLookup.TryGetValue(columnName, out var column))
                        BuildDataColumn(bandBuilder, ref bandSeq, column);
                }
            }));
            builder.CloseComponent();
        }
    };

    protected void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
    {
        builder.OpenComponent<DxGridDataColumn>(seq++);
        builder.AddAttribute(seq++, "FieldName", column.FieldName);
        builder.AddAttribute(seq++, "Caption", column.Caption);
        if (!string.IsNullOrWhiteSpace(column.Width))
            builder.AddAttribute(seq++, "Width", column.Width);
        if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
            builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
        if (column.ReadOnly)
            builder.AddAttribute(seq++, "ReadOnly", true);
        if (column.FilterType == ColumnFilterType.Date)
            builder.AddAttribute(seq++, "FilterMenuTemplate", GetOrCreateDateFilterTemplate(column.FieldName));
        builder.CloseComponent();
    }

    private RenderFragment<GridDataColumnFilterMenuTemplateContext> GetOrCreateDateFilterTemplate(string fieldName)
    {
        if (!_dateFilterTemplates.TryGetValue(fieldName, out var template))
        {
            // EventCallbacks einmalig erstellen – stabile Referenzen über alle Renders
            _fromCallbacks[fieldName] = EventCallback.Factory.Create<DateTime?>(this, async (DateTime? v) => await OnFilterFromChanged(fieldName, v));
            _toCallbacks[fieldName] = EventCallback.Factory.Create<DateTime?>(this, async (DateTime? v) => await OnFilterToChanged(fieldName, v));
            template = BuildDateFilterTemplate(fieldName);
            _dateFilterTemplates[fieldName] = template;
        }
        return template;
    }

    private RenderFragment<GridDataColumnFilterMenuTemplateContext> BuildDateFilterTemplate(string fieldName) =>
        ctx => b =>
        {
            _filterContexts[fieldName] = ctx;
            SyncDateFilterFromContext(fieldName, ctx.FilterCriteria);
            int s = 0;
            b.OpenElement(s++, "div");
            b.AddAttribute(s++, "class", "date-filter-menu p-2");

            // Ab Datum
            b.OpenElement(s++, "div");
            b.AddAttribute(s++, "class", "mb-2");
            b.OpenElement(s++, "label");
            b.AddAttribute(s++, "class", "form-label small fw-semibold");
            b.AddContent(s++, "Ab Datum");
            b.CloseElement();
            b.OpenComponent<DxDateEdit<DateTime?>>(s++);
            b.AddAttribute(s++, "Date", _filterFrom.GetValueOrDefault(fieldName));
            b.AddAttribute(s++, "DateChanged", _fromCallbacks[fieldName]);
            b.AddAttribute(s++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
            b.AddAttribute(s++, "NullText", "Kein Startdatum");
            b.AddAttribute(s++, "Width", "100%");
            b.CloseComponent();
            b.CloseElement();

            // Bis Datum
            b.OpenElement(s++, "div");
            b.AddAttribute(s++, "class", "mb-0");
            b.OpenElement(s++, "label");
            b.AddAttribute(s++, "class", "form-label small fw-semibold");
            b.AddContent(s++, "Bis Datum");
            b.CloseElement();
            b.OpenComponent<DxDateEdit<DateTime?>>(s++);
            b.AddAttribute(s++, "Date", _filterTo.GetValueOrDefault(fieldName));
            b.AddAttribute(s++, "DateChanged", _toCallbacks[fieldName]);
            b.AddAttribute(s++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
            b.AddAttribute(s++, "NullText", "Kein Enddatum");
            b.AddAttribute(s++, "Width", "100%");
            b.CloseComponent();
            b.CloseElement();

            b.CloseElement();
        };

    private void SyncDateFilterFromContext(string fieldName, CriteriaOperator? criteria)
    {
        if (criteria == null)
        {
            _filterFrom[fieldName] = null;
            _filterTo[fieldName] = null;
            return;
        }

        DateTime? from = null;
        DateTime? to = null;

        if (criteria is GroupOperator group)
        {
            foreach (var op in group.Operands.OfType<BinaryOperator>())
                ParseDateOperand(op, ref from, ref to);
        }
        else if (criteria is BinaryOperator binary)
        {
            ParseDateOperand(binary, ref from, ref to);
        }

        _filterFrom[fieldName] = from;
        _filterTo[fieldName] = to;
    }

    private static void ParseDateOperand(BinaryOperator op, ref DateTime? from, ref DateTime? to)
    {
        if (op.RightOperand is not OperandValue val || val.Value is not DateTime dt) return;
        if (op.OperatorType == BinaryOperatorType.GreaterOrEqual) from = dt;
        else if (op.OperatorType == BinaryOperatorType.Less) to = dt.AddDays(-1);
    }

    private async Task OnFilterFromChanged(string fieldName, DateTime? value)
    {
        _filterFrom[fieldName] = value;
        await ApplyDateFilter(fieldName);
    }

    private async Task OnFilterToChanged(string fieldName, DateTime? value)
    {
        _filterTo[fieldName] = value;
        await ApplyDateFilter(fieldName);
    }

    private async Task ApplyDateFilter(string fieldName)
    {
        var ops = new List<CriteriaOperator>();
        if (_filterFrom.TryGetValue(fieldName, out var from) && from.HasValue)
            ops.Add(new BinaryOperator(fieldName, from.Value.Date, BinaryOperatorType.GreaterOrEqual));
        if (_filterTo.TryGetValue(fieldName, out var to) && to.HasValue)
            ops.Add(new BinaryOperator(fieldName, to.Value.Date.AddDays(1), BinaryOperatorType.Less));

        CriteriaOperator? criteria = ops.Count switch
        {
            0 => null,
            1 => ops[0],
            _ => new GroupOperator(GroupOperatorType.And, ops)
        };

        gridRef?.SetFieldFilterCriteria(fieldName, criteria);
        if (_filterContexts.TryGetValue(fieldName, out var ctx))
            ctx.FilterCriteria = criteria;
        await InvokeAsync(StateHasChanged);
    }

    protected void SetEditContext(EditContext context)
    {
        if (editContext == context) return;
        if (editContext != null)
            editContext.OnFieldChanged -= OnEditFieldChanged;
        editContext = context;
        validationMessageStore = new ValidationMessageStore(editContext);
        editContext.OnFieldChanged += OnEditFieldChanged;
    }

    protected virtual void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        validationMessageStore?.Clear();
        editContext?.NotifyValidationStateChanged();
    }

    protected void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";

    protected async Task EditFocusedRow()
        => await gridRef!.StartEditRowAsync(_focusedVisibleIndex);

    protected Task DeleteFocusedRow()
    {
        gridRef!.ShowRowDeleteConfirmation(_focusedVisibleIndex);
        return Task.CompletedTask;
    }

    protected async Task SaveLayoutWithFeedbackAsync()
    {
        try
        {
            await SaveLayoutAsync();
            infoMessage = "Layout gespeichert.";
            errorMessage = null;
        }
        catch (Exception ex)
        {
            errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
        }
    }

    protected async Task ResetLayoutWithFeedbackAsync()
    {
        await ResetLayoutAsync();
        infoMessage = "Layout zurückgesetzt.";
        errorMessage = null;
    }

}