@page "/chrono"
@page "/chrono.html"
@implements IAsyncDisposable
@inject MatchStore Store
@inject NavigationManager Navigation

<PageTitle>ChessCubing Arena | Phase Chrono</PageTitle>
<PageBody Page="chrono" BodyClass="@ChronoBodyClass" />

@{
    var summary = Match is null ? null : BuildSummary();
    var blackZone = Match is null ? null : BuildZone(MatchEngine.ColorBlack);
    var whiteZone = Match is null ? null : BuildZone(MatchEngine.ColorWhite);
}

@if (!_ready)
{
    <main class="phase-shell chrono-stage">
        <div class="panel" style="padding: 1.5rem;">Chargement de la phase chrono...</div>
    </main>
}
else if (Match is not null && summary is not null && blackZone is not null && whiteZone is not null)
{
    <main class="phase-shell chrono-stage">
        <header class="phase-header">
            <a class="brand-link brand-link-logo" href="application.html" aria-label="Retour a l'application">
                <img class="brand-link-icon" src="logo.png" alt="Icone ChessCubing" />
                <img class="brand-link-mark" src="transparent.png" alt="Logo ChessCubing" />
            </a>
            <div class="phase-title">
                <p class="eyebrow">Phase chrono</p>
                <h1 id="chronoTitle">@summary.Title</h1>
                <p id="chronoSubtitle" class="phase-subtitle">@summary.Subtitle</p>
            </div>
            <button class="button ghost small utility-button" id="openArbiterButton" type="button" @onclick="OpenArbiterModal">
                Arbitre
            </button>
        </header>

        <section class="status-strip">
            <article class="status-card">
                <span id="blockTimerLabel">Temps Block</span>
                <strong id="blockTimer">@summary.BlockTimer</strong>
            </article>
            <article class="status-card" id="moveTimerCard" hidden="@summary.HideMoveTimer">
                <span>Temps coup</span>
                <strong id="moveTimer">@summary.MoveTimer</strong>
            </article>
            <article class="status-card wide">
                <span id="chronoCenterLabel">@summary.CenterLabel</span>
                <strong id="chronoCenterValue">@summary.CenterValue</strong>
            </article>
        </section>

        <section class="faceoff-board">
            <article class="player-zone opponent-zone @(blackZone.ActiveZone ? "active-zone" : string.Empty) @(blackZone.HasPlayerClock ? "has-player-clock" : string.Empty)" id="blackZone">
                <div class="zone-inner mirrored-mobile">
                    <div class="zone-head">
                        <div>
                            <span class="seat-tag dark-seat">Noir</span>
                            <h2 id="blackNameChrono">@blackZone.Name</h2>
                        </div>
                        <div class="zone-stats">
                            <strong id="blackMovesChrono">@blackZone.Moves</strong>
                            <span class="player-clock @(blackZone.NegativeClock ? "negative-clock" : string.Empty) @(blackZone.ActiveClock ? "active-clock" : string.Empty)" id="blackClockChrono">@blackZone.Clock</span>
                        </div>
                    </div>

                    <button class="zone-button dark-button @(blackZone.ActiveTurn ? "active-turn" : string.Empty)" id="blackMoveButton" type="button" disabled="@blackZone.Disabled" @onclick="() => HandleChronoTapAsync(MatchEngine.ColorBlack)">
                        @blackZone.ButtonText
                    </button>

                    <p class="zone-foot" id="blackHintChrono">@blackZone.Hint</p>
                </div>
            </article>

            <article class="phase-spine">
                <div class="spine-card">
                    <p class="micro-label" id="spineLabel">@summary.SpineLabel</p>
                    <strong id="spineHeadline">@summary.SpineHeadline</strong>
                    <p id="spineText">@summary.SpineText</p>
                </div>

                <button class="button primary spine-button" id="primaryChronoButton" type="button" @onclick="HandlePrimaryActionAsync">
                    @summary.PrimaryButtonText
                </button>
            </article>

            <article class="player-zone @(whiteZone.ActiveZone ? "active-zone" : string.Empty) @(whiteZone.HasPlayerClock ? "has-player-clock" : string.Empty)" id="whiteZone">
                <div class="zone-inner">
                    <div class="zone-head">
                        <div>
                            <span class="seat-tag light-seat">Blanc</span>
                            <h2 id="whiteNameChrono">@whiteZone.Name</h2>
                        </div>
                        <div class="zone-stats">
                            <strong id="whiteMovesChrono">@whiteZone.Moves</strong>
                            <span class="player-clock @(whiteZone.NegativeClock ? "negative-clock" : string.Empty) @(whiteZone.ActiveClock ? "active-clock" : string.Empty)" id="whiteClockChrono">@whiteZone.Clock</span>
                        </div>
                    </div>

                    <button class="zone-button light-button @(whiteZone.ActiveTurn ? "active-turn" : string.Empty)" id="whiteMoveButton" type="button" disabled="@whiteZone.Disabled" @onclick="() => HandleChronoTapAsync(MatchEngine.ColorWhite)">
                        @whiteZone.ButtonText
                    </button>

                    <p class="zone-foot" id="whiteHintChrono">@whiteZone.Hint</p>
                </div>
            </article>
        </section>
    </main>

    <section class="modal @(ShowArbiterModal ? string.Empty : "hidden")" id="arbiterModal" aria-hidden="@BoolString(!ShowArbiterModal)">
        <div class="modal-backdrop" @onclick="CloseArbiterModal"></div>
        <div class="modal-card">
            <div class="modal-head">
                <div>
                    <p class="eyebrow">Outils arbitre</p>
                    <h2>Controles avances</h2>
                </div>
                <button class="button ghost small" id="closeArbiterButton" type="button" @onclick="CloseArbiterModal">Fermer</button>
            </div>

            <p class="section-copy" id="arbiterStatus">@summary.ArbiterStatus</p>

            <div class="modal-actions">
                <button class="button secondary" id="arbiterPauseButton" type="button" @onclick="TogglePauseAsync">
                    Pause / reprise
                </button>
                <button class="button secondary" id="arbiterCloseBlockButton" type="button" @onclick="CloseBlockAsync">
                    Passer au cube
                </button>
                <button class="button secondary" id="arbiterTimeoutButton" type="button" hidden="@summary.HideMoveTimer" disabled="@summary.HideMoveTimer" @onclick="TimeoutMoveAsync">
                    @summary.TimeoutButtonText
                </button>
                <button class="button secondary" id="arbiterSwitchTurnButton" type="button" @onclick="SwitchTurnAsync">
                    Corriger le trait
                </button>
                <button class="button ghost" id="arbiterWhiteWinButton" type="button" @onclick="() => SetResultAsync(MatchEngine.ColorWhite)">
                    Blanc gagne
                </button>
                <button class="button ghost" id="arbiterBlackWinButton" type="button" @onclick="() => SetResultAsync(MatchEngine.ColorBlack)">
                    Noir gagne
                </button>
                <button class="button ghost danger" id="arbiterStopButton" type="button" @onclick='() => SetResultAsync("stopped")'>
                    Abandon / arret
                </button>
                <button class="button ghost" id="arbiterResetButton" type="button" @onclick="ResetMatchAsync">
                    Reinitialiser le match
                </button>
            </div>
        </div>
    </section>
}

@code {
    private CancellationTokenSource? _tickerCancellation;
    private bool _ready;
    private bool ShowArbiterModal;

    private MatchState? Match => Store.Current;

    private string ChronoBodyClass =>
        Match is not null && MatchEngine.IsTimeMode(Match)
            ? "phase-body time-mode"
            : "phase-body";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        await Store.EnsureLoadedAsync();
        _ready = true;

        if (Match is null)
        {
            Navigation.NavigateTo("/application.html", replace: true);
            return;
        }

        if (string.IsNullOrEmpty(Match.Result) && Match.Phase == MatchEngine.PhaseCube)
        {
            await Store.SaveAsync();
            Navigation.NavigateTo("/cube.html", replace: true);
            return;
        }

        _tickerCancellation = new CancellationTokenSource();
        _ = RunTickerAsync(_tickerCancellation.Token);
        StateHasChanged();
    }

    public async ValueTask DisposeAsync()
    {
        if (_tickerCancellation is not null)
        {
            _tickerCancellation.Cancel();
            _tickerCancellation.Dispose();
        }

        await Store.FlushIfDueAsync(0);
    }

    private async Task RunTickerAsync(CancellationToken cancellationToken)
    {
        try
        {
            using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(100));
            while (await timer.WaitForNextTickAsync(cancellationToken))
            {
                var match = Match;
                if (match is null)
                {
                    continue;
                }

                if (MatchEngine.SyncRunningState(match))
                {
                    Store.MarkDirty();
                }

                await Store.FlushIfDueAsync();

                if (string.IsNullOrEmpty(match.Result) && match.Phase == MatchEngine.PhaseCube)
                {
                    await Store.SaveAsync();
                    await InvokeAsync(() => Navigation.NavigateTo("/cube.html", replace: true));
                    return;
                }

                await InvokeAsync(StateHasChanged);
            }
        }
        catch (OperationCanceledException)
        {
        }
    }

    private void OpenArbiterModal()
        => ShowArbiterModal = true;

    private void CloseArbiterModal()
        => ShowArbiterModal = false;

    private async Task HandleChronoTapAsync(string color)
    {
        var match = Match;
        if (match is null || !string.IsNullOrEmpty(match.Result) || match.Phase != MatchEngine.PhaseBlock)
        {
            return;
        }

        MatchEngine.SyncRunningState(match);
        if (!match.Running || match.CurrentTurn != color)
        {
            return;
        }

        if (match.DoubleCoup.Step == 1)
        {
            MatchEngine.RegisterFreeDoubleMove(match);
        }
        else
        {
            MatchEngine.RegisterCountedMove(match, match.DoubleCoup.Step == 2 ? "double" : "standard");
        }

        await PersistAndRouteAsync();
    }

    private async Task HandlePrimaryActionAsync()
    {
        var match = Match;
        if (match is null)
        {
            return;
        }

        MatchEngine.SyncRunningState(match);
        if (!string.IsNullOrEmpty(match.Result))
        {
            Navigation.NavigateTo("/application.html");
            return;
        }

        if (match.Phase != MatchEngine.PhaseBlock)
        {
            await Store.SaveAsync();
            Navigation.NavigateTo("/cube.html", replace: true);
            return;
        }

        if (match.Running)
        {
            MatchEngine.PauseBlock(match);
        }
        else
        {
            MatchEngine.StartBlock(match);
        }

        await PersistAndRouteAsync();
    }

    private async Task TogglePauseAsync()
    {
        var match = Match;
        if (match is null || !string.IsNullOrEmpty(match.Result) || match.Phase != MatchEngine.PhaseBlock)
        {
            return;
        }

        MatchEngine.SyncRunningState(match);
        if (match.Running)
        {
            MatchEngine.PauseBlock(match);
        }
        else
        {
            MatchEngine.StartBlock(match);
        }

        await PersistAndRouteAsync();
    }

    private async Task CloseBlockAsync()
    {
        var match = Match;
        if (match is null || !string.IsNullOrEmpty(match.Result) || match.Phase != MatchEngine.PhaseBlock)
        {
            return;
        }

        MatchEngine.SyncRunningState(match);
        MatchEngine.RequestBlockClosure(match, $"Cloture manuelle {MatchEngine.GetBlockGenitivePhrase(match)} demandee par l'arbitre.");
        await PersistAndRouteAsync();
    }

    private async Task TimeoutMoveAsync()
    {
        var match = Match;
        if (match is null || !string.IsNullOrEmpty(match.Result) || match.Phase != MatchEngine.PhaseBlock || !MatchEngine.UsesMoveLimit(match))
        {
            return;
        }

        MatchEngine.SyncRunningState(match);
        MatchEngine.RegisterMoveTimeout(match, false);
        await PersistAndRouteAsync();
    }

    private async Task SwitchTurnAsync()
    {
        var match = Match;
        if (match is null || !string.IsNullOrEmpty(match.Result) || match.Phase != MatchEngine.PhaseBlock)
        {
            return;
        }

        MatchEngine.SyncRunningState(match);
        match.CurrentTurn = MatchEngine.OpponentOf(match.CurrentTurn);
        if (MatchEngine.UsesMoveLimit(match))
        {
            match.MoveRemainingMs = MatchEngine.GetMoveLimitMs(match);
        }

        MatchEngine.LogEvent(match, "Trait corrige manuellement par l'arbitre.");
        await PersistAndRouteAsync();
    }

    private async Task SetResultAsync(string result)
    {
        var match = Match;
        if (match is null)
        {
            return;
        }

        MatchEngine.SyncRunningState(match);
        MatchEngine.SetResult(match, result);
        await PersistAndRouteAsync();
    }

    private async Task ResetMatchAsync()
    {
        await Store.ClearAsync();
        Navigation.NavigateTo("/application.html", replace: true);
    }

    private async Task PersistAndRouteAsync()
    {
        Store.MarkDirty();
        await Store.SaveAsync();

        if (Match is not null && string.IsNullOrEmpty(Match.Result) && Match.Phase == MatchEngine.PhaseCube)
        {
            Navigation.NavigateTo("/cube.html", replace: true);
            return;
        }

        StateHasChanged();
    }

    private ChronoSummaryView BuildSummary()
    {
        var match = Match!;
        var blockHeading = MatchEngine.FormatBlockHeading(match, match.BlockNumber);
        var subtitle = $"{blockHeading} - {MatchEngine.Modes[match.Config.Mode].Label} - {MatchEngine.RenderModeContext(match)}";
        var hideMoveTimer = MatchEngine.IsTimeMode(match);
        var timeoutButtonText = $"Depassement {MatchEngine.FormatClock(MatchEngine.GetMoveLimitMs(match))}";

        if (!string.IsNullOrEmpty(match.Result))
        {
            return new ChronoSummaryView(
                match.Config.MatchLabel,
                subtitle,
                MatchEngine.FormatClock(match.BlockRemainingMs),
                hideMoveTimer ? "--:--" : MatchEngine.FormatClock(match.MoveRemainingMs),
                "Resultat",
                MatchEngine.ResultText(match),
                "Termine",
                MatchEngine.ResultText(match),
                "Retournez a la configuration pour lancer une nouvelle rencontre.",
                "Retour a l'accueil",
                "Le match est termine. Vous pouvez revenir a l'accueil ou reinitialiser.",
                hideMoveTimer,
                timeoutButtonText);
        }

        if (match.Running)
        {
            return new ChronoSummaryView(
                match.Config.MatchLabel,
                subtitle,
                MatchEngine.FormatClock(match.BlockRemainingMs),
                hideMoveTimer ? "--:--" : MatchEngine.FormatClock(match.MoveRemainingMs),
                "Trait",
                MatchEngine.PlayerName(match, match.CurrentTurn),
                "Chrono en cours",
                $"{blockHeading} actif",
                "Chaque joueur tape sur sa grande zone quand son coup est termine. La page cube s'ouvrira automatiquement a la fin de la phase chess.",
                "Pause arbitre",
                $"{blockHeading} en cours. Joueur au trait : {MatchEngine.PlayerName(match, match.CurrentTurn)}.",
                hideMoveTimer,
                timeoutButtonText);
        }

        return new ChronoSummaryView(
            match.Config.MatchLabel,
            subtitle,
            MatchEngine.FormatClock(match.BlockRemainingMs),
            hideMoveTimer ? "--:--" : MatchEngine.FormatClock(match.MoveRemainingMs),
            "Trait",
            MatchEngine.PlayerName(match, match.CurrentTurn),
            MatchEngine.IsTimeMode(match) ? "Etat du Block" : "Pret",
            blockHeading,
            "Demarrez le Block, puis laissez uniquement les deux grandes zones aux joueurs. La page cube prendra automatiquement le relais.",
            "Demarrer le Block",
            $"{blockHeading} pret. {MatchEngine.PlayerName(match, match.CurrentTurn)} commencera.",
            hideMoveTimer,
            timeoutButtonText);
    }

    private ChronoZoneView BuildZone(string color)
    {
        var match = Match!;
        var active = match.CurrentTurn == color;
        var clockValue = match.Clocks is not null
            ? color == MatchEngine.ColorWhite ? match.Clocks.White : match.Clocks.Black
            : 0;

        var clockText = match.Clocks is not null
            ? MatchEngine.FormatSignedClock(clockValue)
            : $"Dernier cube {MatchEngine.RenderLastCube(match, color)}";

        if (!string.IsNullOrEmpty(match.Result))
        {
            return new ChronoZoneView(
                MatchEngine.PlayerName(match, color),
                $"{(color == MatchEngine.ColorWhite ? match.Moves.White : match.Moves.Black)} / {match.Quota}",
                clockText,
                MatchEngine.ResultText(match),
                "Le match est termine.",
                true,
                false,
                false,
                match.Clocks is not null,
                match.Clocks is not null && clockValue < 0,
                false);
        }

        if (!match.Running)
        {
            return new ChronoZoneView(
                MatchEngine.PlayerName(match, color),
                $"{(color == MatchEngine.ColorWhite ? match.Moves.White : match.Moves.Black)} / {match.Quota}",
                clockText,
                "Block en pause",
                active
                    ? $"{MatchEngine.GetBlockPhrase(match)} n'a pas encore demarre ou a ete mis en pause."
                    : $"{MatchEngine.PlayerName(match, match.CurrentTurn)} reprendra au demarrage.",
                true,
                false,
                active,
                match.Clocks is not null,
                match.Clocks is not null && clockValue < 0,
                false);
        }

        if (!active)
        {
            return new ChronoZoneView(
                MatchEngine.PlayerName(match, color),
                $"{(color == MatchEngine.ColorWhite ? match.Moves.White : match.Moves.Black)} / {match.Quota}",
                clockText,
                "Attends",
                $"{MatchEngine.PlayerName(match, match.CurrentTurn)} est en train de jouer.",
                true,
                false,
                false,
                match.Clocks is not null,
                match.Clocks is not null && clockValue < 0,
                false);
        }

        if (match.DoubleCoup.Step == 1)
        {
            return new ChronoZoneView(
                MatchEngine.PlayerName(match, color),
                $"{(color == MatchEngine.ColorWhite ? match.Moves.White : match.Moves.Black)} / {match.Quota}",
                clockText,
                "1er coup gratuit",
                "Ce coup ne compte pas et ne doit pas donner echec.",
                false,
                true,
                true,
                match.Clocks is not null,
                match.Clocks is not null && clockValue < 0,
                match.Clocks is not null);
        }

        if (match.DoubleCoup.Step == 2)
        {
            return new ChronoZoneView(
                MatchEngine.PlayerName(match, color),
                $"{(color == MatchEngine.ColorWhite ? match.Moves.White : match.Moves.Black)} / {match.Quota}",
                clockText,
                "2e coup du double",
                "Ce coup compte dans le quota et l'echec redevient autorise.",
                false,
                true,
                true,
                match.Clocks is not null,
                match.Clocks is not null && clockValue < 0,
                match.Clocks is not null);
        }

        return new ChronoZoneView(
            MatchEngine.PlayerName(match, color),
            $"{(color == MatchEngine.ColorWhite ? match.Moves.White : match.Moves.Black)} / {match.Quota}",
            clockText,
            "J'ai fini mon coup",
            "Tape des que ton coup est joue sur l'echiquier.",
            false,
            true,
            true,
            match.Clocks is not null,
            match.Clocks is not null && clockValue < 0,
            match.Clocks is not null);
    }

    private static string BoolString(bool value)
        => value ? "true" : "false";

    private sealed record ChronoSummaryView(
        string Title,
        string Subtitle,
        string BlockTimer,
        string MoveTimer,
        string CenterLabel,
        string CenterValue,
        string SpineLabel,
        string SpineHeadline,
        string SpineText,
        string PrimaryButtonText,
        string ArbiterStatus,
        bool HideMoveTimer,
        string TimeoutButtonText);

    private sealed record ChronoZoneView(
        string Name,
        string Moves,
        string Clock,
        string ButtonText,
        string Hint,
        bool Disabled,
        bool ActiveTurn,
        bool ActiveZone,
        bool HasPlayerClock,
        bool NegativeClock,
        bool ActiveClock);
}
