namespace DodoSSH.Client.Session;

/// <summary>
/// Where this machine keeps its profile.
/// </summary>
/// <remarks>
/// <para>
/// A record with an explicit directory rather than a static lookup, so a test — or a portable install —
/// can point it somewhere else without an environment variable.
/// </para>
/// <para>
/// <b>The choice of directory matters more than it looks.</b> The cache is a SQLite file written by one
/// process, and the whole design assumes each machine has its own: the outbox holds changes this machine
/// has made and not yet pushed, and two machines sharing one file through a cloud sync client corrupts
/// it. So this deliberately picks a <em>local</em>, non-roaming location on every platform. On Windows
/// that means <c>%LOCALAPPDATA%</c> and never <c>%APPDATA%</c>, which roams in a domain environment and
/// would do exactly the wrong thing.
/// </para>
/// </remarks>
/// <param name="DataDirectory">The profile directory. Created on demand.</param>
public sealed record ClientPaths(string DataDirectory)
{
    private const string WindowsFolderName = "DodoSSH";
    private const string UnixFolderName = "dodossh";

    /// <summary>The conventional location for this platform.</summary>
    public static ClientPaths Default { get; } = new(ResolveDataDirectory());

    /// <summary>The encrypted local cache.</summary>
    public string CacheFile => Path.Combine(DataDirectory, "cache.db");

    /// <summary>
    /// This machine's preferences, in plaintext.
    /// </summary>
    /// <remarks>
    /// Beside the cache rather than inside it, because everything in it is needed <em>before</em> a vault
    /// is unlocked — a terminal draws at a chosen size on a launch that never reaches a passphrase. That
    /// is also the reason nothing secret may go in here; see <see cref="ClientSettings"/>.
    /// </remarks>
    public string SettingsFile => Path.Combine(DataDirectory, "settings.json");

    /// <summary>
    /// This machine's device key, encrypted to a key it cannot export.
    /// </summary>
    /// <remarks>
    /// Local and non-roaming for a stronger reason than the cache is: the file is decryptable only by a
    /// key held in this machine's TPM, so a copy of it on another machine is bytes nothing can open. It
    /// following a user to a second computer would be useless rather than dangerous — but a roaming
    /// profile that overwrote one machine's blob with another's would break both.
    /// </remarks>
    public string DeviceKeyFile => Path.Combine(DataDirectory, "device.key");

    /// <summary>Creates the profile directory if it is not there yet.</summary>
    /// <remarks>
    /// Separate from resolving the path, because resolving must never have a side effect: it is read
    /// during startup diagnostics and by tests that have no business creating directories.
    /// </remarks>
    public void EnsureCreated() => Directory.CreateDirectory(DataDirectory);

    /// <remarks>
    /// The platform branches are explicit rather than delegating to
    /// <see cref="Environment.SpecialFolder.LocalApplicationData"/> everywhere. That enumeration does
    /// the right thing on Windows, but on macOS the runtime maps it to <c>~/.local/share</c> rather than
    /// to <c>~/Library/Application Support</c>, and relying on framework behaviour that differs per
    /// platform for a path users will look at is how a file ends up somewhere nobody expects.
    /// <para>
    /// <c>XDG_DATA_HOME</c> is honoured explicitly for the same reason: it is the spec, and reading it
    /// here is one line versus depending on whether the runtime happens to.
    /// </para>
    /// </remarks>
    private static string ResolveDataDirectory()
    {
        if (OperatingSystem.IsWindows())
        {
            return Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                WindowsFolderName);
        }

        var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);

        if (OperatingSystem.IsMacOS())
        {
            return Path.Combine(home, "Library", "Application Support", WindowsFolderName);
        }

        var xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME");

        var root = string.IsNullOrWhiteSpace(xdgDataHome)
            ? Path.Combine(home, ".local", "share")
            : xdgDataHome;

        return Path.Combine(root, UnixFolderName);
    }
}
