@page "/tables"
@rendermode InteractiveServer
@using System
@using System.Linq
@inject NavigationManager NavigationManager
@inject LookupService LookupService
@inject RolemasterDb.App.Frontend.AppState.PinnedTablesState PinnedTablesState
@inject RolemasterDb.App.Frontend.AppState.RecentTablesState RecentTablesState
@inject RolemasterDb.App.Frontend.AppState.TableContextState TableContextState

<PageTitle>Critical Tables</PageTitle>

<section class="panel tables-page">
    <TablesPageHeader />

    @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
    {
        <div class="tables-reference-layout">
            <aside class="tables-reference-rail">
                <TablesIndexRail
                    Tables="referenceData.CriticalTables"
                    SelectedTableSlug="selectedTableSlug"
                    PinnedTableSlugs="PinnedTablesState.Items.Select(item => item.Slug).ToArray()"
                    RecentTableSlugs="RecentTablesState.Items.Select(item => item.Slug).ToArray()"
                    IsPinned="PinnedTablesState.IsPinned"
                    OnSelectTable="SelectTableAsync" />
            </aside>

            <div class="tables-reference-main">
                @if (!hasResolvedStoredTableSelection)
                {
                    <p class="muted">Restoring table context...</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)
                {
                    <div class="table-shell">
                        <TablesContextBar
                            Detail="detail"
                            IsPinned="PinnedTablesState.IsPinned(detail.Slug)"
                            CurrentMode="referenceMode"
                            SelectedGroupKey="selectedGroupKey"
                            SelectedColumnKey="selectedColumnKey"
                            RollJumpValue="rollJumpValue"
                            DensityMode="densityMode"
                            OnTogglePin="TogglePinnedTableAsync"
                            OnModeChanged="UpdateReferenceModeAsync"
                            OnGroupChanged="UpdateSelectedGroupAsync"
                            OnColumnChanged="UpdateSelectedColumnAsync"
                            OnRollJumpChanged="UpdateRollJumpAsync"
                            OnDensityChanged="UpdateDensityModeAsync" />

                        <TablesCanvas
                            Detail="detail"
                            CurrentMode="referenceMode"
                            SelectedGroupKey="selectedGroupKey"
                            SelectedColumnKey="selectedColumnKey"
                            RollJumpValue="rollJumpValue"
                            DensityMode="densityMode"
                            SelectedCell="selectedCell"
                            OnSelectCell="SelectCell"
                            OnOpenCuration="OpenCellCurationAsync"
                            OnOpenEditor="OpenCellEditorAsync" />
                    </div>
                }
            </div>

            @if (tableDetail is not null)
            {
                <aside class="tables-reference-inspector-shell">
                    <TablesInspector SelectedCellDetail="SelectedCellDetail" />
                </aside>
            }
        </div>

        <TablesInspectorSheet SelectedCellDetail="SelectedCellDetail" OnClose="ClearSelectedCell" />
    }
</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 ContextDestination = "tables";
    private LookupReferenceData? referenceData;
    private CriticalTableDetail? tableDetail;
    private string selectedTableSlug = string.Empty;
    private bool isDetailLoading;
    private bool isReferenceDataLoading = true;
    private string? detailError;
    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 string referenceMode = TablesReferenceMode.Reference;
    private string selectedGroupKey = string.Empty;
    private string selectedColumnKey = string.Empty;
    private string rollJumpValue = string.Empty;
    private string densityMode = TablesDensityMode.Comfortable;
    private TablesCellSelection? selectedCell;
    private bool hasResolvedStoredTableSelection;
    private CriticalTableReference? SelectedTableReference =>
        referenceData?.CriticalTables.FirstOrDefault(item => string.Equals(item.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase));
    private CriticalTableCellDetail? SelectedCellDetail =>
        selectedCell is null
            ? null
            : tableDetail?.Cells.FirstOrDefault(cell => cell.ResultId == selectedCell.ResultId);

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

    private async Task SelectTableAsync(string tableSlug)
    {
        selectedTableSlug = tableSlug;
        await LoadTableDetailAsync();
        await PersistAndSyncTableContextAsync();
    }

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

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

        try
        {
            tableDetail = await LookupService.GetCriticalTableAsync(selectedTableSlug);
            if (tableDetail is null)
            {
                detailError = "The selected table could not be loaded.";
                NormalizeViewStateForCurrentDetail();
                return;
            }

            await RecordRecentTableVisitAsync();
            NormalizeViewStateForCurrentDetail();
        }
        catch (Exception exception)
        {
            detailError = exception.Message;
            NormalizeViewStateForCurrentDetail();
        }
        finally
        {
            isDetailLoading = false;
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await RecentTablesState.InitializeAsync();
            await PinnedTablesState.InitializeAsync();
        }

        if (!hasResolvedStoredTableSelection && referenceData?.CriticalTables.Count > 0)
        {
            var initialContext = await TableContextState.RestoreAsync(
                NavigationManager.Uri,
                ContextDestination,
                referenceData.CriticalTables,
                RolemasterDb.App.Frontend.AppState.TableContextMode.Reference);

            hasResolvedStoredTableSelection = true;

            var resolvedTableSlug = initialContext.TableSlug ?? string.Empty;
            if (string.IsNullOrWhiteSpace(selectedTableSlug) ||
                !string.Equals(resolvedTableSlug, selectedTableSlug, StringComparison.OrdinalIgnoreCase))
            {
                selectedTableSlug = resolvedTableSlug;
                await LoadTableDetailAsync();
                await PersistAndSyncTableContextAsync();
                await InvokeAsync(StateHasChanged);
                return;
            }

            await PersistAndSyncTableContextAsync();
        }
    }

    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 Task TogglePinnedTableAsync()
    {
        if (SelectedTableReference is not { } selectedTable)
        {
            return Task.CompletedTask;
        }

        return PinnedTablesState.ToggleAsync(
            selectedTable.Key,
            selectedTable.Label,
            selectedTable.Family,
            selectedTable.CurationPercentage);
    }

    private Task RecordRecentTableVisitAsync()
    {
        if (SelectedTableReference is not { } selectedTable)
        {
            return Task.CompletedTask;
        }

        return RecentTablesState.RecordVisitAsync(
            selectedTable.Key,
            selectedTable.Label,
            selectedTable.Family,
            selectedTable.CurationPercentage);
    }

    private async Task PersistAndSyncTableContextAsync()
    {
        var snapshot = BuildCurrentTableContext();
        await TableContextState.PersistAsync(ContextDestination, snapshot);

        var targetUri = TableContextState.BuildUri("/tables", snapshot);
        if (string.Equals(NavigationManager.ToBaseRelativePath(NavigationManager.Uri), targetUri.TrimStart('/'), StringComparison.Ordinal))
        {
            return;
        }

        NavigationManager.NavigateTo(targetUri, replace: true);
    }

    private RolemasterDb.App.Frontend.AppState.TableContextSnapshot BuildCurrentTableContext() =>
        new(
            TableSlug: selectedTableSlug,
            Mode: RolemasterDb.App.Frontend.AppState.TableContextMode.Reference);

    private Task UpdateReferenceModeAsync(string mode)
    {
        referenceMode = NormalizeMode(mode);
        return Task.CompletedTask;
    }

    private Task UpdateSelectedGroupAsync(string groupKey)
    {
        selectedGroupKey = NormalizeOptionalFilter(groupKey);
        return Task.CompletedTask;
    }

    private Task UpdateSelectedColumnAsync(string columnKey)
    {
        selectedColumnKey = NormalizeOptionalFilter(columnKey);
        return Task.CompletedTask;
    }

    private Task UpdateRollJumpAsync(string rollValue)
    {
        rollJumpValue = NormalizeRollInput(rollValue);
        return Task.CompletedTask;
    }

    private Task UpdateDensityModeAsync(string mode)
    {
        densityMode = NormalizeDensityMode(mode);
        return Task.CompletedTask;
    }

    private void SelectCell(TablesCellSelection selection)
    {
        if (tableDetail?.Cells.Any(cell => cell.ResultId == selection.ResultId) != true)
        {
            selectedCell = null;
            return;
        }

        selectedCell = selection;
    }

    private Task ClearSelectedCell()
    {
        selectedCell = null;
        return Task.CompletedTask;
    }

    private void NormalizeViewStateForCurrentDetail()
    {
        referenceMode = NormalizeMode(referenceMode);

        if (tableDetail is null)
        {
            selectedGroupKey = string.Empty;
            selectedColumnKey = string.Empty;
            rollJumpValue = string.Empty;
            densityMode = NormalizeDensityMode(densityMode);
            selectedCell = null;
            return;
        }

        if (tableDetail.Groups.All(group => !string.Equals(group.Key, selectedGroupKey, StringComparison.OrdinalIgnoreCase)))
        {
            selectedGroupKey = string.Empty;
        }

        if (tableDetail.Columns.All(column => !string.Equals(column.Key, selectedColumnKey, StringComparison.OrdinalIgnoreCase)))
        {
            selectedColumnKey = string.Empty;
        }

        rollJumpValue = NormalizeRollInput(rollJumpValue);
        densityMode = NormalizeDensityMode(densityMode);

        if (selectedCell is not null && tableDetail.Cells.All(cell => cell.ResultId != selectedCell.ResultId))
        {
            selectedCell = null;
        }
    }

    private static string NormalizeMode(string? mode) =>
        mode switch
        {
            TablesReferenceMode.NeedsCuration => TablesReferenceMode.NeedsCuration,
            TablesReferenceMode.Curated => TablesReferenceMode.Curated,
            _ => TablesReferenceMode.Reference
        };

    private static string NormalizeOptionalFilter(string? value) =>
        string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();

    private static string NormalizeDensityMode(string? mode) =>
        string.Equals(mode, TablesDensityMode.Dense, StringComparison.Ordinal)
            ? TablesDensityMode.Dense
            : TablesDensityMode.Comfortable;

    private static string NormalizeRollInput(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        var digitsOnly = new string(value.Where(char.IsDigit).ToArray());
        return digitsOnly.Length == 0 ? string.Empty : digitsOnly;
    }
}
