namespace RioJoy.Core.Profiles;

/// <summary>The three states of the serial-port yield / profile auto-switch.</summary>
public enum SwitchMode
{
    /// <summary>A native game is foreground — release the COM port and go dormant.</summary>
    Yield,

    /// <summary>A supported non-native game is foreground — run its profile.</summary>
    Activate,

    /// <summary>Nothing matched — idle (port released or a neutral default).</summary>
    Idle,
}

/// <summary>The decision produced by <see cref="AutoSwitchResolver"/>.</summary>
public readonly struct SwitchDecision : IEquatable<SwitchDecision>
{
    public SwitchMode Mode { get; }

    /// <summary>The profile to run (set when <see cref="Mode"/> is <see cref="SwitchMode.Activate"/>).</summary>
    public RioProfile? Profile { get; }

    private SwitchDecision(SwitchMode mode, RioProfile? profile)
    {
        Mode = mode;
        Profile = profile;
    }

    public static SwitchDecision Yield() => new(SwitchMode.Yield, null);
    public static SwitchDecision Idle() => new(SwitchMode.Idle, null);
    public static SwitchDecision Activate(RioProfile profile) => new(SwitchMode.Activate, profile);

    public bool Equals(SwitchDecision other) => Mode == other.Mode && ReferenceEquals(Profile, other.Profile);
    public override bool Equals(object? obj) => obj is SwitchDecision d && Equals(d);
    // Manual combine: HashCode.Combine needs Microsoft.Bcl.HashCode, which has
    // no net40 build (Windows XP flavor).
    public override int GetHashCode() =>
        ((int)Mode * 397) ^ (Profile?.GetHashCode() ?? 0);
    public override string ToString() => Mode == SwitchMode.Activate ? $"Activate({Profile?.Name})" : Mode.ToString();
}

/// <summary>
/// Resolves the foreground executable into a <see cref="SwitchDecision"/>, the
/// pure core of the auto-switch state machine (see docs/PLAN.md §Profiles).
/// Precedence: a native game always wins (yield); otherwise the first profile that
/// lists the executable activates; otherwise the neutral profile (if any) or idle.
/// </summary>
public static class AutoSwitchResolver
{
    /// <summary>Normalize an executable name for matching: basename, no ".exe", lower-case.</summary>
    public static string Normalize(string executable)
    {
        if (executable is null) throw new ArgumentNullException(nameof(executable));
        string name = executable.Trim();
        // Strip any path (handle both separators regardless of OS).
        int slash = name.LastIndexOfAny(new[] { '/', '\\' });
        if (slash >= 0)
            name = name[(slash + 1)..];
        if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
            name = name[..^4];
        return name.ToLowerInvariant();
    }

    private static bool Matches(IEnumerable<string> patterns, string normalizedExe) =>
        patterns.Any(p => Normalize(p) == normalizedExe);

    /// <summary>
    /// Decide what RIOJoy should do given the current foreground executable
    /// (<paramref name="foregroundExecutable"/> may be null when unknown/desktop).
    /// </summary>
    public static SwitchDecision Resolve(AppConfig config, string? foregroundExecutable)
    {
        if (config is null) throw new ArgumentNullException(nameof(config));

        if (!string.IsNullOrWhiteSpace(foregroundExecutable))
        {
            string exe = Normalize(foregroundExecutable);

            if (Matches(config.NativeGameExecutables, exe))
                return SwitchDecision.Yield();

            foreach (RioProfile profile in config.Profiles)
            {
                if (Matches(profile.MatchExecutables, exe))
                    return SwitchDecision.Activate(profile);
            }
        }

        RioProfile? neutral = config.FindProfile(config.NeutralProfileName);
        return neutral is not null ? SwitchDecision.Activate(neutral) : SwitchDecision.Idle();
    }
}
