📄 src/Storage/NoteFrontmatter.cs
using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.Converters;

namespace NotesApp.Storage;

// Mirrors the frontmatter handling in the companion CLI's New-Note cmdlet: Markdig's
// UseYamlFrontMatter extension locates the --- delimited YAML block, and YamlDotNet
// (de)serializes it as a loose Dictionary<string, object> so arbitrary frontmatter schemas
// round-trip without a fixed model.
public static class NoteFrontmatter
{
    private static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder()
        .UseYamlFrontMatter()
        .Build();

    private static readonly IDeserializer Deserializer = new DeserializerBuilder().Build();

    private static readonly ISerializer Serializer = new SerializerBuilder()
        .WithTypeConverter(new DateOnlyConverter(formats: "yyyy-MM-dd"))
        .Build();

    // The note's body with any leading YAML frontmatter block (delimiters included) removed,
    // or the content unchanged if it has none.
    public static string StripBlock(string content)
    {
        var block = FindBlock(content);
        if (block is null)
            return content;

        var start = Math.Min(block.Span.End + 1, content.Length);
        return content[start..].TrimStart('\r', '\n');
    }

    public static Dictionary<string, object>? Extract(string content)
    {
        var block = FindBlock(content);
        if (block is null)
            return null;

        return Deserializer.Deserialize<Dictionary<string, object>>(block.Lines.ToString());
    }

    // Builds a fresh frontmatter block (fences included) from a field dictionary, stamping
    // today's date the same way the CLI's New-Note does - even an otherwise-empty dictionary
    // still gets a dated block.
    public static string BuildBlock(Dictionary<string, object> fields)
    {
        fields["date"] = DateOnly.FromDateTime(DateTime.Now);
        var yaml = Serializer.Serialize(fields).TrimEnd();
        return $"---\n{yaml}\n---\n\n\n";
    }

    private static YamlFrontMatterBlock? FindBlock(string content) =>
        Markdown.Parse(content, Pipeline).Descendants<YamlFrontMatterBlock>().FirstOrDefault();
}