@implements IAsyncDisposable

@using Microsoft.AspNetCore.Components
@using Microsoft.CodeAnalysis
@using Microsoft.CodeAnalysis.Text
@using Microsoft.JSInterop
@using RobotNet.WebApp.Scripts.Monaco
@using RobotNet.WebApp.Scripts.Monaco.Editor
@using RobotNet.WebApp.Scripts.Monaco.Languages
@using System.Timers

@inject IJSRuntime JsRuntime
@inject ISnackbar Snackbar

<div class="w-100 h-100 d-flex flex-column" tabindex="0" @onkeyup:preventDefault @onkeyup:stopPropagation @onkeyup="OnKeyPress">
    <div class="w-100 d-flex align-items-center justify-content-between">
        <div></div>
        <div>@NameFile</div>
        <div></div>
    </div>
    <div @ref=EditorContainer class="flex-grow-1 w-100"></div>
</div>


@code {
    [CascadingParameter]
    private ScriptWorkspace Workspace { get; set; } = null!;

    private object dotNetHelper = default!;
    private IJSInProcessObjectReference? editor;
    private IJSInProcessObjectReference? editorModel;
    private IJSInProcessObjectReference? disposableCompletionItemProvider;
    private IJSInProcessObjectReference? disposableSignatureHelpProvider;
    private IJSInProcessObjectReference? disposableHoverProvider;

    private bool IsEditorInitialized;
    private string Code = "";
    private string NameFile = "";
    public ProcessorState State { get; private set; } = ProcessorState.Idle;
    private bool IsReadOnly = true;

    private ElementReference EditorContainer;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);
        if (firstRender)
        {
            dotNetHelper = DotNetObjectReference.Create(this);

            editor = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("monaco.editor.create", EditorContainer, new
            {
                Value = "",
                Language = "csharp",
                Theme = "vs-dark",
                GlyphMargin = true,
                AutomaticLayout = true,
                ReadOnly = IsReadOnly,
            });

            editorModel = await editor.InvokeAsync<IJSInProcessObjectReference>("getModel");

            await JsRuntime.InvokeVoidAsync("MonacoRuntime.OnCodeEditorDidChangeModelContent", editor, dotNetHelper, nameof(CodeEditorDidChangeModelContent));
            disposableCompletionItemProvider = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("MonacoRuntime.CSharpLanguageRegisterCompletionItemProvider", dotNetHelper, nameof(GetCompletionItems));
            disposableSignatureHelpProvider = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("MonacoRuntime.CSharpLanguageRegisterSignatureHelpProvider", dotNetHelper, nameof(GetSignatureHelp));
            disposableHoverProvider = await JsRuntime.InvokeAsync<IJSInProcessObjectReference>("MonacoRuntime.CSharpLanguageRegisterHoverProvider", dotNetHelper, nameof(GetQuickInfo));
            IsEditorInitialized = true;

            Workspace.OnFileChanged += FileChanged;
            Workspace.OnDiagnoticChanged += DiagnoticChanged;
        }
    }

    public void OnProcessStateChanged(ProcessorState state)
    {
        State = state;
        var isReadOnly = State != ProcessorState.Ready && State != ProcessorState.Idle && State != ProcessorState.BuildError;
        if (isReadOnly != IsReadOnly)
        {
            IsReadOnly = isReadOnly;
            if (editor != null)
            {
                _ = InvokeAsync(async Task () =>
                {
                    await editor.InvokeVoidAsync("updateOptions", new
                    {
                        ReadOnly = IsReadOnly,
                    });
                });
            }
        }
    }

    private async Task FileChanged(ScriptFileModel? file)
    {
        await this.InvokeAsync(async Task () =>
        {
            if (file is null)
            {
                NameFile = "";
                Code = "";
            }
            else
            {
                NameFile = file.Name;
                Code = file.EditCode;
            }
            if (IsEditorInitialized && editor is not null)
            {
                await editor.InvokeVoidAsync("setValue", Code);
                await JsRuntime.InvokeVoidAsync("monaco.editor.setModelMarkers", editorModel, "default", file?.Diagnostics.Select(ToMonacoDiagnostic) ?? []);
                // await editor.InvokeVoidAsync("updateOptions", new
                // {
                //     ReadOnly = IsReadOnly,
                // });
            }
            StateHasChanged();
        });
    }

    private async Task DiagnoticChanged(IEnumerable<Diagnostic> diagnostics)
    {
        await this.InvokeAsync(async Task () =>
        {
            if (IsEditorInitialized && editor is not null)
            {
                await JsRuntime.InvokeVoidAsync("monaco.editor.setModelMarkers", editorModel, "default", diagnostics.Select(ToMonacoDiagnostic) ?? []);
            }
        });

    }

    [JSInvokable]
    public async Task<CompletionList> GetCompletionItems(int line, int column, int kind, char? triggerCharacter)
    {
        return new()
        {
            Suggestions = [.. await Workspace.GetCompletionCurrentFileAsync(line, column, kind, triggerCharacter)],
        };
    }

    [JSInvokable]
    public async Task<string?> GetQuickInfo(int line, int column)
    {
        return await Workspace.GetQuickInfoCurrentFile(line, column);
    }

    [JSInvokable]
    public async Task<SignatureHelpResult?> GetSignatureHelp(int line, int column)
    {
        return await Workspace.GetSignatureHelpCurrentFile(line, column);
    }

    [JSInvokable]
    public async Task CodeEditorDidChangeModelContent(ModelContentChangedEvent e)
    {
        if (e.IsFlush) return;

        if (e.Changes.Length != 0)
        {
            if (editor is not null)
            {
                var code = await editor.InvokeAsync<string>("getValue");

                if (code != Code)
                {
                    Code = code;
                    Workspace.WriteDocument(Code);
                }

            }
        }
    }

    bool isSaving = false;
    private async Task OnKeyPress(KeyboardEventArgs e)
    {
        if ((e.Key == "s" || e.Key == "S") && e.CtrlKey)
        {
            if(IsReadOnly)
            {
                Snackbar.Add($"Hệ thống đang ở trạng thái {State}, Không thể thay đổi script", Severity.Warning);
                return;
            }
            if (isSaving) return;
            isSaving = true;
            var result = await Workspace.SaveCurrentFileAsync();
            if (!result.IsSuccess)
            {
                Snackbar.Add(result.Message, Severity.Error);
            }
            isSaving = false;
        }
    }

    public async ValueTask DisposeAsync()
    {
        if (disposableCompletionItemProvider != null)
        {
            await disposableCompletionItemProvider.InvokeVoidAsync("dispose");
            await disposableCompletionItemProvider.DisposeAsync();
            disposableCompletionItemProvider = null;
        }

        if (disposableSignatureHelpProvider != null)
        {
            await disposableSignatureHelpProvider.InvokeVoidAsync("dispose");
            await disposableSignatureHelpProvider.DisposeAsync();
            disposableSignatureHelpProvider = null;
        }

        if (disposableHoverProvider != null)
        {
            await disposableHoverProvider.InvokeVoidAsync("dispose");
            await disposableHoverProvider.DisposeAsync();
            disposableHoverProvider = null;
        }

        if (editorModel != null)
        {
            await editorModel.InvokeVoidAsync("dispose");
            await editorModel.DisposeAsync();
            editorModel = null;
        }

        if (editor != null)
        {
            await editor.InvokeVoidAsync("dispose");
            await editor.DisposeAsync();
            editor = null;
        }

        if (IsEditorInitialized)
        {
            Workspace.OnFileChanged -= FileChanged;
            Workspace.OnDiagnoticChanged -= DiagnoticChanged;
        }

        GC.SuppressFinalize(this);
    }


    private static MarkerData ToMonacoDiagnostic(Diagnostic diagnostic)
    {
        var lineSpan = diagnostic.Location.GetLineSpan();
        return new()
        {
            StartLineNumber = lineSpan.StartLinePosition.Line + 1,
            StartColumn = lineSpan.StartLinePosition.Character + 1,
            EndLineNumber = lineSpan.EndLinePosition.Line + 1,
            EndColumn = lineSpan.EndLinePosition.Character + 1,
            Message = diagnostic.GetMessage(),
            Severity = diagnostic.Severity switch
            {
                DiagnosticSeverity.Info => MarkerSeverity.Info,
                DiagnosticSeverity.Warning => MarkerSeverity.Warning,
                DiagnosticSeverity.Error => MarkerSeverity.Error,
                _ => MarkerSeverity.Hint,
            },
        };
    }
}
