@page "/tables"
@rendermode InteractiveServer
@using System
@using System.Collections.Generic
@using System.Diagnostics.CodeAnalysis
@using System.Linq
@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 id="critical-table-selector-label">Table</label>
            <div class="table-select-shell">
                <button
                    type="button"
                    class="input-shell table-select-trigger"
                    aria-haspopup="listbox"
                    aria-expanded="@isTableMenuOpen"
                    aria-labelledby="critical-table-selector-label critical-table-selector-value"
                    @onclick="ToggleTableMenu"
                    disabled="@IsTableSelectionDisabled">
                    <span class="table-select-trigger-copy">
                        <span id="critical-table-selector-value" class="table-select-trigger-title">@GetSelectedTableLabel()</span>
                    </span>
                    @if (SelectedTableReference is { } selected)
                    {
                        <span class="table-select-trigger-chips">
                            <span class="chip">@($"{selected.CurationPercentage}%")</span>
                        </span>
                    }
                </button>

                @if (isTableMenuOpen && referenceData is not null)
                {
                    <button type="button" class="table-selector-backdrop" @onclick="CloseTableMenu" aria-label="Close table selector"></button>

                    <div class="table-select-menu" role="listbox" aria-labelledby="critical-table-selector-label">
                        @foreach (var table in referenceData.CriticalTables)
                        {
                            <button
                                type="button"
                                role="option"
                                aria-selected="@string.Equals(table.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase)"
                                class="table-select-option @GetTableOptionCssClass(table)"
                                @onclick="() => SelectTableAsync(table.Key)">
                                <span class="table-select-option-main">
                                    <strong class="table-select-option-title">@table.Label</strong>
                                </span>
                                <span class="table-select-option-chips">
                                    <span class="chip">@($"{table.CurationPercentage}%")</span>
                                </span>
                            </button>
                        }
                    </div>
                }
            </div>
        </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">Use the curation action or edit action on any filled result.</p>
            </header>

            @{
                var displayColumns = GetDisplayColumns(detail);
                var gridTemplateStyle = BuildGridTemplateStyle(detail);
            }

            <div class="table-scroll">
                <div class="critical-table-grid" role="group" aria-label="@detail.DisplayName" style="@gridTemplateStyle">
                    @if (detail.Groups.Count > 0)
                    {
                        <div class="critical-table-grid-header-cell critical-table-grid-corner" aria-hidden="true"></div>
                        @foreach (var group in detail.Groups)
                        {
                            <div
                                class="critical-table-grid-header-cell critical-table-grid-group-header"
                                style="@BuildColumnSpanStyle(detail.Columns.Count)">
                                <span>@group.Label</span>
                            </div>
                        }
                    }

                    <div class="critical-table-grid-header-cell critical-table-grid-roll-band-header" aria-hidden="true"></div>
                    @foreach (var displayColumn in displayColumns)
                    {
                        <div class="critical-table-grid-header-cell critical-table-grid-column-header">
                            <span>@displayColumn.ColumnLabel</span>
                        </div>
                    }

                    @foreach (var rollBand in detail.RollBands)
                    {
                        <div class="critical-table-grid-header-cell critical-table-grid-roll-band">@rollBand.Label</div>
                        @foreach (var displayColumn in displayColumns)
                        {
                            @if (TryGetCell(rollBand.Label, displayColumn.GroupKey, displayColumn.ColumnKey, out var cell))
                            {
                                @RenderCriticalTableCell(cell)
                            }
                            else
                            {
                                @RenderEmptyCriticalTableCell()
                            }
                        }
                    }
                </div>
            </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 (isCurationOpen)
{
    <CriticalCellCurationDialog
        @key="curationModel"
        Model="curationModel"
        IsLoading="isCurationLoading"
        IsSaving="isCurationSaving"
        IsReparsing="isCurationReparsing"
        IsQuickParseMode="isCurationQuickParseMode"
        ErrorMessage="@curationError"
        QuickParseErrorMessage="@curationQuickParseError"
        LegendEntries="@(tableDetail?.Legend ?? Array.Empty<CriticalTableLegendEntry>())"
        OnClose="CloseCellCurationAsync"
        OnMarkCurated="MarkCellCuratedAsync"
        OnEdit="OpenEditorFromCurationAsync"
        OnEnterQuickParse="EnterCurationQuickParseMode"
        OnCancelQuickParse="CancelCurationQuickParseMode"
        OnReparse="ReparseCurationCellAsync" />
}

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

@code {
    private const string SelectedTableStorageKey = "rolemaster.tables.selectedTable";
    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 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;
    private CriticalCellEditorModel? editorComparisonBaselineModel;
    private bool isCurationOpen;
    private bool isCurationLoading;
    private bool isCurationSaving;
    private bool isCurationReparsing;
    private bool isCurationQuickParseMode;
    private string? curationError;
    private string? curationQuickParseError;
    private int? curatingResultId;
    private CriticalCellEditorModel? curationModel;
    private bool isTableMenuOpen;
    private bool hasResolvedStoredTableSelection;
    private CriticalTableReference? SelectedTableReference =>
        referenceData?.CriticalTables.FirstOrDefault(item => string.Equals(item.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase));

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

    private void ToggleTableMenu()
    {
        if (IsTableSelectionDisabled)
        {
            return;
        }

        isTableMenuOpen = !isTableMenuOpen;
    }

    private void CloseTableMenu()
    {
        isTableMenuOpen = false;
    }

    private async Task SelectTableAsync(string tableSlug)
    {
        selectedTableSlug = tableSlug;
        isTableMenuOpen = false;
        await PersistSelectedTableAsync(tableSlug);
        await LoadTableDetailAsync();
    }

    private async Task LoadTableDetailAsync()
    {
        if (string.IsNullOrWhiteSpace(selectedTableSlug))
        {
            tableDetail = null;
            cellIndex = null;
            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();
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!hasResolvedStoredTableSelection && referenceData?.CriticalTables.Count > 0)
        {
            try
            {
                var storedTableSlug = await JSRuntime.InvokeAsync<string?>("localStorage.getItem", SelectedTableStorageKey);
                hasResolvedStoredTableSelection = true;

                var resolvedTableSlug = ResolveSelectedTableSlug(storedTableSlug);
                if (string.IsNullOrWhiteSpace(selectedTableSlug) ||
                    !string.Equals(resolvedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase))
                {
                    selectedTableSlug = resolvedTableSlug;
                    await LoadTableDetailAsync();
                    await PersistSelectedTableAsync(selectedTableSlug);
                    await InvokeAsync(StateHasChanged);
                    return;
                }

                if (!string.Equals(storedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase))
                {
                    await PersistSelectedTableAsync(selectedTableSlug);
                }
            }
            catch (InvalidOperationException)
            {
                // During prerender localStorage is unavailable. Retry after interactive render.
            }
        }
    }

    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;
        editorComparisonBaselineModel = 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 OpenCellCurationAsync(int resultId)
    {
        if (string.IsNullOrWhiteSpace(selectedTableSlug))
        {
            return;
        }

        isCurationSaving = false;
        isCurationReparsing = false;
        isCurationQuickParseMode = false;
        isCurationOpen = true;
        await LoadCurationCellAsync(resultId, "The selected cell could not be loaded for curation.");
    }

    private async Task CloseCellCurationAsync()
    {
        isCurationOpen = false;
        isCurationLoading = false;
        isCurationSaving = false;
        isCurationReparsing = false;
        isCurationQuickParseMode = false;
        curationError = null;
        curationQuickParseError = null;
        curatingResultId = null;
        curationModel = null;
        await InvokeAsync(StateHasChanged);
    }

    private async Task MarkCellCuratedAsync()
    {
        if (curationModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || curatingResultId is null)
        {
            return;
        }

        isCurationSaving = true;
        curationError = null;

        try
        {
            curationModel.IsCurated = true;
            var response = await LookupService.UpdateCriticalCellAsync(selectedTableSlug, curatingResultId.Value, curationModel.ToRequest());
            if (response is null)
            {
                curationError = "The selected cell could not be marked curated.";
                return;
            }

            await LoadTableDetailAsync();

            var nextResultId = FindNextUncuratedResultId(curatingResultId.Value);
            if (nextResultId is null)
            {
                await CloseCellCurationAsync();
                return;
            }

            await LoadCurationCellAsync(nextResultId.Value, "The next cell could not be loaded for curation.");
        }
        catch (Exception exception)
        {
            curationError = exception.Message;
        }
        finally
        {
            isCurationSaving = false;
        }
    }

    private async Task OpenEditorFromCurationAsync()
    {
        if (curatingResultId is null)
        {
            return;
        }

        var resultId = curatingResultId.Value;
        await CloseCellCurationAsync();
        await OpenCellEditorAsync(resultId);
    }

    private Task EnterCurationQuickParseMode()
    {
        if (curationModel is null)
        {
            return Task.CompletedTask;
        }

        curationQuickParseError = null;
        isCurationQuickParseMode = true;
        return Task.CompletedTask;
    }

    private Task CancelCurationQuickParseMode()
    {
        if (isCurationReparsing)
        {
            return Task.CompletedTask;
        }

        curationQuickParseError = null;
        isCurationQuickParseMode = false;
        return Task.CompletedTask;
    }

    private async Task LoadCurationCellAsync(int resultId, string loadFailureMessage)
    {
        curationError = null;
        curationQuickParseError = null;
        curationModel = null;
        curatingResultId = resultId;
        isCurationLoading = true;
        isCurationQuickParseMode = false;
        isCurationReparsing = false;

        try
        {
            var response = await LookupService.GetCriticalCellEditorAsync(selectedTableSlug, resultId);
            if (response is null)
            {
                curationError = loadFailureMessage;
                curationModel = null;
                return;
            }

            curationModel = CriticalCellEditorModel.FromResponse(response);
        }
        catch (Exception exception)
        {
            curationError = exception.Message;
            curationModel = null;
        }
        finally
        {
            isCurationLoading = false;
        }
    }

    private async Task ReparseCurationCellAsync()
    {
        if (curationModel is null || string.IsNullOrWhiteSpace(selectedTableSlug) || curatingResultId is null)
        {
            return;
        }

        isCurationReparsing = true;
        curationQuickParseError = null;

        try
        {
            var response = await ReparseCriticalCellAsync(curationModel, curatingResultId.Value);
            if (response is null)
            {
                curationQuickParseError = "The selected cell could not be re-parsed.";
                return;
            }

            curationModel = CriticalCellEditorModel.FromResponse(response);
            isCurationQuickParseMode = false;
            await InvokeAsync(StateHasChanged);
        }
        catch (Exception exception)
        {
            curationQuickParseError = exception.Message;
        }
        finally
        {
            isCurationReparsing = false;
        }
    }

    private int? FindNextUncuratedResultId(int currentResultId)
    {
        if (tableDetail?.Cells is null || tableDetail.Cells.Count == 0)
        {
            return null;
        }

        var orderedCells = tableDetail.Cells
            .OrderBy(cell => GetGroupSortOrder(cell.GroupKey))
            .ThenBy(cell => GetColumnSortOrder(cell.ColumnKey))
            .ThenBy(cell => GetRollBandSortOrder(cell.RollBand))
            .ToList();

        var currentIndex = orderedCells.FindIndex(cell => cell.ResultId == currentResultId);
        for (var index = currentIndex + 1; index < orderedCells.Count; index++)
        {
            if (!orderedCells[index].IsCurated)
            {
                return orderedCells[index].ResultId;
            }
        }

        return null;
    }

    private int GetGroupSortOrder(string? groupKey)
    {
        if (tableDetail is null || string.IsNullOrWhiteSpace(groupKey))
        {
            return 0;
        }

        return tableDetail.Groups
            .FirstOrDefault(group => string.Equals(group.Key, groupKey, StringComparison.OrdinalIgnoreCase))
            ?.SortOrder ?? int.MaxValue;
    }

    private int GetColumnSortOrder(string columnKey)
    {
        if (tableDetail is null)
        {
            return int.MaxValue;
        }

        return tableDetail.Columns
            .FirstOrDefault(column => string.Equals(column.Key, columnKey, StringComparison.OrdinalIgnoreCase))
            ?.SortOrder ?? int.MaxValue;
    }

    private int GetRollBandSortOrder(string rollBandLabel)
    {
        if (tableDetail is null)
        {
            return int.MaxValue;
        }

        return tableDetail.RollBands
            .FirstOrDefault(rollBand => string.Equals(rollBand.Label, rollBandLabel, StringComparison.OrdinalIgnoreCase))
            ?.SortOrder ?? int.MaxValue;
    }

    private async Task CloseCellEditorAsync()
    {
        isEditorOpen = false;
        isEditorLoading = false;
        isEditorReparsing = false;
        isEditorSaving = false;
        editorLoadError = null;
        editorReparseError = null;
        editorSaveError = null;
        editingResultId = null;
        editorModel = null;
        editorComparisonBaselineModel = 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 comparisonBaseline = editorModel.Clone();
            var response = await ReparseCriticalCellAsync(editorModel, editingResultId.Value);
            if (response is null)
            {
                editorReparseError = "The selected cell could not be re-parsed.";
                return;
            }

            editorComparisonBaselineModel = comparisonBaseline;
            editorModel = CriticalCellEditorModel.FromResponse(response);
            await InvokeAsync(StateHasChanged);
        }
        catch (Exception exception)
        {
            editorReparseError = exception.Message;
        }
        finally
        {
            isEditorReparsing = false;
        }
    }

    private Task<CriticalCellEditorResponse?> ReparseCriticalCellAsync(CriticalCellEditorModel model, int resultId) =>
        LookupService.ReparseCriticalCellAsync(selectedTableSlug, resultId, model.ToRequest());

    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 static string GetCellCssClass(CriticalTableCellDetail cell) =>
        cell.IsCurated
            ? "critical-table-cell is-curated"
            : "critical-table-cell needs-curation";

    private static IReadOnlyList<(string? GroupKey, string ColumnKey, string ColumnLabel)> GetDisplayColumns(CriticalTableDetail detail)
    {
        if (detail.Groups.Count == 0)
        {
            return detail.Columns
                .Select(column => ((string?)null, column.Key, column.Label))
                .ToList();
        }

        return detail.Groups
            .SelectMany(group => detail.Columns.Select(column => ((string?)group.Key, column.Key, column.Label)))
            .ToList();
    }

    private static string BuildGridTemplateStyle(CriticalTableDetail detail)
    {
        var dataColumnCount = detail.Columns.Count * Math.Max(detail.Groups.Count, 1);
        return $"grid-template-columns: max-content repeat({dataColumnCount}, minmax(0, 1fr));";
    }

    private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};";

    private string GetSelectedTableLabel() =>
        SelectedTableReference?.Label ?? "Select a table";

    private string ResolveSelectedTableSlug(string? storedTableSlug)
    {
        if (referenceData is null || referenceData.CriticalTables.Count == 0)
        {
            return string.Empty;
        }

        if (!string.IsNullOrWhiteSpace(storedTableSlug) &&
            referenceData.CriticalTables.Any(item => string.Equals(item.Key, storedTableSlug, StringComparison.OrdinalIgnoreCase)))
        {
            return storedTableSlug;
        }

        return referenceData.CriticalTables.First().Key;
    }

    private Task PersistSelectedTableAsync(string tableSlug) =>
        JSRuntime.InvokeVoidAsync("localStorage.setItem", SelectedTableStorageKey, tableSlug).AsTask();

    private string GetTableOptionCssClass(CriticalTableReference table)
    {
        var classes = new List<string>();

        if (string.Equals(table.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase))
        {
            classes.Add("is-selected");
        }

        classes.Add(table.CurationPercentage >= 100 ? "is-curated" : "needs-curation");
        return string.Join(' ', classes);
    }

    private RenderFragment RenderCriticalTableCell(CriticalTableCellDetail cell) => @<div class="@GetCellCssClass(cell)">
        <div class="critical-table-cell-shell">
            <div class="critical-table-cell-actions">
                @if (cell.IsCurated)
                {
                    <span class="critical-cell-status-chip is-curated">Curated</span>
                }
                else
                {
                    <button
                        type="button"
                        class="critical-cell-action-button is-curation"
                        title="Open the curation preview for this cell."
                        @onclick="() => OpenCellCurationAsync(cell.ResultId)">
                        Needs Curation
                    </button>
                }

                <button
                    type="button"
                    class="critical-cell-action-button is-edit"
                    title="Open the full editor for this cell."
                    @onclick="() => OpenCellEditorAsync(cell.ResultId)">
                    Edit
                </button>
            </div>

            <CompactCriticalCell
                Description="@(cell.Description ?? string.Empty)"
                Effects="@(cell.Effects ?? Array.Empty<CriticalEffectLookupResponse>())"
                Branches="@(cell.Branches ?? Array.Empty<CriticalBranchLookupResponse>())" />
        </div>
    </div>;

    private static RenderFragment RenderEmptyCriticalTableCell() => @<div class="critical-table-cell critical-table-cell-empty">
        <span class="empty-cell">—</span>
    </div>;
}
