@page "/user/afh"
@page "/user/afh/{DatabaseStr}"
@page "/user/afh/{DatabaseStr}/{DatabaseInstanceStr}"
@attribute [Authorize]
@using Rms.Risk.Mango.Language
@using Rms.Risk.Mango.Language.Parsers

@inject NavigationManager NavigationManager
@inject IUserSession       UserSession
@inject IJSRuntime        JsRuntime

@*
 *                                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.
 *@

<style>
    .js-field {
        /* height: 100px; */
        position: relative;
        width: 100%;
        background-color: #1d2a3b;
        height: calc(100vh - 178px);
        overflow: auto;
    }

    pre {
        width: 100%;
        height: 100%;
        font-size: 10pt;
        background-color: #1d2a3b;
        color: #FFFFFF;
    }

    .CodeMirror {
        height: calc(100vh - 178px);
    }
</style>

<h3 class="mt-3">Aggregation for humans</h3>

<AuthorizedOnly Policy="ReadAccess" Resource="@UserSession.Database">
    <EditForm EditContext="EditContext">

        <div class="form-row">
            <FormItemText     Enabled="@IsReady" Class="mr-2" Name="Timeout (sec)"  @bind-Value="Timeout"   InputType="number" Icon="icon-clock-sm"/>
            <FormItemText     Enabled="@IsReady" Class="mr-2" Name="Max fetch size" @bind-Value="FetchSize" InputType="number" Icon="icon-download-selected-sm" />
            @* <FormItemCheckBox Enabled="@IsReady" Class="mr-2" Name="Show as Json" @bind-Value="ShowAsJson" /> *@
            <div class="flex-stack-vertical">
                <div class="flex-stack-horizontal">
                    <div class="form-group">
                        <label>&nbsp;</label>
                        <FormButton Enabled="@IsReady" Name="Run" Icon="icon-running-man-sm" OnClick="Run" IsPrimary="true" />
                    </div>
                    <div class="form-group">
                        <label>&nbsp;</label>
                        <FormButton Enabled="@IsReady" Name="Explain" Icon="icon-layers-sm" OnClick="Explain" IsPrimary="false" />
                    </div>
                    <div class="form-group">
                        <label>&nbsp;</label>
                        <FormButton Enabled="@IsReady" Name="Format" Icon="icon-pencil-sm" OnClick="Format" IsPrimary="false" />
                    </div>
                    <div class="form-group">
                        <label>&nbsp;</label>
                        <FormButton Enabled="@IsReady" Name="Copy" Icon="icon-duplicate-document-sm" OnClick="Copy" IsPrimary="false" />
                    </div>
                    <div class="form-group">
                        <label>&nbsp;</label>
                        <FormButton Enabled="@IsReady" Name="Paste" Icon="icon-paste" OnClick="Paste" IsPrimary="false" />
                    </div>
                </div>
                <div class="flex-stack-horizontal">
                    See:
                    <a href="https://www.mongodb.com/docs/manual/reference/command/aggregate/" target="_blank" class="ml-1 mr-1">Aggregation pipeline</a>
                    ,
                    <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/#std-label-aggregation-pipeline-operator-reference" target="_blank" class="ml-1 mr-1">Stages</a>
                    ,
                    <a href="/doc/afh" target="_blank" class="ml-1 mr-1">Script help</a>
                    ,
                    <a href="/doc/editor" target="_blank" class="ml-1 mr-1">Keys</a>
                </div>
            </div>
        </div>

    </EditForm>

    <div class="row">
        <div class="col-6">
            <TabControl ActivePageChanged="@OnActivePageChanged" PersistAllTabs="true">
                <TabPage Text="Text">
                    <div class="js-field">
                        <FormCodeEditor @bind-Text="Text" MediaType="text/x-afh" Class=""/>
                    </div>
                </TabPage>
                <TabPage Text="Json pipeline">
                    <div class="js-field">
                        <FormCodeEditor @bind-Text="TextJson" MediaType="application/json" Class="" Readonly="true" />
                    </div>
                </TabPage>
            </TabControl>
        </div>

        <div class="col-6">
            <TabControl @bind-ActivePage="ResultActivePage" PersistAllTabs="true">
                <TabPage Text="Table">
                    <SimplePivotComponent PivotName="find"
                                          CollectionName="find"
                                          PivotData="@ResultPivot"
                    />
                </TabPage>
                <TabPage Text="Json">
                    <div class="js-field">
                        <FormJson Class="" Value="@Result"/>
                    </div>
                </TabPage>
            </TabControl>
        </div>
    </div>
</AuthorizedOnly>

@code {
    [Parameter] public string? DatabaseStr { get; set; }
    [Parameter] public string? DatabaseInstanceStr { get; set; }

    private string Database
    {
        get => UserSession.Database;
        set
        {
            if (UserSession.Database == value)
                return;
            UserSession.Database = value;
            SyncUrl();
        }
    }

    private string DatabaseInstance
    {
        get => UserSession.DatabaseInstance;
        set
        {
            if (UserSession.DatabaseInstance == value)
                return;
            UserSession.DatabaseInstance = value;
            SyncUrl();
        }
    }

    private string Timeout { get; set; } = "20";
    private string FetchSize  { get; set; } = "10";
    private bool   IsReady    { get; set; } = true;
    // private bool   ShowAsJson { get; set; }
    private string ResultActivePage
    {
        get;
        set
        {
            if (field == value)
                return;
            field = value;
            InvokeAsync(StateHasChanged);
        }
    } = "Table";


    private string              Text        { get; set; } = "";
    private string              TextJson    { get; set; } = "";
    private string              Result      { get; set; } = "{}";
    private List<BsonDocument>  ResultBson  { get; set; } = [];
    public  ArrayBasedPivotData ResultPivot { get; set; } = new([]);

    private EditContext?   _editContext;
    private EditContext    EditContext => _editContext!;

    protected override void OnInitialized()
    {
        _editContext = new (this);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
            return;

        if (string.IsNullOrWhiteSpace(DatabaseStr))
            DatabaseStr = Database;
        else
            Database = DatabaseStr;

        if (string.IsNullOrWhiteSpace(DatabaseInstanceStr))
            DatabaseInstanceStr = DatabaseInstance;
        else
            DatabaseInstance = DatabaseInstanceStr;

        SyncUrl();

        var text = await JsRuntime.LoadFromLocalStorage("AggregationForHumans");
        if (!string.IsNullOrWhiteSpace(text))
        {
            Text = text;
            await InvokeAsync(StateHasChanged);
        }
    }

    private async Task Format()
    {

        await InvokeAsync(StateHasChanged);
    }

    private Task Run()     => Run(false);
    private Task Explain() => Run(true);

    private async Task Run(bool explain)
    {
        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(int.Parse(Timeout)));

        try
        {
            IsReady = false;
            await InvokeAsync(StateHasChanged);

            var pipelineJson = GetCombinedPipelineJson();
            await UpdatePipelineJson();

            await JsRuntime.SaveToLocalStorage("AggregationForHumans", Text, cts.Token);

            UserSession.Collection = GetSelectedCollection();

            var res = explain
                    ? await RunExplain(pipelineJson, cts.Token)
                    : await RunAggregate(pipelineJson, cts.Token)
                ;
            ResultPivot = res.Item1;
            ResultBson  = res.Item2;

            await Display(ResultBson);
        }
        catch (SyntaxErrorException ex)
        {
            var row = ex.Line;
            var col = ex.Position;

            try
            {
                await JsRuntime.InvokeVoidAsync("DashboardUtils.CodeEditor_SetCaret", CancellationToken.None, row, col);
            }
            catch (Exception)
            {
                // exception
            }

            await Display(ex);

        }
        catch (Exception e)
        {
            await Display(e);
        }
        finally
        {
            IsReady = true;
            await InvokeAsync(StateHasChanged);
        }
    }

    private static readonly System.Text.Json.JsonSerializerOptions _prettyPrint = new ()
    {
        WriteIndented    = true
    };


    private string GetCombinedPipelineJson()
    {
        var ast  = LanguageParser.ParseScriptToAST(Text);
        var json = ast.AsJson();

        var result = json?.ToJsonString(_prettyPrint) ?? "";
        return result;
    }

    private Task Display(Exception e)
    {
        ResultActivePage = "Json";
        Result           = e.ToString();
        return InvokeAsync(StateHasChanged);
    }

    private Task Display(IEnumerable<BsonDocument> res)
    {
        Result = res.ToJson(new() { Indent = true });
        return InvokeAsync(StateHasChanged);
    }

    private string GetSelectedCollection()
    {
        try
        {
            var ast  = LanguageParser.ParseScriptToAST(Text);
            return ast.Collection;
        }
        catch (Exception)
        {
            return "<syntax error>";
        }
    }

    private async Task<(ArrayBasedPivotData, List<BsonDocument>)> RunExplain(string json, CancellationToken token)
    {
        var service = UserSession.MongoDb;
        var command = $@"{{
    ""aggregate"" : ""{GetSelectedCollection()}"",
    ""pipeline"" : {json},
    ""cursor"" : {{}}
}}";
        var res = await service.ExplainAsync(command, token);
        ResultActivePage = "Json";
        return (ArrayBasedPivotData.NoData, [res]);
    }

    private async Task<(ArrayBasedPivotData, List<BsonDocument>)> RunAggregate(string text, CancellationToken token)
    {
        var service = UserSession.MongoDb;
        var results = service.AggregateAsyncRaw(text, token: token);

        var fieldMap = new ConcurrentDictionary<string, FieldMapping>
        {
            ["find"] = new(false)
        };

        var (pd, list) = await MongoDbDataSource.FetchPivotData(
            "find",
            "find",
            fieldMap,
            results,
            null,
            true,
            int.Parse(FetchSize),
            false,
            token);

        if (list.Count == 0)
            list.Add(BsonDocument.Parse(@$"{{""Result"": ""No data fetched from {GetSelectedCollection()}""}}"));

        return (pd, list);
    }

    private void SyncUrl()
    {
        var url = NavigationManager.BaseUri + $"user/afh/{Database}";
        if (!string.IsNullOrWhiteSpace(DatabaseInstance))
            url += $"/{DatabaseInstance}";

        JsRuntime.InvokeAsync<string>("DashboardUtils.ChangeUrl", url);
    }


    private async Task Copy()
    {
        await JsRuntime.InvokeVoidAsync("DashboardUtils.CopyToClipboard", Text);
    }

    private async Task Paste()
    {
        try
        {
            Result = "";
            await InvokeAsync(StateHasChanged);

            var text = await JsRuntime.InvokeAsync<string>("DashboardUtils.PasteFromClipboard");

            if (string.IsNullOrWhiteSpace(text))
                return;

            if (text[..Math.Min(text.Length, 100)].Trim().StartsWith("[{"))
            {
                try
                {
                    var ast = LanguageParser.ParseAggregationJsonToAST("<collection name here>", text);
                    text = ast.AsText();
                }
                catch (Exception)
                {
                    // ignore
                }
            }

            Text = text;

            await InvokeAsync(StateHasChanged);
        }
        catch (Exception e)
        {
            await Display(e);
        }
    }

    private async Task OnActivePageChanged(string arg)
    {
        if (arg != "Json pipeline")
            return;

        await UpdatePipelineJson();
    }

    private async Task UpdatePipelineJson()
    {
        try
        {
            var json = GetCombinedPipelineJson();
            TextJson = json;
        }
        catch (Exception e)
        {
            await Display(e);
        }

        await InvokeAsync(StateHasChanged);
    }

}
