namespace ErsatzTV.Core.Metadata;

// #484: a media-server API client maps each item the server returned through a private projection. That
// projection has TWO reasons to produce nothing, and conflating them is dangerous:
//
//   - Skipped  — a deliberate guard clause (a virtual/non-FileSystem item, a STRM file, an unsupported
//                item type). The item is permanently and expectedly absent from the incoming set; a
//                library containing one STRM file produces a Skipped on every single scan, forever.
//   - Failed   — the projection threw and was swallowed by a `catch { LogWarning; }`. The server DID
//                return the item; we simply could not build it. At the reconcile step that is
//                indistinguishable from a deletion, so a projection regression can mass-flag healthy
//                items FileNotFound (which EmptyTrash then deletes permanently).
//
// Only Failed may suppress the reconciliation sweep (see MediaServerReconciliationGuard). Treating
// Skipped as a failure would permanently disable reconciliation for any library holding a single STRM
// file, so stale rows would accumulate forever — a regression, not a safe default.
public readonly struct MediaServerProjectionResult<T>
{
    private MediaServerProjectionResult(Option<T> item, bool isFailure)
    {
        Item = item;
        IsFailure = isFailure;
    }

    public Option<T> Item { get; }

    /// <summary>
    /// True only when the projection threw. A deliberate guard-clause skip is NOT a failure.
    /// </summary>
    public bool IsFailure { get; }

    public static MediaServerProjectionResult<T> Projected(T item) => new(item, false);

    /// <summary>
    /// The server returned an item we deliberately and permanently do not import (virtual item, STRM
    /// file, unsupported type). Expected on every scan; never suppresses a sweep.
    /// </summary>
    public static MediaServerProjectionResult<T> Skipped() => new(Option<T>.None, false);

    /// <summary>
    /// The projection threw and the exception was swallowed. The item exists upstream but is missing
    /// from the incoming set, so the sweep must not run.
    /// </summary>
    public static MediaServerProjectionResult<T> Failed() => new(Option<T>.None, true);

    public static implicit operator MediaServerProjectionResult<T>(T item) => Projected(item);

    public Option<T> ToOption() => Item;
}
