using System.Text.RegularExpressions; using Chrono.Model; using Microsoft.AspNetCore.Components; namespace Shared.Services; public class CardRenderingService { private readonly Dictionary _cardLookup; private readonly List _cardNames; private readonly Regex _nameRegex; public CardRenderingService() { var allCards = CardDatabase.Cards .Where(c => !string.IsNullOrWhiteSpace(c.Name)) .ToList(); _cardLookup = allCards .GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase) .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase); _cardNames = allCards .Select(c => c.Name) .OrderByDescending(n => n.Length) .ToList(); var pattern = string.Join("|", _cardNames.Select(n => $@"\b{Regex.Escape(n)}\b")); _nameRegex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); } public RenderFragment RenderDescription(string description, Action onCardClick) { return builder => { var sequence = 0; var paragraphs = description.Split('\n', StringSplitOptions.RemoveEmptyEntries); foreach (var paragraph in paragraphs) { builder.OpenElement(sequence++, "p"); var matches = _nameRegex.Matches(paragraph); var lastIndex = 0; foreach (Match match in matches) { if (match.Index > lastIndex) builder.AddContent(sequence++, paragraph.Substring(lastIndex, match.Index - lastIndex)); var cardName = match.Value; var card = LookupCard(cardName); builder.OpenElement(sequence++, "button"); builder.AddAttribute(sequence++, "class", "inline-card-btn"); builder.AddAttribute(sequence++, "onclick", EventCallback.Factory.Create(this, () => onCardClick(card))); builder.AddContent(sequence++, cardName); builder.CloseElement(); lastIndex = match.Index + match.Length; } if (lastIndex < paragraph.Length) builder.AddContent(sequence++, paragraph.Substring(lastIndex)); builder.CloseElement(); } }; } private CardData? LookupCard(string cardName) { return _cardLookup.GetValueOrDefault(cardName); } }