@using System.Drawing
@using System.Dynamic
@using ChartJs.Blazor
@using ChartJs.Blazor.LineChart
@using Rms.Risk.Mango.Pivot.Core

@inject IJSRuntime           Js
@inject NavigationManager    NavigationManager
@inject IPivotSharingService PivotSharingService

@*
 *                                dbMango
 *
 * Copyright 2025 Deutsche Bank AG
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *@

<div class="@Class">

<style>
    .chart-canvas {
        width: 1024px;
        height: 470px;
        margin-bottom: 50px;
    }

    .glow-pos {
        background: #279320;
    }

    .glow-neg {
        background: #723e36;
    }

    .glow-last {
        border-bottom-color: yellow;
        border-bottom-style: double;
    }
</style>

<div class="@ChartClass">
    <div class="chart-canvas ml-3">
        <Chart Config="@ChartConfig"/>
    </div>
</div>

@if (_pivotRows is { Length: > 0 } 
 && (ShowIfEmpty || (!PivotData.Get(0, 0)?.ToString()?.Equals("No results") ?? false)))
{
    @if (!string.IsNullOrEmpty(Title))
    {
        <h2>@Title</h2>
    }
    <TableControl Class="table table-hover table-striped table-forge fit-content" 
                  Items="@_pivotRows" 
                  PageSize="@Rows" 
                  @bind-FilteredItems="FilteredRows"
                  CellStyle="GetCellStyle"
                  GetCellClassCallback="GetCellClassCallback"
                  Totals="@VisibleTotals"
                  @bind-SortMode="SortMode"
                  @bind-CurrentSortColumn="SortColumn"
                  >
        @foreach (var field in _pivotRows[0].GetDynamicMemberNames())
        {
            
            var headerStyle = HeaderStyles.GetValueOrDefault(field, "");
            
            <TableColumnControl Name="@field" Field="@field" Format="@_pivotRows[0].GetFormat(field)" ShowTotals="@_pivotRows[0].ShouldShowTotals(field)" Class="@ColumnClass" HeaderStyle="@headerStyle">
                <Template>
                    <span @onclick="@(() => OnCellClick((DynamicObject)context.Row, field))">
                        @TableControl.ConvertToString(TableControl.GetLambdaForValue(field)(context.Row), _pivotRows[0].GetFormat(field))
                    </span>
                </Template>
            </TableColumnControl>
        }
    </TableControl>
}
</div>

@code{
    [CascadingParameter] public IModalService Modal { get; set; } = null!;

    [Parameter] public string Class { get; set; } = "";

    [Parameter, EditorRequired]
    public IPivotedData PivotData
    {
        get;
        set
        {
            // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
            if (field == value || value == null)
                return;

            field = value;
            _totals.Clear();

            _shadowHeaders = field.GetColumnPositions();

            _descriptorsCache.Clear();
            _fieldDescriptorsCache.Clear();

            _pivotRows = Enumerable
                .Range(0, field.Count)
                .Select(x => new PivotRow(field, x, _shadowHeaders, GetColumnDescriptorInternal, GetFieldDescriptorInternal))
                .ToArray()
                ;

            _totals.Update(field);

            if (PivotDef != null)
            {
                try
                {
                    ChartHelper.UpdateLineChart(PivotDef, field, x => _pivotRows[0].GetFormat(x));
                }
                catch (Exception)
                {
                    // ignore
                }
            }

            InvokeAsync(StateHasChanged);
        }
    } = new ArrayBasedPivotData([]);

    [Parameter] public PivotDefinition?                                    PivotDef               { get; set; }
    [Parameter] public GroupedCollection?                                  SelectedCollectionNode { get; set; }
    [Parameter] public int                                                 Rows                   { get; set; } = 35;
    [Parameter] public Func<string /*columnName*/, PivotColumnDescriptor?> GetColumnDescriptor    { get; set; } = _ => null;
    [Parameter] public Func<string /*columnName*/, PivotFieldDescriptor?>  GetFieldDescriptor     { get; set; } = _ => null;
    [Parameter] public Func<DynamicObject, string, Task<bool>>             HandleCellClick        { get; set; } = (_, _) => Task.FromResult(false);
    [Parameter] public string                                              Title                  { get; set; } = "";
    [Parameter] public bool                                                ShowIfEmpty            { get; set; } = true;
    [Parameter] public Dictionary<string, string>                          HeaderStyles           { get; set; } = [];

    [Parameter]
    public bool IsExportEnabled
    {
        get => _pivotRows is { Length: > 0 };
        // ReSharper disable once ValueParameterNotUsed
        set
        {
            // ignore
        }
    }
    [Parameter] public EventCallback<bool> IsExportEnabledChanged { get; set; }


    public Task CopyCsv()                      => CopyCsvInternal();
    public Task ExportCsv(string destFileName) => ExportCsvInternal(destFileName);

    private          PivotRow[]                                 _pivotRows = [];
    private readonly MinMaxCache                                _totals = new();
    private          PivotColumnDescriptor[]                  ? _descriptors;
    private          Dictionary<string, PivotFieldDescriptor> ? _fieldTypes;
    private          List<dynamic>                              _filteredRows  = [];
    private readonly DelayedExecution                           _delayedUpdate = new(TimeSpan.FromMilliseconds(1500));
    private          Dictionary<string, int>                    _shadowHeaders = [];

    private bool                ShowTotals    => PivotDef?.ShowTotals ?? false;
    private IMinMaxCache?       VisibleTotals => ShowTotals ? _totals : null;
    private LineConfig          ChartConfig   => ChartHelper.ChartConfig;
    private ChartHelperForPivot ChartHelper   { get; }      = new();

    private string ChartClass => PivotDef == null || !ChartHelper.IsLineChart(PivotDef, PivotData)
        ? "d-none"
        : ""
        ;

    private string ColumnClass
    {
        get 
        {
            //This is a little awkward, usually the table control will auto wrap long headers when they contain spaces, but we have some pivots currently that have
            //a combination of long columns with and without spaces. Wrapping looks weird here, so we want to turn off wrapping if we can
            //detect any column which has a long name without spaces

            if ((_pivotRows[0].GetDynamicMemberNames().Count() <= 16 
              || _pivotRows[0].GetDynamicMemberNames().Any(n => n.Length >= 10 && !n.Contains(" "))
                )
               )
                return "table-nowrap";
            else 
                return string.Empty;
        }
    }

    private async Task<bool> OnCellClick(DynamicObject row, string fieldName)
    {
        try
        {
            await HandleCellClick(row, fieldName);
        }
        catch (Exception e)
        {
            await ModalDialogUtils.ShowExceptionDialog(Modal, $"Error drilling down into {fieldName}", e);
        }
        return true;
    }

    private readonly Dictionary<string, PivotColumnDescriptor> _descriptorsCache      = [];
    private readonly Dictionary<string, PivotFieldDescriptor?> _fieldDescriptorsCache = [];

    private readonly PivotColumnDescriptor _defaultPivotColumnDescriptor = new()
    {
        Format              = "N0",
        NameRegexString     = ".*",
        Background          = Color.FromName(Night.Background),
        AlternateBackground = Color.FromName(Night.BackgroundLight)
    };

    private TableControl.SortModeType SortMode   { get; set; }
    private string?                   SortColumn { get; set; }

    private PivotColumnDescriptor GetColumnDescriptorInternal(string columnName)
    {
        if (_descriptorsCache.TryGetValue(columnName, out var desc))
            return desc;

        _descriptors ??= SelectedCollectionNode?.ColumnDescriptors;

        desc = GetColumnDescriptor(columnName)
            ?? _descriptors?.FirstOrDefault(x => x.NameRegex.IsMatch(columnName)) 
            ?? _defaultPivotColumnDescriptor;

        _descriptorsCache[columnName] = desc;
        return desc;
    }

    private PivotFieldDescriptor? GetFieldDescriptorInternal(string columnName)
    {
        if (_fieldDescriptorsCache.TryGetValue(columnName, out var desc))
            return desc;

        _fieldTypes ??= SelectedCollectionNode?.FieldTypes;

        desc = GetFieldDescriptor(columnName);
        if (desc == null)
            _fieldTypes?.TryGetValue(columnName, out desc);

        _fieldDescriptorsCache[columnName] = desc;
        return desc;
    }

    private async Task CopyCsvInternal()
    {
        if (!IsExportEnabled)
            return;

        try
        {
            var csv = _pivotRows[0].PivotData.CopyToCsv();
            await Js.InvokeVoidAsync("DashboardUtils.CopyToClipboard", csv);
        }
        catch (Exception e)
        {
            await ModalDialogUtils.ShowExceptionDialog(Modal, "Error copying pivot data", e);
        }
    }

    private async Task ExportCsvInternal(string destFileName)
    {
        if (!IsExportEnabled)
            return;

        try
        {
            await using var writer = new StringWriter();
            _pivotRows[0].PivotData.WriteToCsv(writer);
            var data = writer.ToString();

            var url = await PivotSharingService.ExportToCsv(
                destFileName,
                data
            );

            @* var url = await DownloadController.GetDownloadLink( *@
            @*     _storage, *@
            @*     _passwordManager, *@
            @*     _singleUseTokenService, *@
            @*     fileName => *@
            @*     { *@
            @*         _pivotRows![0].PivotData.WriteToCsv(fileName); *@
            @*         return Task.CompletedTask; *@
            @*     }, *@
            @*     destFileName *@
            @*     ); *@

            await Js.InvokeVoidAsync("open", $"{NavigationManager.BaseUri}{url}", "_blank");
        }
        catch (Exception e)
        {
            await ModalDialogUtils.ShowExceptionDialog(Modal, "Error exporting pivot data", e);
        }
    }

    // ReSharper disable once UnusedMember.Local
    private List<dynamic> FilteredRows
    {
        get => _filteredRows;
        set
        {
            _filteredRows = value;
            _totals.Update(PivotData, _filteredRows.OfType<PivotRow>().Select( x => x.Row).ToArray());
            
            if (_filteredRows.Count == 0 || PivotDef is not { MakeLineChart: true })
                return;

            _delayedUpdate.Run(UpdateFilteredChart, "UpdateFilteredChart");
        }
    }

    private Task UpdateFilteredChart(CancellationToken token)
    {
        try
        {

            if (PivotDef == null || token.IsCancellationRequested)
                return Task.CompletedTask;

            var filteredPivot = new PivotFilteredView(_filteredRows.Cast<PivotRow>().ToList());

            if (token.IsCancellationRequested)
                return Task.CompletedTask;

            ChartHelper.UpdateLineChart(PivotDef, filteredPivot, x => _pivotRows[0].GetFormat(x));
        }
        catch (Exception)
        {
            // ignore
        }
        return InvokeAsync(StateHasChanged);
    }

    public static string GetCellStyle(dynamic row, TableColumnControl col)
    {
        if ( row is not PivotRow r || string.IsNullOrWhiteSpace(col.Field) )
            return "";

        var desc = r.GetColumnDescriptor(col.Field);
        if ( desc == null )
            return "";

        // for some reason comparison with 0x00000 and Color.Black are not working
        if ( desc.Background is { R: 0, G: 0, B: 0 })
            return "";

        return $"background-color:#{desc.Background.A:x2}{desc.Background.R:x2}{desc.Background.G:x2}{desc.Background.B:x2}";
    }
    
    private string GetCellClassCallback(dynamic row, TableColumnControl col)
    {
        var cellClass = "";

        // make a copy as GetFieldDescriptor may take noticeable time
        var highlightTopPercent = PivotDef?.HighlightTopPercent ?? 0.0;

        if (highlightTopPercent <= 0.0)
            return cellClass;

        if ( row is not PivotRow r || string.IsNullOrWhiteSpace(col.Field) )
            return cellClass;

        var mm = _totals.TryGet(col.Name);
        if ( mm == null )
            return cellClass;

        var desc = r.GetFieldDescriptor(col.Field);
        if ( desc != null && desc.Purpose != PivotFieldPurpose.Data )
            return cellClass;

        double val = GetCellValue(row, col.Field);

        cellClass = val switch
        {
            > 0 when val > mm.MaxValue - mm.MaxValue * highlightTopPercent / 100.0 =>
                $"{cellClass} glow-pos",
            < 0 when val < mm.MinValue - mm.MinValue * highlightTopPercent / 100.0 =>
                $"{cellClass} glow-neg",
            _ => cellClass
        };

        // NaN here most likely means string column
        if ( !double.IsNaN(val) && IsLastImportant(r, col.Name, mm) )
            cellClass += " glow-last";

        return cellClass;
    }

    private double GetCellValue(PivotRow row, string col)
    {
        if ( !_shadowHeaders.TryGetValue(col, out var idx) )
            return double.NaN;

        var val = row.PivotData.Get(idx, row.Row);
        return val switch
        {
            double d => d,
            int i    => i,
            _        => double.NaN
        };
    }

    private bool IsLastImportant(PivotRow row, string colName, MinMax mm)
    {
        if ( SortColumn != colName || SortMode != TableControl.SortModeType.DescendingAbsolute) 
            return false;

        var sum    = 0.0;
        var target = mm.AbsTotal * ( PivotDef?.HighlightTopPercent ?? 0.0 ) / 100.0;

        foreach ( var r in FilteredRows.OfType<PivotRow>() )
        {
            var val = GetCellValue(r, colName);
            if ( double.IsNaN(val) )
                return false;
            
            sum += Math.Abs(val);
            
            if ( sum >= target )
                return row.Row == r.Row;
        }

        return false;
    }

}
