using System.Diagnostics.CodeAnalysis;

namespace DodoSSH.Client.Domain;

/// <summary>
/// A saved command, decrypted.
/// </summary>
/// <remarks>
/// <para>
/// <b><see cref="RunsOnInsert"/> is the field this type exists to get right.</b> A terminal is one input
/// stream with no notion of "at a prompt": the remote may be inside <c>vi</c>, or at a <c>sudo</c> password
/// prompt with echo off, and without shell integration the client cannot tell. So inserting a snippet is
/// always "type this into whatever is there", never "run this command" — and whether a newline follows the
/// text is the difference between the user reading what appeared and deciding, and something happening.
/// It defaults to <see langword="false"/>, which makes that decision the user's Enter key.
/// </para>
/// <para>
/// <b><see cref="Command"/> is stored verbatim.</b> No trimming, no newline normalisation — the same rule
/// <see cref="SshKeySecret.PrivateKeyPem"/> follows, for a related reason: a heredoc's trailing newline is
/// load-bearing, and a shell that receives a here-document terminator with the whitespace tidied off it hangs
/// waiting for one that never comes.
/// </para>
/// <para>
/// Deliberately not in this version, each with a reason rather than an omission: <b>host scoping</b>, which
/// needs a set merge that <see cref="ThreeWayMerge"/> does not have; <b>tags</b>, which are their own reserved
/// item kind; and <b>parameter substitution</b>, which would make this a template language expanding into a
/// root shell — a second security surface for a feature whose first one is already the hard part.
/// </para>
/// </remarks>
public sealed record SnippetSecret : IVaultSecret
{
    /// <summary>What the snippet is called. The only name it has anywhere.</summary>
    public required string Label { get; init; }

    /// <summary>The text to insert. May be several lines.</summary>
    public required string Command { get; init; }

    /// <summary>Free-text notes.</summary>
    public string? Notes { get; init; }

    /// <summary>
    /// Whether inserting this also presses Enter.
    /// </summary>
    /// <remarks>
    /// Off unless the user turns it on, per snippet. A vault-wide preference was the alternative and it is
    /// worse: the setting belongs to the command, because <c>ls -la</c> and <c>rm -rf /var/lib/postgresql</c>
    /// do not want the same answer, and a single switch would eventually be left on by whoever needed it for
    /// the first of those.
    /// </remarks>
    public bool RunsOnInsert { get; init; }

    /// <summary>Whether this is storable, and why not if it is not.</summary>
    /// <remarks>
    /// <see cref="Command"/> is checked for being blank but for nothing else. What makes a valid command is
    /// the remote shell's business, this client does not know which shell that is, and a validator guessing
    /// at it would refuse the legitimate cases — a bare <c>\x03</c>, a partial line meant to be completed by
    /// hand — while catching nothing that matters.
    /// </remarks>
    public bool TryValidate([NotNullWhen(false)] out string? reason)
    {
        if (string.IsNullOrWhiteSpace(Label))
        {
            reason = "A snippet needs a name.";
            return false;
        }

        if (string.IsNullOrEmpty(Command))
        {
            reason = "A snippet needs something to insert.";
            return false;
        }

        reason = null;
        return true;
    }
}
