using System.Diagnostics.CodeAnalysis;

namespace DodoSSH.Client.Domain;

/// <summary>
/// A label that can be put on many hosts, decrypted.
/// </summary>
/// <remarks>
/// <para>
/// One field, and the one field is the whole argument for the type existing. A tag is only worth having
/// because the same tag goes on twenty machines and filters them back out later, so it needs an identity of
/// its own: renaming "staging" to "stage" is then one write to one item, rather than twenty host payloads
/// that have to be found, decrypted, edited and pushed — nineteen of which can fail halfway and leave a
/// vault holding two spellings of one tag.
/// </para>
/// <para>
/// <b>Membership is a <see cref="HostSecret.TagIds"/> set on the host</b>, not a member list here and not the
/// <c>HostTag</c> join the contract reserves. The pointer-on-the-host argument that decided
/// <see cref="HostSecret.GroupId"/> applies unchanged — tagging two hosts is two writes to two items — and
/// the extra thing a join would have bought, two machines tagging the <em>same</em> host without one losing,
/// is already bought by <see cref="ThreeWayMerge.Map"/>, which resolves a keyed collection key by key and so
/// gives set semantics with removals. <c>SyncEntityType.HostTag</c> therefore stays reserved and unused.
/// </para>
/// <para>
/// <b>No colour, no description, no ordering.</b> Each was considered and each would be a field two clients
/// can disagree about in exchange for nothing the filter needs. If one arrives later it arrives behind a
/// schema version, which is what the envelope in <see cref="TagSecretCodec"/> is for.
/// </para>
/// </remarks>
public sealed record TagSecret : IVaultSecret
{
    /// <summary>What the tag is called. The only name it has anywhere.</summary>
    public required string Label { get; init; }

    /// <summary>Whether this is storable, and why not if it is not.</summary>
    /// <remarks>
    /// <para>
    /// A blank name is refused rather than defaulted, for a sharper reason than a group's: a tag is drawn as
    /// a chip beside a host and selected from a list of chips, so a nameless one is an empty chip that
    /// filters a set nobody can name.
    /// </para>
    /// <para>
    /// <b>A duplicate name is not refused here</b>, and cannot be — this record is one tag and knows nothing
    /// about the others. Two tags called "staging" are two tags, which is a mess the editor should warn about
    /// and the vault must still be able to hold: two people creating the same tag offline is exactly how it
    /// happens, and refusing to store the second one would mean dropping the tags a colleague had already
    /// put on their hosts.
    /// </para>
    /// </remarks>
    public bool TryValidate([NotNullWhen(false)] out string? reason)
    {
        if (string.IsNullOrWhiteSpace(Label))
        {
            reason = "A tag needs a name.";
            return false;
        }

        reason = null;
        return true;
    }
}
