📄 src/JsonLdRecipeParser/Ingredient.cs
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using JsonLdRecipeParser.Json;

namespace JsonLdRecipeParser;

public record Ingredient
{
    private static readonly SearchValues<char> WhitespaceCharacters = SearchValues.Create(" \t");

    public required string Name { get; set; }
    public Amount? Amount { get; set; }

    internal static Ingredient? From(RecipeIngredient ingredient) =>
        ingredient.Value switch
        {
            string text => TryParse(text, out var i) ? i : null,
            _ => throw new NotImplementedException("Ingredient parsing is only implemented for strings."),
        };

    public static bool TryParse(string text, [NotNullWhen(true)] out Ingredient? ingredient)
    {
        var span = text.AsSpan();

        _ = Amount.TryParse(ref span, out var amount);

        if (TryParseIngredient(ref span, out var name))
        {
            ingredient = new() { Name = name, Amount = amount };
            return true;
        }

        if (amount?.Unit is string unitName)
        {
            ingredient = new() { Name = unitName, Amount = amount with { Unit = null } };
            return true;
        }

        ingredient = default;
        return false;
    }

    private static bool TryParseIngredient(ref ReadOnlySpan<char> text, [NotNullWhen(true)] out string? name)
    {
        var start = text.IndexOfAnyExcept(WhitespaceCharacters);
        if (start == -1)
        {
            name = default;
            return false;
        }
        text = text[start..];
        var end = text.LastIndexOfAnyExcept(WhitespaceCharacters);
        if (end == -1)
        {
            end = text.Length;
        }
        end += 1;

        name = text[..end].ToString();
        text = text[end..];
        return true;
    }
}