using DodoSSH.Client.Domain;
using DodoSSH.Contracts;

namespace DodoSSH.Client.Sync;

/// <summary>
/// Somewhere to record that a keychain item was created, changed or deleted.
/// </summary>
/// <remarks>
/// <para>
/// <b>Hooked into <see cref="VaultItemRepository{TSecret}"/> rather than into the view models.</b> That
/// repository is the single generic funnel every kind's create, update and delete goes through, so one
/// write site covers all of them and picks up a new kind for free. Hooking the view models instead would
/// miss <c>VaultKnownHostStore</c>, which writes pins programmatically at connect time and never touches a
/// screen — and those are exactly the writes an audit trail must not be blind to.
/// </para>
/// <para>
/// Three rules govern the call, and they are properties of where it sits rather than of what it does:
/// </para>
/// <list type="number">
/// <item>
/// <b>After the outbox queue, never before.</b> A crash between the two loses an advisory line; the reverse
/// records a change that never happened.
/// </item>
/// <item>
/// <b>Every exception swallowed by the implementation.</b> A failing log write must never fail a save — the
/// entire point of the outbox is that saving works offline and cannot be refused.
/// </item>
/// <item>
/// <b>Not in a transaction with the outbox</b>, or a log failure rolls back a change the user made.
/// </item>
/// </list>
/// <para>
/// Note the deliberate asymmetry with the outbox itself, because it reads as a discrepancy otherwise: the
/// outbox <em>coalesces</em> two edits of one item into a single pending row, and this does not — two edits
/// are two lines. The outbox describes what still has to be sent; this describes what somebody did.
/// </para>
/// </remarks>
public interface IActivityLogSink
{
    /// <summary>Records one write.</summary>
    /// <param name="vaultId">Which vault it happened in.</param>
    /// <param name="kind">Which sort of item, as the wire contract names it.</param>
    /// <param name="entityId">The item.</param>
    /// <param name="label">What the item was called at the time.</param>
    /// <param name="operation">What was done.</param>
    /// <param name="changedFields">
    /// The names of the fields that differ, and never their values. Empty for a create and a delete, and
    /// also when the previous version could not be read — which is why an empty list must not be taken to
    /// mean nothing changed.
    /// </param>
    /// <remarks>
    /// Returns <see langword="void"/> by contract, for the reason <c>IConnectionLogSink</c> does: the caller
    /// is a save the user is waiting on, and an encrypt-and-write on that path would put the cost of the log
    /// into every keystroke that reaches a Save button.
    /// </remarks>
    void Record(
        Guid vaultId,
        SyncEntityType kind,
        Guid entityId,
        string label,
        ActivityOperation operation,
        IReadOnlyList<string> changedFields);
}
