@page "/design/shared-scripts/create"
@page "/design/shared-scripts/{Id:int}/edit"
@using ScadaLink.Security
@using ScadaLink.Commons.Entities.Scripts
@using ScadaLink.Commons.Interfaces.Repositories
@using ScadaLink.TemplateEngine
@using ScriptAnalysis = ScadaLink.CentralUI.ScriptAnalysis
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDesign)]
@inject ITemplateEngineRepository TemplateEngineRepository
@inject SharedScriptService SharedScriptService
@inject ScriptAnalysis.ScriptAnalysisService AnalysisService
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager NavigationManager

<div class="container-fluid mt-3">
    <div class="d-flex align-items-center mb-3">
        <button class="btn btn-outline-secondary btn-sm me-3" @onclick="GoBack">&larr; Back</button>
        <h4 class="mb-0">@(Id.HasValue ? $"Edit Shared Script: {_formName}" : "New Shared Script")</h4>
    </div>

    @if (_loading)
    {
        <LoadingSpinner IsLoading="true" />
    }
    else
    {
        <div class="card mb-3">
            <div class="card-body">
                <div class="mb-3">
                    <label class="form-label small">Name</label>
                    <input type="text" class="form-control form-control-sm" @bind="_formName"
                           disabled="@(Id.HasValue)" />
                </div>

                @* Tabs: Code, Parameters, Return. Panels stay mounted (toggled
                   via display:none) so the Monaco editor and the JSONJoy React
                   island don't tear down on tab switch. *@
                <ul class="nav nav-tabs" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button type="button"
                                class="nav-link @(_formTab == "code" ? "active" : "")"
                                role="tab"
                                aria-selected="@(_formTab == "code" ? "true" : "false")"
                                @onclick='() => _formTab = "code"'>Code</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button type="button"
                                class="nav-link @(_formTab == "parameters" ? "active" : "")"
                                role="tab"
                                aria-selected="@(_formTab == "parameters" ? "true" : "false")"
                                @onclick='() => _formTab = "parameters"'>Parameters</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button type="button"
                                class="nav-link @(_formTab == "return" ? "active" : "")"
                                role="tab"
                                aria-selected="@(_formTab == "return" ? "true" : "false")"
                                @onclick='() => _formTab = "return"'>Return type</button>
                    </li>
                </ul>
                <div class="border border-top-0 rounded-bottom p-3">
                    <div style="display: @(_formTab == "code" ? "block" : "none")">
                        <MonacoEditor @ref="_editor" Value="@_formCode" ValueChanged="@(v => _formCode = v)"
                                      Language="csharp" Height="320px"
                                      DeclaredParameters="@ScriptParameterNames.Parse(_formParameters)"
                                      DeclaredParameterShapes="@ScriptParameterNames.ParseShapes(_formParameters)"
                                      MarkersChanged="@(m => { _markers = m; StateHasChanged(); })" />
                        <ProblemsPanel Markers="@_markers" OnNavigate="@(m => _editor?.RevealLineAsync(m.StartLineNumber, m.StartColumn) ?? Task.CompletedTask)" />
                    </div>
                    <div style="display: @(_formTab == "parameters" ? "block" : "none")">
                        <SchemaBuilder Mode="object"
                                       Value="@_formParameters"
                                       ValueChanged="@(v => _formParameters = v)" />
                    </div>
                    <div style="display: @(_formTab == "return" ? "block" : "none")">
                        <SchemaBuilder Mode="value"
                                       Value="@_formReturn"
                                       ValueChanged="@(v => _formReturn = v)" />
                    </div>
                </div>

                @if (_formError != null)
                {
                    <div class="text-danger small mt-2">@_formError</div>
                }
                @if (_syntaxCheckResult != null)
                {
                    <div class="@(_syntaxCheckPassed ? "text-success" : "text-danger") small mt-1">@_syntaxCheckResult</div>
                }
                <div class="mt-3">
                    <button class="btn btn-success btn-sm me-1" @onclick="SaveScript">Save</button>
                    <button class="btn btn-outline-info btn-sm me-1" @onclick="CheckCompilation">Check Syntax</button>
                    <button class="btn btn-outline-primary btn-sm me-1" @onclick="ToggleTestRunPanel">
                        @(_showTestRun ? "Hide Test Run" : "Test Run")
                    </button>
                    <button class="btn btn-outline-secondary btn-sm" @onclick="GoBack">Cancel</button>
                </div>
            </div>
        </div>

        @if (_showTestRun)
        {
            <div class="card mb-3" id="test-run-panel">
                <div class="card-header py-2 d-flex justify-content-between align-items-center">
                    <span class="fw-semibold">Test Run <span class="badge bg-warning text-dark ms-1">Real I/O</span></span>
                </div>
                <div class="alert alert-warning py-1 mb-0 small rounded-0 border-0 border-bottom">
                    <strong>Heads up:</strong>
                    <code>External</code>, <code>Database</code>, and <code>Notify</code> calls fire for real against central's configured systems — real HTTP, real SQL, real emails. Side effects are permanent.
                    <code>CallShared</code> executes the named shared script (saved version) in the same sandbox.
                    <code>Attributes</code> and <code>CallScript</code> still throw.
                </div>
                <div class="card-body">
                    <div class="mb-3">
                        <label class="form-label small">Parameter values</label>
                        <ParameterValueForm ParameterDefinitions="@_formParameters"
                                            Values="_paramValues"
                                            ValuesChanged="@(v => _paramValues = v)" />
                    </div>
                    <div class="d-flex gap-2 align-items-center mb-3">
                        <button class="btn btn-primary btn-sm" @onclick="RunInSandboxAsync" disabled="@_running">
                            @if (_running)
                            {
                                <span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
                                <span>Running…</span>
                            }
                            else
                            {
                                <span>Run</span>
                            }
                        </button>
                        @if (_runResult != null)
                        {
                            <span class="text-muted small">@_runResult.DurationMs ms</span>
                        }
                    </div>

                    @if (_runResult != null)
                    {
                        @if (_runResult.Success)
                        {
                            <div class="mb-3">
                                <label class="form-label small text-success mb-1">
                                    Return value <span class="badge bg-light text-dark ms-1">@_runResult.ReturnTypeName</span>
                                </label>
                                <pre class="bg-light border rounded p-2 small mb-0 font-monospace" style="white-space: pre-wrap;">@_runResult.ReturnValueJson</pre>
                            </div>
                        }
                        else
                        {
                            <div class="mb-3">
                                <label class="form-label small text-danger mb-1">
                                    <span class="badge bg-danger me-1">@ErrorKindLabel(_runResult.ErrorKind)</span>
                                </label>
                                <pre class="border border-danger-subtle rounded p-2 small mb-0 font-monospace text-danger" style="white-space: pre-wrap;">@_runResult.Error</pre>
                                @if (_runResult.Markers is { Count: > 0 })
                                {
                                    <ul class="small text-danger mt-2 mb-0">
                                        @foreach (var m in _runResult.Markers)
                                        {
                                            <li>Line @m.StartLineNumber, col @m.StartColumn: @m.Message <code class="ms-1">@m.Code</code></li>
                                        }
                                    </ul>
                                }
                            </div>
                        }

                        @if (!string.IsNullOrEmpty(_runResult.ConsoleOutput))
                        {
                            <div class="mb-0">
                                <label class="form-label small mb-1">Console output</label>
                                <pre class="bg-dark text-light rounded p-2 small mb-0 font-monospace" style="white-space: pre-wrap;">@_runResult.ConsoleOutput</pre>
                            </div>
                        }
                    }
                </div>
            </div>
        }
    }
</div>

@code {
    [Parameter] public int? Id { get; set; }

    private bool _loading;
    private string _formTab = "code"; // "code" | "parameters" | "return"
    private string _formName = string.Empty;
    private string _formCode = string.Empty;
    private string? _formParameters;
    private string? _formReturn;
    private string? _formError;
    private string? _syntaxCheckResult;
    private bool _syntaxCheckPassed;
    private MonacoEditor? _editor;
    private IReadOnlyList<ScriptAnalysis.DiagnosticMarker> _markers = Array.Empty<ScriptAnalysis.DiagnosticMarker>();

    private bool _showTestRun;
    private bool _running;
    private Dictionary<string, object?> _paramValues = new();
    private ScriptAnalysis.SandboxRunResult? _runResult;
    private CancellationTokenSource? _runCts;

    // CentralUI-024: delegates to the shared helper so the claim type stays
    // resolved through JwtTokenService rather than a duplicated magic string.
    private Task<string> GetCurrentUserAsync()
        => AuthStateProvider.GetCurrentUsernameAsync();

    protected override async Task OnInitializedAsync()
    {
        if (Id.HasValue)
        {
            _loading = true;
            try
            {
                var scripts = await SharedScriptService.GetAllSharedScriptsAsync();
                var script = scripts.FirstOrDefault(s => s.Id == Id.Value);
                if (script != null)
                {
                    _formName = script.Name;
                    _formCode = script.Code;
                    _formParameters = script.ParameterDefinitions;
                    _formReturn = script.ReturnDefinition;
                }
                else
                {
                    _formError = $"Shared script with ID {Id.Value} not found.";
                }
            }
            catch (Exception ex)
            {
                _formError = $"Failed to load script: {ex.Message}";
            }
            _loading = false;
        }
    }

    private void GoBack()
    {
        NavigationManager.NavigateTo("/design/shared-scripts");
    }

    private void CheckCompilation()
    {
        var syntaxError = ValidateSyntaxLocally(_formCode);
        if (syntaxError == null)
        {
            _syntaxCheckResult = "Syntax check passed.";
            _syntaxCheckPassed = true;
        }
        else
        {
            _syntaxCheckResult = syntaxError;
            _syntaxCheckPassed = false;
        }
    }

    private async Task SaveScript()
    {
        _formError = null;
        _syntaxCheckResult = null;

        try
        {
            if (Id.HasValue)
            {
                var user = await GetCurrentUserAsync();
                var result = await SharedScriptService.UpdateSharedScriptAsync(
                    Id.Value, _formCode, _formParameters?.Trim(), _formReturn?.Trim(), user);
                if (result.IsSuccess)
                {
                    NavigationManager.NavigateTo("/design/shared-scripts");
                }
                else
                {
                    _formError = result.Error;
                }
            }
            else
            {
                var user = await GetCurrentUserAsync();
                var result = await SharedScriptService.CreateSharedScriptAsync(
                    _formName.Trim(), _formCode, _formParameters?.Trim(), _formReturn?.Trim(), user);
                if (result.IsSuccess)
                {
                    NavigationManager.NavigateTo("/design/shared-scripts");
                }
                else
                {
                    _formError = result.Error;
                }
            }
        }
        catch (Exception ex)
        {
            _formError = $"Save failed: {ex.Message}";
        }
    }

    private void ToggleTestRunPanel()
    {
        _showTestRun = !_showTestRun;
    }

    private async Task RunInSandboxAsync()
    {
        _runCts?.Cancel();
        _runCts = new CancellationTokenSource();
        _running = true;
        _runResult = null;
        StateHasChanged();

        try
        {
            var jsonParams = _paramValues.ToDictionary(
                kv => kv.Key,
                kv => System.Text.Json.JsonSerializer.SerializeToElement(kv.Value));
            var request = new ScriptAnalysis.SandboxRunRequest(_formCode, jsonParams, TimeoutSeconds: null);
            _runResult = await AnalysisService.RunInSandboxAsync(request, _runCts.Token);
        }
        catch (OperationCanceledException) { /* superseded by next Run click */ }
        catch (Exception ex)
        {
            _runResult = new ScriptAnalysis.SandboxRunResult(
                Success: false,
                ReturnValueJson: null,
                ReturnTypeName: null,
                ConsoleOutput: "",
                Error: $"Unexpected: {ex.GetType().Name}: {ex.Message}",
                ErrorKind: ScriptAnalysis.SandboxErrorKind.RuntimeError,
                DurationMs: 0,
                Markers: null);
        }
        finally
        {
            _running = false;
            StateHasChanged();
        }
    }

    private static string ErrorKindLabel(ScriptAnalysis.SandboxErrorKind kind) => kind switch
    {
        ScriptAnalysis.SandboxErrorKind.CompileError => "Compile error",
        ScriptAnalysis.SandboxErrorKind.SandboxLimitation => "Sandbox limitation",
        ScriptAnalysis.SandboxErrorKind.RuntimeError => "Runtime error",
        ScriptAnalysis.SandboxErrorKind.Timeout => "Timeout",
        _ => "Error"
    };

    /// <summary>
    /// Basic syntax check: balanced braces/brackets/parens.
    /// Mirrors the internal SharedScriptService.ValidateSyntax logic.
    /// </summary>
    private static string? ValidateSyntaxLocally(string code)
    {
        if (string.IsNullOrWhiteSpace(code)) return "Script code cannot be empty.";
        int brace = 0, bracket = 0, paren = 0;
        foreach (var ch in code)
        {
            switch (ch) { case '{': brace++; break; case '}': brace--; break; case '[': bracket++; break; case ']': bracket--; break; case '(': paren++; break; case ')': paren--; break; }
            if (brace < 0) return "Syntax error: unmatched closing brace '}'.";
            if (bracket < 0) return "Syntax error: unmatched closing bracket ']'.";
            if (paren < 0) return "Syntax error: unmatched closing parenthesis ')'.";
        }
        if (brace != 0) return "Syntax error: unmatched opening brace '{'.";
        if (bracket != 0) return "Syntax error: unmatched opening bracket '['.";
        if (paren != 0) return "Syntax error: unmatched opening parenthesis '('.";
        return null;
    }
}
