using DodoSSH.Client.Terminal;

namespace DodoSSH.Client.Android.Platform;

/// <summary>
/// Keeps <see cref="SessionForegroundService"/> in step with what is actually running.
/// </summary>
/// <remarks>
/// <para>
/// The service is started and stopped from one place, and that place is a count rather than a lifecycle.
/// Anything else drifts: a service started when a shell opens and stopped when a tab closes would leave
/// the notification up after the last shell died on its own, and a phone showing "1 shell connected" over
/// nothing is the same dishonesty the unlock screen's shell count exists to avoid.
/// </para>
/// <para>
/// <see cref="TerminalWorkspace.LiveSessionCount"/> is deliberately the source of truth rather than a
/// tally kept here. It already knows that a session whose shell exited half an hour ago is not live, which
/// a counter incremented on open and decremented on close would not.
/// </para>
/// </remarks>
internal sealed class SessionKeepAlive : IDisposable
{
    private readonly TerminalWorkspace workspace;
    private readonly Func<int> activeTransfers;

    /// <param name="workspace">The live shells.</param>
    /// <param name="activeTransfers">
    /// How many transfers are moving bytes. A delegate rather than a queue, because file transfer is out
    /// of this head's first scope — see the decision in docs/android-port.md — and this is the seam it
    /// will arrive through rather than a dependency taken before there is anything to depend on.
    /// </param>
    public SessionKeepAlive(TerminalWorkspace workspace, Func<int> activeTransfers)
    {
        this.workspace = workspace;
        this.activeTransfers = activeTransfers;

        // Raised on whatever thread the pump unwound on, which is fine: starting and stopping a service is
        // a binder call and needs no particular thread. Nothing here touches the interface.
        workspace.SessionEnded += OnSessionEnded;
    }

    /// <summary>Re-reads the counts and starts or stops the service to match.</summary>
    /// <remarks>
    /// Called after anything that could change either count — opening a shell, closing a tab, a transfer
    /// finishing. Calling it when nothing changed is free: reconciling to the state it is already in is
    /// either a redundant <c>startForegroundService</c> on a running service or a <c>stopService</c> on a
    /// stopped one, and Android treats both as no-ops.
    /// </remarks>
    public void Refresh() =>
        SessionForegroundService.Reconcile(workspace.LiveSessionCount, activeTransfers());

    /// <inheritdoc />
    public void Dispose()
    {
        workspace.SessionEnded -= OnSessionEnded;

        // The notification goes with the composition root. Leaving it up over a process that is shutting
        // down is how an SSH client acquires a reputation for a notification you cannot get rid of.
        SessionForegroundService.Reconcile(0, 0);
    }

    private void OnSessionEnded(object? sender, TerminalSessionEndedEventArgs e) => Refresh();
}
