namespace RioJoy.Core.Profiles;

/// <summary>Supplies the current foreground application's executable name.</summary>
public interface IForegroundProcessProvider
{
    /// <summary>The foreground executable name (e.g. "game.exe"), or null if unknown.</summary>
    string? GetForegroundExecutable();
}

/// <summary>
/// Polls the foreground process and resolves the three-state auto-switch decision,
/// raising <see cref="DecisionChanged"/> only when the decision actually changes.
/// The polling cadence is driven by the host; <see cref="Poll"/> performs one
/// resolution so the logic is deterministic and unit-testable.
/// </summary>
public sealed class AutoSwitchWatcher
{
    private readonly IForegroundProcessProvider _provider;
    private readonly Func<AppConfig> _configAccessor;
    private SwitchDecision? _last;

    public AutoSwitchWatcher(IForegroundProcessProvider provider, Func<AppConfig> configAccessor)
    {
        _provider = provider ?? throw new ArgumentNullException(nameof(provider));
        _configAccessor = configAccessor ?? throw new ArgumentNullException(nameof(configAccessor));
    }

    /// <summary>Raised when the resolved decision differs from the previous one.</summary>
    public event Action<SwitchDecision>? DecisionChanged;

    /// <summary>The most recently resolved decision (null until the first <see cref="Poll"/>).</summary>
    public SwitchDecision? Current => _last;

    /// <summary>
    /// Forget the last decision so the next <see cref="Poll"/> re-raises
    /// <see cref="DecisionChanged"/> even if the resolved decision is unchanged. Used
    /// after a manual/editor override to re-sync the coordinator with the foreground app.
    /// </summary>
    public void Reset() => _last = null;

    /// <summary>Resolve once; raise <see cref="DecisionChanged"/> if it changed. Returns the decision.</summary>
    public SwitchDecision Poll()
    {
        string? exe = _provider.GetForegroundExecutable();
        SwitchDecision decision = AutoSwitchResolver.Resolve(_configAccessor(), exe);

        if (_last is null || !_last.Value.Equals(decision))
        {
            _last = decision;
            DecisionChanged?.Invoke(decision);
        }

        return decision;
    }

    /// <summary>Poll on <paramref name="interval"/> until cancelled.</summary>
    public async Task RunAsync(TimeSpan interval, CancellationToken cancellationToken)
    {
        // net48 has no PeriodicTimer; poll on a Task.Delay loop instead.
        Poll();
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                await Compat.TaskCompat.Delay(interval, cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                break;
            }
            Poll();
        }
    }
}
