<commit_before>using System;
using System.Linq;
using FluentAssertions.Formatting;

namespace FluentAssertions.Common
{
    internal static class StringExtensions
    {
        /// <summary>
        /// Finds the first index at which the <paramref name="value"/> does not match the <paramref name="expected"/>
        /// string anymore, accounting for the specified <paramref name="stringComparison"/>.
        /// </summary>
        public static int IndexOfFirstMismatch(this string value, string expected, StringComparison stringComparison)
        {
            for (int index = 0; index < value.Length; index++)
            {
                if ((index >= expected.Length) || !value[index].ToString().Equals(expected[index].ToString(), stringComparison))
                {
                    return index;
                }
            }

            return -1;
        }

        /// <summary>
        /// Gets the quoted three characters at the specified index of a string, including the index itself.
        /// </summary>
        public static string IndexedSegmentAt(this string value, int index)
        {
            int length = Math.Min(value.Length - index, 3);
            string formattedString = Formatter.ToString(value.Substring(index, length));

            return $"{formattedString} (index {index})".EscapePlaceholders();
        }

        /// <summary>
        /// Replaces all characters that might conflict with formatting placeholders with their escaped counterparts.
        /// </summary>
        public static string EscapePlaceholders(this string value) =><commit_msg>Optimize IndexOfFirstMismatch<commit_after>using System;
using System.Linq;
using FluentAssertions.Formatting;

namespace FluentAssertions.Common
{
    internal static class StringExtensions
    {
        /// <summary>
        /// Finds the first index at which the <paramref name="value"/> does not match the <paramref name="expected"/>
        /// string anymore, accounting for the specified <paramref name="stringComparison"/>.
        /// </summary>
        public static int IndexOfFirstMismatch(this string value, string expected, StringComparison stringComparison)
        {
            Func<char, char, bool> comparer = GetCharComparer(stringComparison);

            for (int index = 0; index < value.Length; index++)
            {
                if ((index >= expected.Length) || !comparer(value[index], expected[index]))
                {
                    return index;
                }
            }

            return -1;
        }

        private static Func<char, char, bool> GetCharComparer(StringComparison stringComparison) =>
            stringComparison == StringComparison.Ordinal
                ? (Func<char, char, bool>)((x, y) => x == y)
                : (x, y) => char.ToUpperInvariant(x) == char.ToUpperInvariant(y);

        /// <summary>
        /// Gets the quoted three characters at the specified index of a string, including the index itself.
        /// </summary>
        public static string IndexedSegmentAt(this string value, int index)
        {
            int length = Math.Min(value.Length - index, 3);
            string formattedString = Formatter.ToString(value.Substring(index, length));

            return $"{formattedString} (index {index})".EscapePlaceholders();
        }

        /// <summary>
        /// Replaces all characters that might conflict with formatting placeholders with their escaped counterparts.
        /// </summary>
        public static string EscapePlaceholders(this string value) =>
{'fraction_non_alphanumeric': 0.10397982062780269, 'fraction_numerical': 0.0016816143497757848, 'mean_word_length': 2.212421242124212, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3946568', 'n_tokens_mistral': 875, 'n_tokens_neox': 843, 'n_words': 271}