using Avalonia;
using Avalonia.Media;
using DodoSSH.Client.App.Platform;
using DodoSSH.Client.Session;
using Velopack;

namespace DodoSSH.Client.App;

internal static class Program
{
    /// <summary>
    /// Entry point.
    /// </summary>
    /// <remarks>
    /// <c>STAThread</c> is required, not decorative: WebView2 checks the apartment state and refuses
    /// to initialise on an MTA thread. Without it the terminal is simply blank on Windows. It applies to
    /// everything below, which is why the Velopack call lives inside this method rather than in an entry
    /// point of its own.
    /// </remarks>
    [STAThread]
    public static void Main(string[] args)
    {
        // First, before Avalonia is even configured.
        //
        // The installer, the updater and the uninstaller all re-run this executable with arguments that
        // mean "do the install bookkeeping and stop". Run() is what notices, does it, and exits — so on
        // those runs nothing below happens at all, and that is the point rather than a side effect:
        // DodoSshApp.Compose opens the SQLite cache and starts the terminal workspace's listening socket,
        // and a silent installer run that reached either would be a background process holding the cache
        // file open during the very file operations the installer is performing.
        //
        // There are deliberately no OnFirstRun or OnAfterUpdate hooks. A hook process has no passphrase,
        // so the cache is bytes it cannot read, and the one thing that would want doing after an update —
        // a schema migration — already runs on every ordinary launch from MainWindowViewModel.StartAsync,
        // before unlock and touching no encrypted content.
        VelopackApp.Build().Run();

        KeepTheWebViewProfileOutOfTheInstallDirectory();

        BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
    }

    /// <summary>
    /// Puts WebView2's user data folder beside the vault cache instead of beside the executable.
    /// </summary>
    /// <remarks>
    /// <para>
    /// WebView2 defaults this to a directory next to the host executable. Under a Velopack install that is
    /// <c>%LOCALAPPDATA%\DodoSSH.Desktop\current\</c>, and <c>current\</c> is <em>replaced</em> by every
    /// update — so the browser profile would be destroyed on each one, and the first connect afterwards
    /// would pay a cold WebView2 start: a new user data directory and a fresh process tree, which is the
    /// slow path <c>TerminalWorkspaceOptions.RendererTimeout</c>'s fifteen seconds was sized for. It would
    /// land at the exact moment somebody is most ready to believe the update broke the terminal.
    /// </para>
    /// <para>
    /// The profile directory is the right home because Velopack never touches it — the pack id is
    /// deliberately not <c>DodoSSH</c>, so the install root and <c>ClientPaths.DataDirectory</c> are
    /// siblings rather than the same folder. See docs/adr/0013-desktop-distribution-and-updates.md.
    /// </para>
    /// <para>
    /// An environment variable rather than the control's own options, because it is read by the WebView2
    /// loader before any of this application's UI exists, and because it needs no reference to whichever
    /// WebView package the terminal happens to be hosted by.
    /// </para>
    /// </remarks>
    private static void KeepTheWebViewProfileOutOfTheInstallDirectory()
    {
        if (!OperatingSystem.IsWindows())
        {
            return;
        }

        // The same profile directory the rest of the application resolves, channel and all — a nightly
        // pointing WebView2 at the release build's profile would put two browser profiles in one folder
        // and hand the pair of them whichever process opened first.
        var folder = Path.Combine(
            ClientPaths.ForChannel(DesktopChannel.Name).DataDirectory,
            "WebView2");

        try
        {
            Directory.CreateDirectory(folder);

            Environment.SetEnvironmentVariable("WEBVIEW2_USER_DATA_FOLDER", folder);
        }
        catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
        {
            // Left unset, which puts the profile back beside the executable. That is a slow first connect
            // after each update, not a broken terminal, and refusing to start an SSH client over it would
            // be the wrong trade.
        }
    }

    /// <summary>Used by the designer as well as by <see cref="Main"/>.</summary>
    /// <remarks>
    /// <c>WithInterFont</c> still registers Inter, and it is still what the layout suite pins: see
    /// HeadlessApp. What changed is which face answers when nothing more specific is asked for. The v5
    /// design specifies Montserrat, and unlike the substitution this comment used to explain, Montserrat
    /// now ships embedded in the shell project rather than being requested by a name the machine may not
    /// have — see DodoSSH.Client.Shell/Assets/Fonts and its csproj entry. Inter stays registered and is
    /// named as a fallback, so a glyph Montserrat does not cover still resolves to a font that travels
    /// with the build rather than to whatever the platform's default sans happens to be.
    /// </remarks>
    public static AppBuilder BuildAvaloniaApp() =>
        AppBuilder.Configure<DodoSshApp>()
            .UsePlatformDetect()
            .WithInterFont()
            .With(new FontManagerOptions
            {
                DefaultFamilyName = "avares://DodoSSH.Client.Shell/Assets/Fonts#Montserrat",
                FontFallbacks =
                [
                    new FontFallback { FontFamily = new FontFamily("avares://Avalonia.Fonts.Inter/Assets#Inter") },
                ],
            })
            .LogToTrace();
}
