using System.Text;
using System.Text.RegularExpressions;
namespace AIStudio.Assistants.BeyondFactsLab;
///
/// A person whom the research on the participants of the BeyondFacts Lab lists as taking part in the event.
///
///
/// The agent of the topic "Participants" gives every person a heading of the third level with nothing but the name, see
/// Prompts/research/participants.md. The lab reads these headings once, when it stores the research, and keeps the list
/// as it is from then on, see BflResearch.Participants.
///
public sealed partial class BflParticipant
{
///
/// How many characters a name has at most. A longer heading is no name.
///
public const int MAX_NAME_CHARACTERS = 200;
///
/// How many people a list holds at most. The prompt asks for 40 at most; the rest is left out.
///
public const int MAX_PARTICIPANTS = 50;
///
/// Gets or sets the name of the person, as the heading gives it.
///
public string Name { get; set; } = string.Empty;
///
/// Gets or sets what the list says about the person below the heading, in Markdown: the role at the event, the
/// institution or function, and the source.
///
public string Entry { get; set; } = string.Empty;
///
/// Tells whether this is the person with a name, regardless of case and of the spaces between the words.
///
/// The name.
public bool HasName(string name) => string.Equals(NameKey(this.Name), NameKey(name), StringComparison.Ordinal);
///
/// Reads the people from the answer of the agent of the topic "Participants".
///
///
/// Every heading of the third level starts a person; a heading of a higher level ends it, one of a lower level
/// belongs to its entry. Markdown in a heading, such as emphasis or a link, is taken off. A heading which is empty
/// or too long is no person, and a name which comes twice counts once, with its first entry.
///
/// The answer, with its lines ending in LF.
/// The people, in the order of the answer.
public static IReadOnlyList ListIn(string text)
{
var participants = new List();
var keys = new HashSet(StringComparer.Ordinal);
BflParticipant? current = null;
var entry = new StringBuilder();
foreach (var line in text.ReplaceLineEndings("\n").Split('\n'))
{
var heading = HeadingPattern().Match(line);
if (!heading.Success || heading.Groups["level"].Length > 3)
{
if (current is not null)
entry.Append(line).Append('\n');
continue;
}
Finish();
if (heading.Groups["level"].Length < 3 || participants.Count >= MAX_PARTICIPANTS)
continue;
var name = CleanName(heading.Groups["text"].Value);
if (name.Length is 0 or > MAX_NAME_CHARACTERS || !keys.Add(NameKey(name)))
continue;
current = new BflParticipant { Name = name };
participants.Add(current);
}
Finish();
return participants;
void Finish()
{
if (current is not null)
current.Entry = entry.ToString().Trim();
current = null;
entry.Clear();
}
}
///
/// Gets what decides whether two names are the same: the words in capitals, separated by one space.
///
/// The name.
public static string NameKey(string name) => string.Join(' ', name.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant();
///
/// Takes the Markdown off a heading and joins its words with single spaces.
///
/// The text of the heading.
private static string CleanName(string heading)
{
var cleaned = MarkdownLinkPattern().Replace(heading, "${label}");
cleaned = MarkupPattern().Replace(cleaned, string.Empty);
return string.Join(' ', cleaned.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries));
}
///
/// A heading in Markdown, with its level and its text.
///
[GeneratedRegex(@"^[ \t]{0,3}(?#{1,6})[ \t]+(?.*?)[ \t#]*$", RegexOptions.CultureInvariant)]
private static partial Regex HeadingPattern();
///
/// A link in Markdown, whose text stays.
///
[GeneratedRegex(@"\[(?