namespace DodoSSH.Client.Domain;

/// <summary>The merged tag, and everything that had to be overridden to produce it.</summary>
/// <param name="Merged">The tag to store and push.</param>
/// <param name="Conflicts">Empty when the two sides were reconcilable field by field.</param>
public sealed record TagMergeResult(
    TagSecret Merged,
    IReadOnlyList<HostFieldConflict> Conflicts)
{
    /// <summary>Whether anything had to be overridden.</summary>
    public bool HasConflicts => Conflicts.Count > 0;
}

/// <summary>
/// Merges two divergent versions of a tag against the version they both started from.
/// </summary>
/// <remarks>
/// <para>
/// One scalar, so this is now the simplest merge in the client, and the interesting thing about it is what
/// it never has to consider. Putting a tag on a host does not write to the tag — membership is a set on the
/// host — so two people tagging two different machines at the same time cannot reach this code. The only way
/// to get here is for two people to rename the same tag differently, which is a real disagreement and gets a
/// conflict notice.
/// </para>
/// <para>
/// <b>Two tags that end up with the same name are not merged into one, here or anywhere.</b> They are two
/// items with two ids and two sets of hosts, and collapsing them would mean rewriting every host that named
/// the loser — the exact N-payload write that keeping membership on the host exists to avoid. A duplicate is
/// something the editor should warn about before it is created, not something the merge silently repairs
/// afterwards.
/// </para>
/// <para>
/// Nothing is redacted. A tag name is the one thing a tag has, and a notice saying only that "the name
/// differed" would leave the user unable to tell which of their two names survived.
/// </para>
/// </remarks>
public static class TagSecretMerge
{
    /// <summary>Produces the merged tag.</summary>
    /// <param name="ancestor">The version both sides branched from.</param>
    /// <param name="local">The pending local version.</param>
    /// <param name="remote">The server's current version.</param>
    public static TagMergeResult Merge(TagSecret ancestor, TagSecret local, TagSecret remote)
    {
        ArgumentNullException.ThrowIfNull(ancestor);
        ArgumentNullException.ThrowIfNull(local);
        ArgumentNullException.ThrowIfNull(remote);

        var conflicts = new List<HostFieldConflict>();

        var merge = ThreeWayMerge.Scalar(
            ancestor.Label, local.Label, remote.Label, StringComparer.Ordinal);

        if (merge.IsConflicted)
        {
            // The local side always loses a scalar clash — see ThreeWayMerge — so the discarded side is
            // fixed here rather than derived from the outcome.
            conflicts.Add(new HostFieldConflict(
                nameof(TagSecret.Label),
                MergeSide.Local,
                merge.Value,
                merge.Discarded,
                DiscardedWasRemoval: false));
        }

        return new TagMergeResult(new TagSecret { Label = merge.Value }, conflicts);
    }
}
