@page "/"
@using BlazorMonaco
@using BlazorMonaco.Editor
@using BlazorMonaco.Languages
@using CommunityToolkit.Maui.Storage
@using Inspectron.Epson
@using Inspectron.Epson.PrintServer.Printers
@using Inspectron.Epson.TemplateEngine
@using Inspectron.PrintServer.TemplateEditor.Services
@implements IDisposable
@inject IJSRuntime JSRuntime

<div class="editor-container">
    <div class="toolbar">
        <button class="toolbar-btn" title="New (Ctrl+N)" @onclick="NewFileAsync">New</button>
        <button class="toolbar-btn" title="Open (Ctrl+O)" @onclick="OpenFileAsync">Open</button>
        <button class="toolbar-btn" title="Save (Ctrl+S)" @onclick="SaveFileAsync">Save</button>
        <span class="file-indicator" title="@(_currentFilePath ?? "Untitled")">
            @FileDisplayName@(HasUnsavedChanges ? " *" : "")
        </span>
        <div class="toolbar-separator"></div>
        <button class="toolbar-btn" title="Load JSON (Ctrl+J)" @onclick="LoadJsonAsync">Load JSON</button>
        @if (!string.IsNullOrEmpty(_loadedJsonFile))
        {
            <span class="json-indicator" title="@_loadedJsonFile">@_loadedJsonFile</span>
        }
        <div class="toolbar-separator"></div>
        <select class="toolbar-select" title="Printer model" @onchange="OnPrinterModelChanged" value="@_selectedPrinterModel">
            <option value="TM-T30III">TM-T30III</option>
            <option value="TM-U220II">TM-U220II</option>
        </select>
    </div>

    <div class="split-pane">
        <div class="pane pane-editor">
            <div class="pane-header">Editor</div>
            <StandaloneCodeEditor @ref="_editor" Id="my-editor-id"
                                  ConstructionOptions="EditorConstructionOptions"
                                  OnDidInit="OnEditorInitAsync"
                                  OnDidChangeModelContent="OnContentChanged" />
        </div>

        <div class="pane pane-preview">
            <div class="pane-header">Preview</div>
            <div class="preview-content">
                @if (!string.IsNullOrEmpty(_previewError))
                {
                    <div class="preview-error">@_previewError</div>
                }
                <iframe id="preview-iframe"></iframe>
            </div>
        </div>
    </div>
</div>

@code {
    private StandaloneCodeEditor _editor = null!;
    private string _editorContent = "";
    private string? _loadedJsonFile;
    private readonly TemplateCompletionProvider _completionProvider = new();

    private string? _currentFilePath;
    private string _savedContent = "";

    private string? _jsonContent;
    private string? _previewHtml;
    private string? _previewError;
    private string _selectedPrinterModel = "TM-T30III";
    private CancellationTokenSource? _debounceCts;
    private DotNetObjectReference<Home>? _dotNetRef;

    private bool HasUnsavedChanges => _editorContent != _savedContent;

    private string FileDisplayName => _currentFilePath is not null
        ? Path.GetFileName(_currentFilePath)
        : "Untitled";

    private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
    {
        return new StandaloneEditorConstructionOptions
        {
            AutomaticLayout = true,
            Language = "receipt-xml",
            Theme = "receipt-dark"
        };
    }

    private async Task OnEditorInitAsync()
    {
        // Register custom language and theme
        await JSRuntime.InvokeVoidAsync("receiptLanguage.register");
        await JSRuntime.InvokeVoidAsync("receiptLanguage.defineTheme");

        // Apply the theme now that it's defined
        await BlazorMonaco.Editor.Global.SetTheme(JSRuntime, "receipt-dark");

        // Wire up content accessor for the completion provider
        _completionProvider.SetEditorContentAccessor(() => _editorContent);

        // Register completion provider
        await BlazorMonaco.Languages.Global.RegisterCompletionItemProvider(
            JSRuntime,
            "receipt-xml",
            _completionProvider.ProvideCompletionItems,
            _completionProvider.ResolveCompletionItem);

        // Register global hotkeys
        _dotNetRef = DotNetObjectReference.Create(this);
        await JSRuntime.InvokeVoidAsync("registerHotkeys", _dotNetRef);
    }

    [JSInvokable]
    public async Task OnHotkeyAsync(string action)
    {
        switch (action)
        {
            case "New": await NewFileAsync(); break;
            case "Open": await OpenFileAsync(); break;
            case "Save": await SaveFileAsync(); break;
            case "LoadJson": await LoadJsonAsync(); break;
        }
        StateHasChanged();
    }

    public void Dispose()
    {
        _debounceCts?.Cancel();
        _debounceCts?.Dispose();
        _dotNetRef?.Dispose();
    }

    private async Task OnContentChanged(ModelContentChangedEvent e)
    {
        _editorContent = await _editor.GetValue();

        _debounceCts?.Cancel();
        _debounceCts = new CancellationTokenSource();
        var token = _debounceCts.Token;

        _ = Task.Run(async () =>
        {
            try
            {
                await Task.Delay(500, token);
                await InvokeAsync(async () =>
                {
                    await RenderPreviewAsync();
                    StateHasChanged();
                });
            }
            catch (TaskCanceledException) { }
        });
    }

    private async Task NewFileAsync()
    {
        _currentFilePath = null;
        _savedContent = "";
        _editorContent = "";
        await _editor.SetValue("");
        _previewHtml = null;
        _previewError = null;
        await JSRuntime.InvokeVoidAsync("setPreviewContent", "");
    }

    private async Task OpenFileAsync()
    {
        try
        {
            var result = await FilePicker.Default.PickAsync(new PickOptions
            {
                PickerTitle = "Select an XML template file",
                FileTypes = new FilePickerFileType(new Dictionary<DevicePlatform, IEnumerable<string>>
                {
                    { DevicePlatform.WinUI, new[] { ".xml" } },
                    { DevicePlatform.macOS, new[] { "xml" } },
                    { DevicePlatform.iOS, new[] { "public.xml" } },
                    { DevicePlatform.Android, new[] { "text/xml" } },
                })
            });

            if (result is null)
                return;

            var content = await File.ReadAllTextAsync(result.FullPath);
            _currentFilePath = result.FullPath;
            _savedContent = content;
            _editorContent = content;
            await _editor.SetValue(content);
            await RenderPreviewAsync();
        }
        catch (Exception)
        {
            // User cancelled or file read failed
        }
    }

    private async Task SaveFileAsync()
    {
        if (_currentFilePath is not null)
        {
            await File.WriteAllTextAsync(_currentFilePath, _editorContent);
            _savedContent = _editorContent;
        }
        else
        {
            await SaveAsAsync();
        }
    }

    private async Task SaveAsAsync()
    {
        try
        {
            using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(_editorContent));
            var result = await FileSaver.Default.SaveAsync("template.xml", stream, CancellationToken.None);

            if (result.IsSuccessful)
            {
                _currentFilePath = result.FilePath;
                _savedContent = _editorContent;
            }
        }
        catch (Exception)
        {
            // User cancelled or save failed
        }
    }

    private async Task LoadJsonAsync()
    {
        try
        {
            var result = await FilePicker.Default.PickAsync(new PickOptions
            {
                PickerTitle = "Select a JSON data file",
                FileTypes = new FilePickerFileType(new Dictionary<DevicePlatform, IEnumerable<string>>
                {
                    { DevicePlatform.WinUI, new[] { ".json" } },
                    { DevicePlatform.macOS, new[] { "json" } },
                    { DevicePlatform.iOS, new[] { "public.json" } },
                    { DevicePlatform.Android, new[] { "application/json" } },
                })
            });

            if (result is null)
                return;

            using var stream = await result.OpenReadAsync();
            using var reader = new StreamReader(stream);
            var json = await reader.ReadToEndAsync();

            _jsonContent = json;

            var paths = JsonPathExtractor.Extract(json);
            _completionProvider.SetJsonPaths(paths);
            _loadedJsonFile = result.FileName;

            await RenderPreviewAsync();
            StateHasChanged();
        }
        catch (Exception)
        {
            // User cancelled or file read failed
        }
    }

    private async Task OnPrinterModelChanged(ChangeEventArgs e)
    {
        _selectedPrinterModel = e.Value?.ToString() ?? "TM-T30III";
        await RenderPreviewAsync();
    }

    private async Task RenderPreviewAsync()
    {
        if (string.IsNullOrWhiteSpace(_editorContent) || string.IsNullOrWhiteSpace(_jsonContent))
            return;

        try
        {
            _previewError = null;

            var engine = new ReceiptTemplateEngine();
            var commands = engine.Render(_editorContent, _jsonContent);

            var htmlBuilder = new HtmlPrintBuilder();
            await htmlBuilder.ConnectAsync("preview");

            if (_selectedPrinterModel == "TM-U220II")
            {
                var printer = new TM_U220IITranslated(htmlBuilder) { SkipDelays = true };
                await printer.InitAsync();
                await printer.PrintAsync(commands);
                await printer.Cut();
            }
            else
            {
                var printer = new TM_T30IIITranslated(htmlBuilder) { SkipDelays = true };
                await printer.InitAsync();
                await printer.PrintAsync(commands);
                await printer.Cut();
            }

            _previewHtml = htmlBuilder.GetHtml();
            await JSRuntime.InvokeVoidAsync("setPreviewContent", _previewHtml);
        }
        catch (Exception ex)
        {
            _previewError = ex.Message;
            _previewHtml = null;
            await JSRuntime.InvokeVoidAsync("setPreviewContent", "");
        }
    }
}
