using DodoSSH.Client.Session;
using global::Android.Content;
using global::Android.OS;
using global::Android.Provider;

namespace DodoSSH.Client.Android.Platform;

/// <summary>
/// The handful of facts about this phone that the platform-neutral layers need handed to them.
/// </summary>
/// <remarks>
/// <para>
/// Named <c>Phone</c> rather than <c>Android</c> because <c>Android.Runtime.AndroidEnvironment</c> already
/// exists and this type is not it.
/// </para>
/// <para>
/// Both members below are things docs/android-port.md called out as needing to come from the head rather
/// than be branched for inside the core: the profile directory, and a device name that is not
/// <c>Environment.MachineName</c>.
/// </para>
/// </remarks>
internal static class PhoneEnvironment
{
    private static Context? context;

    /// <summary>Captured once, from the launcher activity, before Avalonia starts.</summary>
    /// <remarks>
    /// The <em>application</em> context rather than the activity's. An activity is destroyed and recreated
    /// on configuration changes this head does not declare as handled, and holding one in a static field is
    /// the textbook Android leak; the application context lives as long as the process, which is exactly the
    /// lifetime the composition root has.
    /// </remarks>
    public static void Attach(Context activity) =>
        context = activity.ApplicationContext ?? activity;

    /// <summary>Where this phone keeps its profile.</summary>
    /// <remarks>
    /// <c>filesDir</c> — per-app, non-roaming, not user-visible, and removed when the app is uninstalled.
    /// <see cref="ClientPaths"/> asks for a local, non-roaming directory because two machines sharing one
    /// cache file corrupts the outbox; on Android that is not merely satisfied but enforced by the platform,
    /// since no other app can reach this path at all.
    /// </remarks>
    public static ClientPaths Paths =>
        new(Require().FilesDir?.AbsolutePath
            ?? throw new InvalidOperationException("Android returned no filesDir for this application."));

    /// <summary>
    /// What this phone calls itself in connection and keychain log entries.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <c>Environment.MachineName</c> returns <c>localhost</c> on Android, which would make every log entry
    /// written from a phone indistinguishable from every other — the finding recorded in
    /// docs/android-port.md §7.
    /// </para>
    /// <para>
    /// <c>Settings.Global.DeviceName</c> is what the user themselves typed in Settings, so it is the name
    /// they will recognise in a log written by a different device. It is null on phones that have never had
    /// one set, and the fallback is the marketing model rather than the board name: a person reading a log
    /// knows what a Pixel 8 is and does not know what <c>shiba</c> is.
    /// </para>
    /// </remarks>
    public static string DeviceName
    {
        get
        {
            var chosen = Settings.Global.GetString(Require().ContentResolver, "device_name");

            if (!string.IsNullOrWhiteSpace(chosen))
            {
                return chosen;
            }

            var model = Build.Model;

            return string.IsNullOrWhiteSpace(model) ? "Android phone" : model;
        }
    }

    /// <summary>Where this phone keeps copies that only have to survive the thing that made them.</summary>
    /// <remarks>
    /// <c>cacheDir</c> — per-app like <see cref="Paths"/>, and unlike it, reclaimable: Android deletes from
    /// here when the device runs short of storage. That is the right trade for the upload staging in
    /// <see cref="DocumentStaging"/>, whose files are worthless the moment their transfer finishes, and it
    /// is why the profile is not kept here. The cost is stated rather than hidden: a file reclaimed under
    /// storage pressure while its upload is still running fails that upload.
    /// </remarks>
    public static string CacheDirectory =>
        Require().CacheDir?.AbsolutePath
        ?? throw new InvalidOperationException("Android returned no cacheDir for this application.");

    /// <summary>
    /// The activity currently on screen, or null while the app is backgrounded.
    /// </summary>
    /// <remarks>
    /// Set and cleared by <c>MainActivity</c>. Separate from <see cref="Require"/> because the two have
    /// genuinely different lifetimes and different uses: the application context is what outlives
    /// everything and is right for starting services, and this is what a window flag or a system dialogue
    /// needs — neither substitutes for the other.
    /// </remarks>
    public static global::Android.App.Activity? CurrentActivity { get; set; }

    /// <summary>The application context, once <see cref="Attach"/> has run.</summary>
    public static Context Require() =>
        context ?? throw new InvalidOperationException(
            "PhoneEnvironment was read before MainActivity attached it.");
}
