using System.Text; namespace MeetingAssistant.MeetingNotes; public sealed class MeetingArtifactFrontmatter { public string Title { get; set; } = ""; public DateTimeOffset? StartTime { get; set; } public DateTimeOffset? EndTime { get; set; } public string Meeting { get; set; } = ""; public string Transcript { get; set; } = ""; public string AssistantContext { get; set; } = ""; public string Summary { get; set; } = ""; public string? State { get; set; } public string? Agenda { get; set; } public DateTimeOffset? ScheduledEnd { get; set; } } public static class MeetingArtifactFrontmatterRenderer { public static string Render( MeetingArtifactFrontmatter frontmatter, string body) { var builder = new StringBuilder(); builder.AppendLine("---"); AppendScalar(builder, "title", frontmatter.Title); AppendDateTime(builder, "start_time", frontmatter.StartTime); AppendDateTime(builder, "end_time", frontmatter.EndTime); AppendQuoted(builder, "meeting", frontmatter.Meeting); AppendQuoted(builder, "transcript", frontmatter.Transcript); AppendQuoted(builder, "assistant_context", frontmatter.AssistantContext); AppendQuoted(builder, "summary", frontmatter.Summary); if (!string.IsNullOrWhiteSpace(frontmatter.State)) { AppendScalar(builder, "state", frontmatter.State); } if (frontmatter.Agenda is not null) { AppendBlockScalar(builder, "agenda", frontmatter.Agenda); } if (frontmatter.ScheduledEnd is not null) { AppendDateTime(builder, "scheduled_end", frontmatter.ScheduledEnd); } builder.AppendLine("---"); builder.AppendLine(); builder.Append(body.TrimStart('\r', '\n')); return builder.ToString(); } public static (string Frontmatter, string Body) Split(string content) { var document = MarkdownDocumentParser.SplitOptional(content); return (document.Frontmatter, document.Body); } public static MeetingArtifactFrontmatter Create( MeetingSessionArtifacts artifacts, MeetingNote meetingNote, string title, string sourceNotePath, string? state = null) { return new MeetingArtifactFrontmatter { Title = title, StartTime = meetingNote.Frontmatter.StartTime, EndTime = meetingNote.Frontmatter.EndTime, Meeting = ObsidianLink.ToWikiLink(artifacts.MeetingNotePath, sourceNotePath, "Meeting Note"), Transcript = ObsidianLink.ToWikiLink(artifacts.TranscriptPath, sourceNotePath, "Transcript"), AssistantContext = ObsidianLink.ToWikiLink(artifacts.AssistantContextPath, sourceNotePath, "Assistant Context"), Summary = ObsidianLink.ToWikiLink(artifacts.SummaryPath, sourceNotePath, "Summary"), State = state }; } public static string DefaultTitle(MeetingNote meetingNote, string fallback) { return string.IsNullOrWhiteSpace(meetingNote.Frontmatter.Title) ? fallback : meetingNote.Frontmatter.Title; } private static void AppendScalar(StringBuilder builder, string key, string? value) { builder.Append(key); builder.Append(": "); builder.AppendLine(string.IsNullOrWhiteSpace(value) ? "\"\"" : EscapeListItem(value)); } private static void AppendQuoted(StringBuilder builder, string key, string value) { builder.Append(key); builder.Append(": "); builder.AppendLine(EscapeQuoted(value)); } private static void AppendDateTime(StringBuilder builder, string key, DateTimeOffset? value) { builder.Append(key); builder.Append(": "); builder.AppendLine(value is null ? "\"\"" : EscapeQuoted(value.Value.ToString("O"))); } private static void AppendBlockScalar(StringBuilder builder, string key, string value) { if (string.IsNullOrEmpty(value)) { builder.Append(key); builder.AppendLine(": \"\""); return; } builder.Append(key); builder.AppendLine(": |-"); foreach (var line in value.Replace("\r\n", "\n", StringComparison.Ordinal).Replace('\r', '\n').Split('\n')) { builder.Append(" "); builder.AppendLine(line); } } private static string EscapeQuoted(string value) { return $"\"{value.Replace("\"", "\\\"", StringComparison.Ordinal)}\""; } private static string EscapeListItem(string value) { return value.Contains(':', StringComparison.Ordinal) || value.StartsWith("[", StringComparison.Ordinal) ? EscapeQuoted(value) : value; } }