@page "/tables"
@rendermode InteractiveServer
@using System
@using System.Collections.Generic
@using System.Diagnostics.CodeAnalysis
@inject IJSRuntime JSRuntime
@inject LookupService LookupService

<PageTitle>Critical Tables</PageTitle>

<section class="panel tables-page">
    <div class="table-browser-toolbar">
        <div class="table-selector">
            <label for="critical-table-select">Table</label>
            <select
                id="critical-table-select"
                class="input-shell"
                value="@selectedTableSlug"
                @onchange="HandleTableChanged"
                disabled="@IsTableSelectionDisabled">
                @if (referenceData is null)
                {
                    <option value="">Loading tables...</option>
                }
                else
                {
                    @foreach (var table in referenceData.CriticalTables)
                    {
                        <option value="@table.Key">@table.Label</option>
                    }
                }
            </select>
        </div>

        <p class="table-browser-toolbar-copy">Choose a table, then read from the roll band on the left across to the result you need.</p>
    </div>

    @if (referenceData is null)
    {
        <p class="muted">Loading table list...</p>
    }
    else if (!referenceData.CriticalTables.Any())
    {
        <p class="muted">No critical tables are available yet.</p>
    }
    else if (isDetailLoading)
    {
        <p class="muted">Loading the selected table...</p>
    }
    else if (!string.IsNullOrWhiteSpace(detailError))
    {
        <p class="error-text">@detailError</p>
    }
    else if (tableDetail is null)
    {
        <p class="muted">The selected table could not be loaded.</p>
    }
    else if (tableDetail is { } detail)
    {
        var readingHint = detail.Groups.Count > 0
            ? "Find the roll band on the left, then read across to the group and severity you need."
            : "Find the roll band on the left, then read across to the severity you need.";

        <div class="table-shell">
            <header class="table-browser-header">
                <div>
                    <h2 class="panel-title">@detail.DisplayName</h2>
                    <p class="table-browser-reading-hint">@readingHint</p>
                </div>
                <p class="table-browser-edit-hint">Click any filled result to correct it.</p>
            </header>

            <div class="table-scroll">
                <table class="critical-table">
                    <thead>
                        @if (detail.Groups.Count > 0)
                        {
                            <tr>
                                <th class="roll-band-header" rowspan="2"></th>
                                @foreach (var group in detail.Groups)
                                {
                                    <th colspan="@detail.Columns.Count">@group.Label</th>
                                }
                            </tr>
                            <tr>
                                @foreach (var group in detail.Groups)
                                {
                                    foreach (var column in detail.Columns)
                                    {
                                        <th>
                                            <span>@column.Label</span>
                                        </th>
                                    }
                                }
                            </tr>
                        }
                        else
                        {
                            <tr>
                                <th class="roll-band-header"></th>
                                @foreach (var column in detail.Columns)
                                {
                                    <th>
                                        <span>@column.Label</span>
                                    </th>
                                }
                            </tr>
                        }
                    </thead>
                    <tbody>
                        @foreach (var rollBand in detail.RollBands)
                        {
                            <tr>
                                <th class="roll-band-header">@rollBand.Label</th>
                                @if (detail.Groups.Count > 0)
                                {
                                    foreach (var group in detail.Groups)
                                    {
                                        foreach (var column in detail.Columns)
                                        {
                                            @if (TryGetCell(rollBand.Label, group.Key, column.Key, out var groupedCell))
                                            {
                                                <td
                                                    class="critical-table-cell is-editable"
                                                    tabindex="0"
                                                    title="Click to edit this cell"
                                                    @onclick="() => OpenCellEditorAsync(groupedCell.ResultId)"
                                                    @onkeydown="args => HandleCellKeyDownAsync(args, groupedCell.ResultId)">
                                                    <CompactCriticalCell
                                                        Description="@(groupedCell.Description ?? string.Empty)"
                                                        Effects="@(groupedCell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
                                                        Branches="@(groupedCell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" />
                                                </td>
                                            }
                                            else
                                            {
                                                <td class="critical-table-cell">
                                                    <span class="empty-cell">—</span>
                                                </td>
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    foreach (var column in detail.Columns)
                                    {
                                        @if (TryGetCell(rollBand.Label, null, column.Key, out var cell))
                                        {
                                            <td
                                                class="critical-table-cell is-editable"
                                                tabindex="0"
                                                title="Click to edit this cell"
                                                @onclick="() => OpenCellEditorAsync(cell.ResultId)"
                                                @onkeydown="args => HandleCellKeyDownAsync(args, cell.ResultId)">
                                                <CompactCriticalCell
                                                    Description="@(cell.Description ?? string.Empty)"
                                                    Effects="@(cell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
                                                    Branches="@(cell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" />
                                            </td>
                                        }
                                        else
                                        {
                                            <td class="critical-table-cell">
                                                <span class="empty-cell">—</span>
                                            </td>
                                        }
                                    }
                                }
                            </tr>
                        }
                    </tbody>
                </table>
            </div>

            @{
                var legendEntries = detail.Legend ?? Array.Empty<CriticalTableLegendEntry>();
            }

            @if (legendEntries.Count > 0)
            {
                <div class="critical-legend">
                    <div class="critical-legend-header">
                        <h4>Reading help</h4>
                        <p class="muted">These symbols show the effects attached to a result at a glance.</p>
                    </div>
                    <div class="legend-grid">
                        @foreach (var entry in legendEntries)
                        {
                            <div class="legend-item" title="@entry.Tooltip">
                                <span class="legend-symbol">@entry.Symbol</span>
                                <div>
                                    <strong>@entry.Label</strong>
                                    <span class="muted">@entry.Description</span>
                                </div>
                            </div>
                        }
                    </div>
                </div>
            }
        </div>
    }
</section>

@if (isEditorOpen)
{
    <CriticalCellEditorDialog
        Model="editorModel"
        IsLoading="isEditorLoading"
        IsReparsing="isEditorReparsing"
        IsSaving="isEditorSaving"
        LoadErrorMessage="@editorLoadError"
        ReparseErrorMessage="@editorReparseError"
        SaveErrorMessage="@editorSaveError"
        OnClose="CloseCellEditorAsync"
        OnReparse="ReparseCellEditorAsync"
        OnSave="SaveCellEditorAsync" />
}

@code {
    private LookupReferenceData? referenceData;
    private CriticalTableDetail? tableDetail;
    private string selectedTableSlug = string.Empty;
    private bool isDetailLoading;
    private bool isReferenceDataLoading = true;
    private string? detailError;
    private Dictionary<(string RollBand, string? GroupKey, string ColumnKey), CriticalTableCellDetail>? cellIndex;
    private int tableLayoutVersion;
    private int appliedLayoutVersion = -1;
    private bool IsTableSelectionDisabled => isReferenceDataLoading || (referenceData?.CriticalTables.Count ?? 0) == 0;
    private bool isEditorOpen;
    private bool isEditorLoading;
    private bool isEditorReparsing;
    private bool isEditorSaving;
    private string? editorLoadError;
    private string? editorReparseError;
    private string? editorSaveError;
    private int? editingResultId;
    private CriticalCellEditorModel? editorModel;

    protected override async Task OnInitializedAsync()
    {
        referenceData = await LookupService.GetReferenceDataAsync();
        isReferenceDataLoading = false;

        selectedTableSlug = referenceData?.CriticalTables.FirstOrDefault()?.Key ?? string.Empty;
        await LoadTableDetailAsync();
    }

    private async Task HandleTableChanged(ChangeEventArgs args)
    {
        selectedTableSlug = args.Value?.ToString() ?? string.Empty;
        await LoadTableDetailAsync();
    }

    private async Task LoadTableDetailAsync()
    {
        if (string.IsNullOrWhiteSpace(selectedTableSlug))
        {
            tableDetail = null;
            cellIndex = null;
            tableLayoutVersion++;
            return;
        }

        isDetailLoading = true;
        detailError = null;
        tableDetail = null;
        cellIndex = null;

        try
        {
            tableDetail = await LookupService.GetCriticalTableAsync(selectedTableSlug);
            if (tableDetail is null)
            {
                detailError = "The selected table could not be loaded.";
            }
        }
        catch (Exception exception)
        {
            detailError = exception.Message;
        }
        finally
        {
            isDetailLoading = false;
            BuildCellIndex();
            tableLayoutVersion++;
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (tableDetail is null || appliedLayoutVersion == tableLayoutVersion)
        {
            return;
        }

        await JSRuntime.InvokeVoidAsync("rolemasterTables.alignCriticalCells");
        appliedLayoutVersion = tableLayoutVersion;
    }

    private void BuildCellIndex()
    {
        if (tableDetail?.Cells is null)
        {
            cellIndex = null;
            return;
        }

        cellIndex = new Dictionary<(string, string?, string), CriticalTableCellDetail>();
        foreach (var cell in tableDetail.Cells)
        {
            cellIndex[(cell.RollBand, cell.GroupKey, cell.ColumnKey)] = cell;
        }
    }

    private bool TryGetCell(string rollBand, string? groupKey, string columnKey, [NotNullWhen(true)] out CriticalTableCellDetail? cell)
    {
        if (cellIndex is null)
        {
            cell = null;
            return false;
        }

        return cellIndex.TryGetValue((rollBand, groupKey, columnKey), out cell);
    }

    private async Task OpenCellEditorAsync(int resultId)
    {
        if (string.IsNullOrWhiteSpace(selectedTableSlug))
        {
            return;
        }

        editorLoadError = null;
        editorReparseError = null;
        editorSaveError = null;
        editorModel = null;
        editingResultId = resultId;
        isEditorReparsing = false;
        isEditorSaving = false;
        isEditorLoading = true;
        isEditorOpen = true;

        try
        {
            var response = await LookupService.GetCriticalCellEditorAsync(selectedTableSlug, resultId);
            if (response is null)
            {
                editorLoadError = "The selected cell could not be loaded for editing.";
                editorModel = null;
                return;
            }

            editorModel = CriticalCellEditorModel.FromResponse(response);
        }
        catch (Exception exception)
        {
            editorLoadError = exception.Message;
            editorModel = null;
        }
        finally
        {
            isEditorLoading = false;
        }
    }

    private async Task CloseCellEditorAsync()
    {
        isEditorOpen = false;
        isEditorLoading = false;
        isEditorReparsing = false;
        isEditorSaving = false;
        editorLoadError = null;
        editorReparseError = null;
        editorSaveError = null;
        editingResultId = null;
        editorModel = null;
        await InvokeAsync(StateHasChanged);
    }

    private async Task ReparseCellEditorAsync()
    {
        if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null)
        {
            return;
        }

        isEditorReparsing = true;
        editorReparseError = null;

        try
        {
            var response = await LookupService.ReparseCriticalCellAsync(selectedTableSlug, editingResultId.Value, editorModel.RawCellText);
            if (response is null)
            {
                editorReparseError = "The selected cell could not be re-parsed.";
                return;
            }

            editorModel = CriticalCellEditorModel.FromResponse(response);
        }
        catch (Exception exception)
        {
            editorReparseError = exception.Message;
        }
        finally
        {
            isEditorReparsing = false;
        }
    }

    private async Task SaveCellEditorAsync()
    {
        if (editorModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || editingResultId is null)
        {
            return;
        }

        isEditorSaving = true;
        editorSaveError = null;

        try
        {
            var response = await LookupService.UpdateCriticalCellAsync(selectedTableSlug, editingResultId.Value, editorModel.ToRequest());
            if (response is null)
            {
                editorSaveError = "The selected cell could not be saved.";
                return;
            }

            await LoadTableDetailAsync();
            await CloseCellEditorAsync();
        }
        catch (Exception exception)
        {
            editorSaveError = exception.Message;
        }
        finally
        {
            isEditorSaving = false;
        }
    }

    private async Task HandleCellKeyDownAsync(KeyboardEventArgs args, int resultId)
    {
        if (args.Key is "Enter" or " ")
        {
            await OpenCellEditorAsync(resultId);
        }
    }
}
