using System.Security.Cryptography;

public static class StringRandom
{
    public static class Charsets
    {
        public const string Upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        public const string Lower = "abcdefghijklmnopqrstuvwxyz";
        public const string Digits = "0123456789";
        public const string HexLower = "0123456789abcdef";
        public const string HexUpper = "0123456789ABCDEF";
        public const string SymbolsSafe = "-._~"; // URL-safe extras
        public const string Alnum = Upper + Lower + Digits;
        public const string AlnumUrl = Upper + Lower + Digits + SymbolsSafe;
    }

    /// <summary>
    /// FAST (non-crypto) random string (use for UX ids, demo data, etc.)
    /// </summary>
    public static string Fast(int length, ReadOnlySpan<char> allowed = default)
    {
        if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
        if (allowed.Length == 0) allowed = Charsets.Alnum.AsSpan();

        var rng = Random.Shared;
        var result = new char[length];
        for (int i = 0; i < result.Length; i++)
            result[i] = allowed[rng.Next(allowed.Length)];
        return new string(result);
    }

    /// <summary>
    /// SECURE (crypto) random string (use for tokens, passwords, codes).
    /// </summary>
    public static string Secure(int length, ReadOnlySpan<char> allowed = default)
    {
        if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
        if (allowed.Length == 0) allowed = Charsets.Alnum.AsSpan();

        var result = new char[length];
        for (int i = 0; i < result.Length; i++)
        {
            int idx = RandomNumberGenerator.GetInt32(allowed.Length); // no modulo bias
            result[i] = allowed[idx];
        }
        return new string(result);
    }

    /// <summary>
    /// SECURE Base64URL token (URL-safe, no padding). bytes = raw entropy size.
    /// </summary>
    public static string Base64Url(int bytes = 32)
    {
        if (bytes <= 0) throw new ArgumentOutOfRangeException(nameof(bytes));
        var buf = new byte[bytes];
        RandomNumberGenerator.Fill(buf);
        return Convert.ToBase64String(buf)
            .Replace('+', '-').Replace('/', '_').TrimEnd('=');
    }

    /// <summary>
    /// Shuffle characters in a string (Fisher–Yates). Set secure=true for crypto RNG.
    /// </summary>
    public static string Shuffle(string text, bool secure = false)
    {
        if (text == null) throw new ArgumentNullException(nameof(text));
        var arr = text.ToCharArray();

        if (!secure)
        {
            var r = Random.Shared;
            for (int i = arr.Length - 1; i > 0; i--)
            {
                int j = r.Next(i + 1);
                (arr[i], arr[j]) = (arr[j], arr[i]);
            }
            return new string(arr);
        }

        for (int i = arr.Length - 1; i > 0; i--)
        {
            int j = RandomNumberGenerator.GetInt32(i + 1);
            (arr[i], arr[j]) = (arr[j], arr[i]);
        }
        return new string(arr);
    }
    /* example
     // Fast (UI ids, test data)
var orderId = StringRandom.Fast(10);                 // A1b2C3d4E5
var hexTag  = StringRandom.Fast(8, StringRandom.Charsets.HexLower);

// Secure (tokens, invite codes)
var token    = StringRandom.Secure(32);              // cryptographically strong
var pin      = StringRandom.Secure(6, StringRandom.Charsets.Digits);
var urlCode  = StringRandom.Secure(12, StringRandom.Charsets.AlnumUrl);

// Base64URL token (256-bit)
var apiKey = StringRandom.Base64Url(32);             // e.g. "VQ2kC3-..."

// Shuffle existing string
var shuffled = StringRandom.Shuffle("abcdef", secure: true);

     */
}
