using System.Globalization;
using RioJoy.Core.Profiles;
namespace RioJoy.Core.Hosting;
///
/// The process a pod-bundled RIOJoy lives alongside (--exit-with):
/// either a PID or an executable name, normalized the same way auto-switch
/// triggers are (basename, no .exe, lower-case) so launch scripts can
/// pass whatever they have.
///
public sealed record CompanionTarget
{
public int? Pid { get; init; }
/// Normalized executable name (when is null).
public string? Name { get; init; }
public static CompanionTarget Parse(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Companion target is required.", nameof(value));
return int.TryParse(value.Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out int pid)
? new CompanionTarget { Pid = pid }
: new CompanionTarget { Name = AutoSwitchResolver.Normalize(value) };
}
public override string ToString() => Pid is int p ? $"pid {p}" : Name ?? "?";
}
///
/// Pure decision core of --exit-with: RIOJoy should exit once its
/// companion game has run and then gone away. Launch order is not guaranteed
/// (the pod start script fires both), so a companion that has never
/// been seen only triggers exit after a startup grace — covering both "game
/// still loading" and "game failed to launch, don't linger forever". The
/// caller polls (the tray's 1 s timer) and supplies elapsed time, so this
/// stays clock-free and unit-testable.
///
public sealed class CompanionExit
{
public static readonly TimeSpan DefaultStartupGrace = TimeSpan.FromSeconds(60);
private readonly TimeSpan _grace;
private bool _seen;
public CompanionExit(TimeSpan? startupGrace = null)
{
_grace = startupGrace ?? DefaultStartupGrace;
}
/// True once RIOJoy should tear down and exit.
public bool ShouldExit(bool companionRunning, TimeSpan elapsed)
{
if (companionRunning)
{
_seen = true;
return false;
}
return _seen || elapsed >= _grace;
}
}