// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "TerminalWindow.h"

#include "AppLogic.h"

#include <til/env.h>

#include "TerminalWindow.g.cpp"
#include "SettingsLoadEventArgs.g.cpp"
#include "WindowProperties.g.cpp"

#include "../TerminalSettingsAppAdapterLib/TerminalSettings.h"

using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Windows::Foundation::Collections;
using namespace ::TerminalApp;

namespace winrt
{
    namespace MUX = Microsoft::UI::Xaml;
    namespace WUX = Windows::UI::Xaml;
    using IInspectable = Windows::Foundation::IInspectable;
}

// !!! IMPORTANT !!!
// Make sure that these keys are in the same order as the
// SettingsLoadWarnings/Errors enum is!
static const std::array settingsLoadWarningsLabels{
    USES_RESOURCE(L"MissingDefaultProfileText"),
    USES_RESOURCE(L"DuplicateProfileText"),
    USES_RESOURCE(L"UnknownColorSchemeText"),
    USES_RESOURCE(L"InvalidMediaResource"),
    USES_RESOURCE(L"AtLeastOneKeybindingWarning"),
    USES_RESOURCE(L"TooManyKeysForChord"),
    USES_RESOURCE(L"MissingRequiredParameter"),
    USES_RESOURCE(L"FailedToParseCommandJson"),
    USES_RESOURCE(L"FailedToWriteToSettings"),
    USES_RESOURCE(L"InvalidColorSchemeInCmd"),
    USES_RESOURCE(L"InvalidSplitSize"),
    USES_RESOURCE(L"FailedToParseStartupActions"),
    USES_RESOURCE(L"InvalidProfileEnvironmentVariables"),
    USES_RESOURCE(L"FailedToParseSubCommands"),
    USES_RESOURCE(L"UnknownTheme"),
    USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
    USES_RESOURCE(L"InvalidUseOfContent"),
    USES_RESOURCE(L"InvalidRegex"),
};

static_assert(settingsLoadWarningsLabels.size() == static_cast<size_t>(SettingsLoadWarnings::WARNINGS_SIZE));
// Errors are defined in AppLogic.cpp

// Function Description:
// - General-purpose helper for looking up a localized string for a
//   warning/error. First will look for the given key in the provided map of
//   keys->strings, where the values in the map are ResourceKeys. If it finds
//   one, it will lookup the localized string from that ResourceKey.
// - If it does not find a key, it'll return an empty string
// Arguments:
// - key: the value to use to look for a resource key in the given map
// - map: A map of keys->Resource keys.
// Return Value:
// - the localized string for the given type, if it exists.
template<typename T>
winrt::hstring _GetMessageText(uint32_t index, const T& keys)
{
    if (index < keys.size())
    {
        return GetLibraryResourceString(til::at(keys, index));
    }
    return {};
}

// Function Description:
// - Gets the text from our ResourceDictionary for the given
//   SettingsLoadWarning. If there is no such text, we'll return nullptr.
// - The warning should have an entry in settingsLoadWarningsLabels.
// Arguments:
// - warning: the SettingsLoadWarnings value to get the localized text for.
// Return Value:
// - localized text for the given warning
static winrt::hstring _GetWarningText(SettingsLoadWarnings warning)
{
    return _GetMessageText(static_cast<uint32_t>(warning), settingsLoadWarningsLabels);
}

// Function Description:
// - Creates a Run of text to display an error message. The text is yellow or
//   red for dark/light theme, respectively.
// Arguments:
// - text: The text of the error message.
// - resources: The application's resource loader.
// Return Value:
// - The fully styled text run.
static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceDictionary& resources)
{
    Documents::Run textRun;
    textRun.Text(text);

    // Color the text red (light theme) or yellow (dark theme) based on the system theme
    auto key = winrt::box_value(L"ErrorTextBrush");
    if (resources.HasKey(key))
    {
        auto g = resources.Lookup(key);
        auto brush = g.try_as<winrt::Windows::UI::Xaml::Media::Brush>();
        textRun.Foreground(brush);
    }

    return textRun;
}

namespace winrt::TerminalApp::implementation
{
    TerminalWindow::TerminalWindow(const TerminalApp::SettingsLoadEventArgs& settingsLoadedResult,
                                   const TerminalApp::ContentManager& manager) :
        _settings{ settingsLoadedResult.NewSettings() },
        _manager{ manager },
        _initialLoadResult{ settingsLoadedResult },
        _WindowProperties{ winrt::make_self<TerminalApp::implementation::WindowProperties>() }
    {
        // The TerminalPage has to ABSOLUTELY NOT BE constructed during our
        // construction. We can't do ANY xaml till Initialize() is called.

        // For your own sanity, it's better to do setup outside the ctor.
        // If you do any setup in the ctor that ends up throwing an exception,
        // then it might look like App just failed to activate, which will
        // cause you to chase down the rabbit hole of "why is App not
        // registered?" when it definitely is.
    }

    // Method Description:
    // - Implements the IInitializeWithWindow interface from shobjidl_core.
    HRESULT TerminalWindow::Initialize(HWND hwnd)
    {
        // Now that we know we can do XAML, build our page.
        _root = winrt::make_self<TerminalPage>(*_WindowProperties, _manager);

        // Pass in information about the initial state of the window.
        // * If we were supposed to start from serialized "content", do that,
        // * If we were supposed to load from a persisted layout, do that
        //   instead.
        // * if we have commandline arguments, Pass commandline args into the
        //   TerminalPage.
        if (_startupConnection)
        {
            _root->SetStartupConnection(std::move(_startupConnection));
        }
        else if (!_initialContentArgs.empty())
        {
            _root->SetStartupActions(std::move(_initialContentArgs));
        }
        else if (const auto& layout = LoadPersistedLayout())
        {
            // layout will only ever be non-null if there were >0 tabs persisted in
            // .TabLayout(). We can re-evaluate that as a part of TODO: GH#12633
            _root->SetStartupActions(wil::to_vector(layout.TabLayout()));
        }
        else if (_appArgs)
        {
            _root->SetStartupActions(_appArgs->ParsedArgs().GetStartupActions());
        }

        return _root->Initialize(hwnd);
    }

    // Method Description:
    // - Build the UI for the terminal app. Before this method is called, it
    //   should not be assumed that the TerminalApp is usable. The Settings
    //   should be loaded before this is called, either with LoadSettings or
    //   GetLaunchDimensions (which will call LoadSettings)
    // Arguments:
    // - <none>
    // Return Value:
    // - <none>
    void TerminalWindow::Create()
    {
        _root->DialogPresenter(*this);

        // Pay attention, that even if some command line arguments were parsed (like launch mode),
        // we will not use the startup actions from settings.
        // While this simplifies the logic, we might want to reconsider this behavior in the future.
        //
        // Obviously, don't use the `startupActions` from the settings in the
        // case of a tear-out / reattach. GH#16050
        if (!_hasCommandLineArguments &&
            _initialContentArgs.empty() &&
            _gotSettingsStartupActions)
        {
            _root->SetStartupActions(_settingsStartupArgs);
        }

        _root->SetSettings(_settings, false); // We're on our UI thread right now, so this is safe
        _root->Loaded({ get_weak(), &TerminalWindow::_OnLoaded });
        _root->Initialized({ get_weak(), &TerminalWindow::_pageInitialized });
        _root->WindowSizeChanged({ get_weak(), &TerminalWindow::_WindowSizeChanged });
        _root->RenameWindowRequested({ get_weak(), &TerminalWindow::_RenameWindowRequested });
        _root->ShowLoadWarningsDialog({ get_weak(), &TerminalWindow::_ShowLoadWarningsDialog });
        _root->Create();

        AppLogic::Current()->SettingsChanged({ get_weak(), &TerminalWindow::UpdateSettingsHandler });

        _RefreshThemeRoutine();

        auto args = winrt::make_self<SystemMenuChangeArgs>(RS_(L"SettingsMenuItem"),
                                                           SystemMenuChangeAction::Add,
                                                           SystemMenuItemHandler(this, &TerminalWindow::_OpenSettingsUI));
        SystemMenuChangeRequested.raise(*this, *args);

        TraceLoggingWrite(
            g_hTerminalAppProvider,
            "WindowCreated",
            TraceLoggingDescription("Event emitted when the window is started"),
            TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
            TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
    }

    void TerminalWindow::_pageInitialized(const IInspectable&, const IInspectable&)
    {
        // GH#288 - When we finish initialization, if the user wanted us
        // launched _fullscreen_, toggle fullscreen mode. This will make sure
        // that the window size is _first_ set up as something sensible, so
        // leaving fullscreen returns to a reasonable size.
        const auto launchMode = this->GetLaunchMode();
        if (_WindowProperties->IsQuakeWindow() || WI_IsFlagSet(launchMode, LaunchMode::FocusMode))
        {
            _root->SetFocusMode(true);
        }

        // The IslandWindow handles (creating) the maximized state
        // we just want to record it here on the page as well.
        if (WI_IsFlagSet(launchMode, LaunchMode::MaximizedMode))
        {
            _root->Maximized(true);
        }

        if (WI_IsFlagSet(launchMode, LaunchMode::FullscreenMode) && !_WindowProperties->IsQuakeWindow())
        {
            _root->SetFullscreen(true);
        }

        AppLogic::Current()->NotifyRootInitialized();
    }

    void TerminalWindow::PersistState()
    {
        if (_root)
        {
            _root->PersistState();
        }
    }

    winrt::Windows::UI::Xaml::ElementTheme TerminalWindow::GetRequestedTheme()
    {
        return Theme().RequestedTheme();
    }

    bool TerminalWindow::GetShowTabsInTitlebar()
    {
        return _settings.GlobalSettings().ShowTabsInTitlebar();
    }

    bool TerminalWindow::GetInitialAlwaysOnTop()
    {
        return _settings.GlobalSettings().AlwaysOnTop();
    }

    bool TerminalWindow::GetInitialShowTabsFullscreen()
    {
        return _settings.GlobalSettings().ShowTabsFullscreen();
    }

    bool TerminalWindow::GetMinimizeToNotificationArea()
    {
        return _settings.GlobalSettings().MinimizeToNotificationArea();
    }

    bool TerminalWindow::GetAlwaysShowNotificationIcon()
    {
        return _settings.GlobalSettings().AlwaysShowNotificationIcon();
    }

    bool TerminalWindow::GetShowTitleInTitlebar()
    {
        return _settings.GlobalSettings().ShowTitleInTitlebar();
    }

    Microsoft::Terminal::Settings::Model::Theme TerminalWindow::Theme()
    {
        return _settings.GlobalSettings().CurrentTheme();
    }

    // WinUI can't show 2 dialogs simultaneously. Yes, really. If you do, you get an exception.
    // As such, we must dismiss whatever dialog is currently being shown.
    //
    // This limit is of course per-thread and not per-window. Yes... really. See:
    //   https://github.com/microsoft/microsoft-ui-xaml/issues/794
    // The consequence is that we use a static variable to keep track of the shown dialog.
    static ContentDialog s_activeDialog{ nullptr };

    // Method Description:
    // - Show a ContentDialog with buttons to take further action. Uses the
    //   FrameworkElements provided as the title and content of this dialog, and
    //   displays buttons (or a single button). Two buttons (primary and secondary)
    //   will be displayed if this is a warning dialog for closing the terminal,
    //   this allows the users to abandon the closing action. Otherwise, a single
    //   close button will be displayed.
    // - Only one dialog can be visible at a time. If another dialog is visible
    //   when this is called, nothing happens.
    // Arguments:
    // - dialog: the dialog object that is going to show up
    // Return value:
    // - an IAsyncOperation with the dialog result
    winrt::Windows::Foundation::IAsyncOperation<ContentDialogResult> TerminalWindow::ShowDialog(winrt::WUX::Controls::ContentDialog dialog)
    {
        const auto weak = get_weak();
        const auto dispatcher = _root->Dispatcher();
        const auto root = _root->XamlRoot();

        // As mentioned on s_activeDialog, dismissing the active dialog is necessary.
        // We repeat it a few times in case the resume_foreground failed to work,
        // but I found that one iteration will always be enough in practice.
        for (int i = 0; i < 3; ++i)
        {
            if (!s_activeDialog)
            {
                break;
            }

            s_activeDialog.Hide();

            // Wait for the current dialog to be hidden.
            co_await wil::resume_foreground(dispatcher, CoreDispatcherPriority::Low);
        }

        // If two sources call ShowDialog() simultaneously, it may happen that both enter the above loop,
        // but it's crucial that only one of them continues below as only 1 dialog can be shown at a time.
        // Thankfully, everything runs on the UI thread, so only 1 caller will exit the above loop at a time.
        // So, if s_activeDialog is still set at this point, we must have lost the race.
        if (s_activeDialog)
        {
            co_return ContentDialogResult::None;
        }

        s_activeDialog = dialog;

        // IMPORTANT: This is necessary as documented in the ContentDialog MSDN docs.
        // Since we're hosting the dialog in a Xaml island, we need to connect it to the
        // xaml tree somehow.
        dialog.XamlRoot(root);

        // IMPORTANT: Set the requested theme of the dialog, because the
        // PopupRoot isn't directly in the Xaml tree of our root. So the dialog
        // won't inherit our RequestedTheme automagically.
        // GH#5195, GH#3654 Because we cannot set RequestedTheme at the application level,
        // we occasionally run into issues where parts of our UI end up themed incorrectly.
        // Dialogs, for example, live under a different Xaml root element than the rest of
        // our application. This makes our popup menus and buttons "disappear" when the
        // user wants Terminal to be in a different theme than the rest of the system.
        // This hack---and it _is_ a hack--walks up a dialog's ancestry and forces the
        // theme on each element up to the root. We're relying a bit on Xaml's implementation
        // details here, but it does have the desired effect.
        // It's not enough to set the theme on the dialog alone.
        auto themingLambda{ [weak](const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&) {
            if (const auto strong = weak.get())
            {
                auto theme{ strong->_settings.GlobalSettings().CurrentTheme() };
                auto requestedTheme{ theme.RequestedTheme() };
                auto element{ sender.try_as<winrt::Windows::UI::Xaml::FrameworkElement>() };
                while (element)
                {
                    element.RequestedTheme(requestedTheme);
                    element = element.Parent().try_as<winrt::Windows::UI::Xaml::FrameworkElement>();
                }
            }
        } };

        auto result = ContentDialogResult::None;

        // Extra scope to drop the revoker before resetting the s_activeDialog to null.
        {
            themingLambda(dialog, nullptr); // if it's already in the tree
            auto loadedRevoker{ dialog.Loaded(winrt::auto_revoke, themingLambda) }; // if it's not yet in the tree
            result = co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup);
        }

        s_activeDialog = nullptr;
        co_return result;
    }

    // Method Description:
    // - Dismiss the (only) visible ContentDialog
    void TerminalWindow::DismissDialog()
    {
        if (s_activeDialog)
        {
            s_activeDialog.Hide();
        }
    }

    // Method Description:
    // - Displays a dialog for errors found while loading or validating the
    //   settings. Uses the resources under the provided  title and content keys
    //   as the title and first content of the dialog, then also displays a
    //   message for whatever exception was found while validating the settings.
    // - Only one dialog can be visible at a time. If another dialog is visible
    //   when this is called, nothing happens. See ShowDialog for details
    // Arguments:
    // - titleKey: The key to use to lookup the title text from our resources.
    // - contentKey: The key to use to lookup the content text from our resources.
    void TerminalWindow::_ShowLoadErrorsDialog(const winrt::hstring& titleKey,
                                               const winrt::hstring& contentKey,
                                               HRESULT settingsLoadedResult,
                                               const winrt::hstring& exceptionText)
    {
        auto title = GetLibraryResourceString(titleKey);
        auto buttonText = RS_(L"Ok");

        Controls::TextBlock warningsTextBlock;
        warningsTextBlock.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
        // Make sure you can copy-paste
        warningsTextBlock.IsTextSelectionEnabled(true);
        // Make sure the lines of text wrap
        warningsTextBlock.TextWrapping(TextWrapping::Wrap);

        winrt::Windows::UI::Xaml::Documents::Run errorRun;
        const auto errorLabel = GetLibraryResourceString(contentKey);
        errorRun.Text(errorLabel);
        warningsTextBlock.Inlines().Append(errorRun);
        warningsTextBlock.Inlines().Append(Documents::LineBreak{});

        if (FAILED(settingsLoadedResult))
        {
            if (!exceptionText.empty())
            {
                warningsTextBlock.Inlines().Append(_BuildErrorRun(exceptionText,
                                                                  winrt::WUX::Application::Current().as<::winrt::TerminalApp::App>().Resources()));
                warningsTextBlock.Inlines().Append(Documents::LineBreak{});
            }
        }

        // Add a note that we're using the default settings in this case.
        winrt::Windows::UI::Xaml::Documents::Run usingDefaultsRun;
        const auto usingDefaultsText = RS_(L"UsingDefaultSettingsText");
        usingDefaultsRun.Text(usingDefaultsText);
        warningsTextBlock.Inlines().Append(Documents::LineBreak{});
        warningsTextBlock.Inlines().Append(usingDefaultsRun);

        Controls::ContentDialog dialog;
        dialog.Title(winrt::box_value(title));
        dialog.Content(winrt::box_value(warningsTextBlock));
        dialog.CloseButtonText(buttonText);
        dialog.DefaultButton(Controls::ContentDialogButton::Close);

        ShowDialog(dialog);
    }

    // Method Description:
    // - Displays a dialog for warnings found while loading or validating the
    //   settings. Displays messages for whatever warnings were found while
    //   validating the settings.
    // - Only one dialog can be visible at a time. If another dialog is visible
    //   when this is called, nothing happens. See ShowDialog for details
    void TerminalWindow::_ShowLoadWarningsDialog(const IInspectable&, const Windows::Foundation::Collections::IVectorView<SettingsLoadWarnings>& warnings)
    {
        auto title = RS_(L"SettingsValidateErrorTitle");
        auto buttonText = RS_(L"Ok");

        Controls::TextBlock warningsTextBlock;
        warningsTextBlock.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
        // Make sure you can copy-paste
        warningsTextBlock.IsTextSelectionEnabled(true);
        // Make sure the lines of text wrap
        warningsTextBlock.TextWrapping(TextWrapping::Wrap);

        for (const auto& warning : warnings)
        {
            // Try looking up the warning message key for each warning.
            const auto warningText = _GetWarningText(warning);
            if (!warningText.empty())
            {
                warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, winrt::WUX::Application::Current().as<::winrt::TerminalApp::App>().Resources()));
                warningsTextBlock.Inlines().Append(Documents::LineBreak{});
            }
        }

        Controls::ContentDialog dialog;
        dialog.Title(winrt::box_value(title));
        dialog.Content(winrt::box_value(warningsTextBlock));
        dialog.CloseButtonText(buttonText);
        dialog.DefaultButton(Controls::ContentDialogButton::Close);

        ShowDialog(dialog);
    }

    // Method Description:
    // - Triggered when the application is finished loading. If we failed to load
    //   the settings, then this will display the error dialog. This is done
    //   here instead of when loading the settings, because we need our UI to be
    //   visible to display the dialog, and when we're loading the settings,
    //   the UI might not be visible yet.
    // Arguments:
    // - <unused>
    void TerminalWindow::_OnLoaded(const IInspectable& /*sender*/,
                                   const RoutedEventArgs& /*eventArgs*/)
    {
        if (_settings.GlobalSettings().InputServiceWarning())
        {
            const auto keyboardServiceIsDisabled = !_IsKeyboardServiceEnabled();
            if (keyboardServiceIsDisabled)
            {
                _root->ShowKeyboardServiceWarning();
            }
        }

        const auto& settingsLoadedResult = gsl::narrow_cast<HRESULT>(_initialLoadResult.Result());
        if (FAILED(settingsLoadedResult))
        {
            const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle");
            const winrt::hstring textKey = USES_RESOURCE(L"InitialJsonParseErrorText");
            _ShowLoadErrorsDialog(titleKey, textKey, settingsLoadedResult, _initialLoadResult.ExceptionText());
        }
        else if (settingsLoadedResult == S_FALSE)
        {
            _ShowLoadWarningsDialog(nullptr, _initialLoadResult.Warnings());
        }
    }

    // Method Description:
    // - Helper for determining if the "Touch Keyboard and Handwriting Panel
    //   Service" is enabled. If it isn't, we want to be able to display a
    //   warning to the user, because they won't be able to type in the
    //   Terminal.
    // Return Value:
    // - true if the service is enabled, or if we fail to query the service. We
    //   return true in that case, to be less noisy (though, that is unexpected)
    bool TerminalWindow::_IsKeyboardServiceEnabled()
    {
        // If at any point we fail to open the service manager, the service,
        // etc, then just quick return true to disable the dialog. We'd rather
        // not be noisy with this dialog if we failed for some reason.

        // Open the service manager. This will return 0 if it failed.
        wil::unique_schandle hManager{ OpenSCManagerW(nullptr, nullptr, 0) };

        if (LOG_LAST_ERROR_IF(!hManager.is_valid()))
        {
            return true;
        }

        // Get a handle to the keyboard service
        wil::unique_schandle hService{ OpenServiceW(hManager.get(), TabletInputServiceKey.data(), SERVICE_QUERY_STATUS) };

        // Windows 11 doesn't have a TabletInputService.
        // (It was renamed to TextInputManagementService, because people kept thinking that a
        // service called "tablet-something" is system-irrelevant on PCs and can be disabled.)
        if (!hService.is_valid())
        {
            return true;
        }

        // Get the current state of the service
        SERVICE_STATUS status{ 0 };
        if (!LOG_IF_WIN32_BOOL_FALSE(QueryServiceStatus(hService.get(), &status)))
        {
            return true;
        }

        const auto state = status.dwCurrentState;
        return (state == SERVICE_RUNNING || state == SERVICE_START_PENDING);
    }

    // Method Description:
    // - Get the size in pixels of the client area we'll need to launch this
    //   terminal app. This method will use the default profile's settings to do
    //   this calculation, as well as the _system_ dpi scaling. See also
    //   TermControl::GetProposedDimensions.
    // Arguments:
    // - <none>
    // Return Value:
    // - a point containing the requested dimensions in pixels.
    winrt::Windows::Foundation::Size TerminalWindow::GetLaunchDimensions(uint32_t dpi)
    {
        winrt::Windows::Foundation::Size proposedSize{};

        // In focus mode, we don't want to include our own tab row in the size
        // of the window that we hand back. So we account for passing
        // --focusMode on the commandline here, and the mode in the settings.
        // Below, we'll also account for if focus mode was persisted into the
        // session for restoration.
        bool focusMode = _appArgs && _appArgs->ParsedArgs().GetLaunchMode().value_or(_settings.GlobalSettings().LaunchMode()) == LaunchMode::FocusMode;

        const auto scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
        if (const auto layout = LoadPersistedLayout())
        {
            if (layout.LaunchMode())
            {
                focusMode = layout.LaunchMode().Value() == LaunchMode::FocusMode;
            }

            if (layout.InitialSize())
            {
                proposedSize = layout.InitialSize().Value();
                // The size is saved as a non-scaled real pixel size,
                // so we need to scale it appropriately.
                proposedSize.Height = proposedSize.Height * scale;
                proposedSize.Width = proposedSize.Width * scale;
            }
        }

        if ((_appArgs && _appArgs->ParsedArgs().GetSize().has_value()) || (proposedSize.Width == 0 && proposedSize.Height == 0))
        {
            // Use the default profile to determine how big of a window we need.
            const auto settings{ Settings::TerminalSettings::CreateWithNewTerminalArgs(_settings, nullptr) };

            const til::size emptySize{};
            const auto commandlineSize = _appArgs ? _appArgs->ParsedArgs().GetSize().value_or(emptySize) : til::size{};
            proposedSize = TermControl::GetProposedDimensions(*settings.DefaultSettings(),
                                                              dpi,
                                                              commandlineSize.width,
                                                              commandlineSize.height);
        }

        if (_contentBounds)
        {
            // If we've been created as a torn-out window, then we'll need to
            // use that size instead. _contentBounds is in DIPs. Scale
            // accordingly to the new pixel size.
            return {
                _contentBounds.Value().Width * scale,
                _contentBounds.Value().Height * scale
            };
        }

        // GH#2061 - If the global setting "Always show tab bar" is
        // set or if "Show tabs in title bar" is set, then we'll need to add
        // the height of the tab bar here.
        if (_settings.GlobalSettings().ShowTabsInTitlebar() && !focusMode)
        {
            // In the past, we used to actually instantiate a TitlebarControl
            // and use Measure() to determine the DesiredSize of the control, to
            // reserve exactly what we'd need.
            //
            // We can't do that anymore, because this is now called _before_
            // we've initialized XAML for this thread. We can't start XAML till
            // we have an HWND, and we can't finish creating the window till we
            // know how big it should be.
            //
            // Instead, we'll just hardcode how big the titlebar should be. If
            // the titlebar / tab row ever change size, these numbers will have
            // to change accordingly.

            static constexpr auto titlebarHeight = 40;
            proposedSize.Height += (titlebarHeight)*scale;
        }
        else if (_settings.GlobalSettings().AlwaysShowTabs() && !focusMode)
        {
            // Same comment as above, but with a TabRowControl.
            //
            // A note from before: For whatever reason, there's about 10px of
            // unaccounted-for space in the application. I couldn't tell you
            // where these 10px are coming from, but they need to be included in
            // this math.
            static constexpr auto tabRowHeight = 32;
            proposedSize.Height += (tabRowHeight + 10) * scale;
        }

        return proposedSize;
    }

    // Method Description:
    // - Get the launch mode in json settings file. Now there
    //   two launch mode: default, maximized. Default means the window
    //   will launch according to the launch dimensions provided. Maximized
    //   means the window will launch as a maximized window
    // Arguments:
    // - <none>
    // Return Value:
    // - LaunchMode enum that indicates the launch mode
    LaunchMode TerminalWindow::GetLaunchMode()
    {
        if (_contentBounds)
        {
            return LaunchMode::DefaultMode;
        }

        // GH#4620/#5801 - If the user passed --maximized or --fullscreen on the
        // commandline, then use that to override the value from the settings.
        const auto valueFromSettings = _settings.GlobalSettings().LaunchMode();
        const auto valueFromCommandlineArgs = _appArgs ? _appArgs->ParsedArgs().GetLaunchMode() : std::nullopt;
        if (const auto layout = LoadPersistedLayout())
        {
            if (layout.LaunchMode())
            {
                return layout.LaunchMode().Value();
            }
        }
        return valueFromCommandlineArgs.has_value() ?
                   valueFromCommandlineArgs.value() :
                   valueFromSettings;
    }

    // Method Description:
    // - Get the user defined initial position from Json settings file.
    //   This position represents the top left corner of the Terminal window.
    //   This setting is optional, if not provided, we will use the system
    //   default size, which is provided in IslandWindow::MakeWindow.
    // Arguments:
    // - defaultInitialX: the system default x coordinate value
    // - defaultInitialY: the system default y coordinate value
    // Return Value:
    // - a point containing the requested initial position in pixels.
    TerminalApp::InitialPosition TerminalWindow::GetInitialPosition(int64_t defaultInitialX, int64_t defaultInitialY)
    {
        auto initialPosition{ _settings.GlobalSettings().InitialPosition() };

        if (const auto layout = LoadPersistedLayout())
        {
            if (layout.InitialPosition())
            {
                initialPosition = layout.InitialPosition().Value();
            }
        }

        // Commandline args trump everything except for content bounds (tear-out)
        if (_appArgs && _appArgs->ParsedArgs().GetPosition().has_value())
        {
            initialPosition = _appArgs->ParsedArgs().GetPosition().value();
        }

        if (_contentBounds)
        {
            // If the user has specified a contentBounds, then we should use
            // that to determine the initial position of the window. This is
            // used when the user is dragging a tab out of the window, to create
            // a new window.
            // BODY: Technically we aren't guaranteed to be within the XAML stack right now to have a "current view".
            const auto scale = static_cast<float>(DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel());
            const auto bounds = _contentBounds.Value();
            initialPosition = { lroundf(bounds.X * scale), lroundf(bounds.Y * scale) };
        }
        return {
            initialPosition.X ? initialPosition.X.Value() : defaultInitialX,
            initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY
        };
    }

    bool TerminalWindow::CenterOnLaunch()
    {
        // If
        // * the position has been specified on the commandline,
        // * we're re-opening from a persisted layout,
        // * We're opening the window as a part of tear out (and _contentBounds were set)
        // then don't center on launch
        bool hadPersistedPosition = false;
        if (const auto layout = LoadPersistedLayout())
        {
            hadPersistedPosition = (bool)layout.InitialPosition();
        }

        return !_contentBounds &&
               !hadPersistedPosition &&
               _settings.GlobalSettings().CenterOnLaunch() &&
               (_appArgs && !_appArgs->ParsedArgs().GetPosition().has_value());
    }

    // Method Description:
    // - See Pane::CalcSnappedDimension
    float TerminalWindow::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
    {
        return _root->CalcSnappedDimension(widthOrHeight, dimension);
    }

    // Method Description:
    // - Update the current theme of the application. This will trigger our
    //   RequestedThemeChanged event, to have our host change the theme of the
    //   root of the application.
    // Arguments:
    // - newTheme: The ElementTheme to apply to our elements.
    void TerminalWindow::_RefreshThemeRoutine()
    {
        // Propagate the event to the host layer, so it can update its own UI
        RequestedThemeChanged.raise(*this, Theme());
    }

    // This may be called on a background thread, or the main thread, but almost
    // definitely not on OUR UI thread.
    void TerminalWindow::UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args)
    {
        _settings = args.NewSettings();

        // Update the settings in TerminalPage
        // We're on our UI thread right now, so this is safe
        _root->SetSettings(_settings, true);

        // Bubble the notification up to the AppHost, now that we've updated our _settings.
        SettingsChanged.raise(*this, args);

        if (FAILED(args.Result()))
        {
            const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
            const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
            _ShowLoadErrorsDialog(titleKey,
                                  textKey,
                                  gsl::narrow_cast<HRESULT>(args.Result()),
                                  args.ExceptionText());
            return;
        }
        else if (args.Result() == S_FALSE)
        {
            _ShowLoadWarningsDialog(nullptr, args.Warnings());
        }
        else if (args.Result() == S_OK)
        {
            DismissDialog();
        }
        _RefreshThemeRoutine();
    }

    void TerminalWindow::_OpenSettingsUI()
    {
        _root->OpenSettingsUI();
    }
    UIElement TerminalWindow::GetRoot() noexcept
    {
        return _root.as<winrt::Windows::UI::Xaml::Controls::Control>();
    }

    winrt::Windows::Foundation::Collections::IVector<IPaneContent> TerminalWindow::Panes() const
    {
        return winrt::single_threaded_vector(_root->Panes());
    }

    // Method Description:
    // - Gets the title of the currently focused terminal control. If there
    //   isn't a control selected for any reason, returns "Terminal"
    // Arguments:
    // - <none>
    // Return Value:
    // - the title of the focused control if there is one, else "Terminal"
    hstring TerminalWindow::Title()
    {
        if (_root)
        {
            return _root->Title();
        }
        return { L"Terminal" };
    }

    // Method Description:
    // - Used to tell the app that the titlebar has been clicked. The App won't
    //   actually receive any clicks in the titlebar area, so this is a helper
    //   to clue the app in that a click has happened. The App will use this as
    //   a indicator that it needs to dismiss any open flyouts.
    // Arguments:
    // - <none>
    // Return Value:
    // - <none>
    void TerminalWindow::TitlebarClicked()
    {
        if (_root)
        {
            _root->TitlebarClicked();
        }
    }

    // Method Description:
    // - Used to tell the PTY connection that the window visibility has changed.
    //   The underlying PTY might need to expose window visibility status to the
    //   client application for the `::GetConsoleWindow()` API.
    // Arguments:
    // - showOrHide - True is show; false is hide.
    // Return Value:
    // - <none>
    void TerminalWindow::WindowVisibilityChanged(const bool showOrHide)
    {
        if (_root)
        {
            _root->WindowVisibilityChanged(showOrHide);
        }
    }

    // Method Description:
    // - Implements the F7 handler (per GH#638)
    // - Implements the Alt handler (per GH#6421)
    // Return value:
    // - whether the key was handled
    bool TerminalWindow::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
    {
        if (_root)
        {
            // Manually bubble the OnDirectKeyEvent event up through the focus tree.
            auto xamlRoot{ _root->XamlRoot() };
            if (!xamlRoot)
            {
                return false;
            }

            auto focusedObject{ Windows::UI::Xaml::Input::FocusManager::GetFocusedElement(xamlRoot) };
            do
            {
                if (auto keyListener{ focusedObject.try_as<UI::IDirectKeyListener>() })
                {
                    if (keyListener.OnDirectKeyEvent(vkey, scanCode, down))
                    {
                        return true;
                    }
                    // otherwise, keep walking. bubble the event manually.
                }

                if (auto focusedElement{ focusedObject.try_as<Windows::UI::Xaml::FrameworkElement>() })
                {
                    focusedObject = focusedElement.Parent();

                    // Parent() seems to return null when the focusedElement is created from an ItemTemplate.
                    // Use the VisualTreeHelper's GetParent as a fallback.
                    if (!focusedObject)
                    {
                        focusedObject = winrt::Windows::UI::Xaml::Media::VisualTreeHelper::GetParent(focusedElement);

                        // We were unable to find a focused object. Give the
                        // TerminalPage one last chance to let the alt+space
                        // menu still work.
                        //
                        // We return always, because the TerminalPage handler
                        // will return false for just a bare `alt` press, and
                        // don't want to go around the loop again.
                        if (!focusedObject)
                        {
                            if (auto keyListener{ _root.try_as<UI::IDirectKeyListener>() })
                            {
                                return keyListener.OnDirectKeyEvent(vkey, scanCode, down);
                            }
                        }
                    }
                }
                else
                {
                    break; // we hit a non-FE object, stop bubbling.
                }
            } while (focusedObject);
        }

        return false;
    }

    // Method Description:
    // - Used to tell the app that the 'X' button has been clicked and
    //   the user wants to close the app. We kick off the close warning
    //   experience.
    // Arguments:
    // - <none>
    // Return Value:
    // - <none>
    void TerminalWindow::CloseWindow()
    {
        if (_root)
        {
            _root->CloseWindow();
        }
    }

    winrt::TerminalApp::TaskbarState TerminalWindow::TaskbarState()
    {
        if (_root)
        {
            return _root->TaskbarState();
        }
        return {};
    }

    winrt::Windows::UI::Xaml::Media::Brush TerminalWindow::TitlebarBrush()
    {
        return _root ? _root->TitlebarBrush() : nullptr;
    }
    winrt::Windows::UI::Xaml::Media::Brush TerminalWindow::FrameBrush()
    {
        return _root ? _root->FrameBrush() : nullptr;
    }

    void TerminalWindow::WindowActivated(const bool activated)
    {
        if (_root)
        {
            _root->WindowActivated(activated);
        }
    }

    bool TerminalWindow::FocusMode() const
    {
        return _root ? _root->FocusMode() : false;
    }

    bool TerminalWindow::Fullscreen() const
    {
        return _root ? _root->Fullscreen() : false;
    }

    void TerminalWindow::Maximized(bool newMaximized)
    {
        if (_root)
        {
            _root->Maximized(newMaximized);
        }
    }

    bool TerminalWindow::AlwaysOnTop() const
    {
        return _root ? _root->AlwaysOnTop() : false;
    }

    bool TerminalWindow::ShowTabsFullscreen() const
    {
        return _root ? _root->ShowTabsFullscreen() : false;
    }

    void TerminalWindow::SetSettingsStartupArgs(const std::vector<ActionAndArgs>& actions)
    {
        for (const auto& action : actions)
        {
            _settingsStartupArgs.push_back(action);
        }
        _gotSettingsStartupActions = true;
    }

    bool TerminalWindow::HasCommandlineArguments() const noexcept
    {
        return _hasCommandLineArguments;
    }

    // Method Description:
    // - Sets the initial commandline to process on startup, and attempts to
    //   parse it. Commands will be parsed into a list of ShortcutActions that
    //   will be processed on TerminalPage::Create().
    // - This function will have no effective result after Create() is called.
    // - This function returns 0, unless a there was a non-zero result from
    //   trying to parse one of the commands provided. In that case, no commands
    //   after the failing command will be parsed, and the non-zero code
    //   returned.
    // Arguments:
    // - args: an array of strings to process as a commandline. These args can contain spaces
    // - cwd: The CWD that this window should treat as its own "virtual" CWD
    // Return Value:
    // - the result of the first command who's parsing returned a non-zero code,
    //   or 0. (see TerminalWindow::_ParseArgs)
    int32_t TerminalWindow::SetStartupCommandline(TerminalApp::CommandlineArgs args)
    {
        _appArgs = winrt::get_self<CommandlineArgs>(args);
        _startupConnection = args.Connection();
        auto& parsedArgs = _appArgs->ParsedArgs();

        _WindowProperties->SetInitialCwd(_appArgs->CurrentDirectory());
        _WindowProperties->VirtualEnvVars(_appArgs->CurrentEnvironment());

        // This is called in AppHost::ctor(), before we've created the window
        // (or called TerminalWindow::Initialize)
        if (_appArgs->ExitCode() == 0)
        {
            // The existing logic (before this commit) strictly relied on
            // ValidateStartupCommands() only to be called for new windows.
            // It modifies the actions it stores.
            parsedArgs.ValidateStartupCommands();

            // If the size of the arguments list is 1,
            // then it contains only the executable name and no other arguments.
            _hasCommandLineArguments = _appArgs->CommandlineRef().size() > 1;

            // DON'T pass the args into the page yet. It doesn't exist yet.
            // Instead, we'll handle that in Initialize, when we first instantiate the page.
        }

        // If we have a -s param passed to us to load a saved layout, cache that now.
        if (const auto idx = parsedArgs.GetPersistedLayoutIdx())
        {
            SetPersistedLayoutIdx(idx.value());
        }

        return _appArgs->ExitCode();
    }

    void TerminalWindow::SetStartupContent(const winrt::hstring& content,
                                           const Windows::Foundation::IReference<Windows::Foundation::Rect>& bounds)
    {
        _contentBounds = bounds;

        const auto args = _contentStringToActions(content, true);
        _initialContentArgs = wil::to_vector(args);
    }

    // Method Description:
    // - Parse the provided commandline arguments into actions, and try to
    //   perform them immediately.
    // - This function returns 0, unless a there was a non-zero result from
    //   trying to parse one of the commands provided. In that case, no commands
    //   after the failing command will be parsed, and the non-zero code
    //   returned.
    // - If a non-empty cwd is provided, the entire terminal exe will switch to
    //   that CWD while we handle these actions, then return to the original
    //   CWD.
    // Arguments:
    // - args: an array of strings to process as a commandline. These args can contain spaces
    // - cwd: The directory to use as the CWD while performing these actions.
    // Return Value:
    // - the result of the first command who's parsing returned a non-zero code,
    //   or 0. (see TerminalWindow::_ParseArgs)
    int32_t TerminalWindow::ExecuteCommandline(TerminalApp::CommandlineArgs args)
    {
        _appArgs = winrt::get_self<CommandlineArgs>(args);

        if (_appArgs->ExitCode() == 0)
        {
            auto& parsedArgs = _appArgs->ParsedArgs();
            auto& actions = parsedArgs.GetStartupActions();

            if (auto conn = args.Connection())
            {
                _root->CreateTabFromConnection(std::move(conn));
            }
            else if (!actions.empty())
            {
                _root->ProcessStartupActions(actions, _appArgs->CurrentDirectory(), _appArgs->CurrentEnvironment());
            }
        }

        // Return the result of parsing with commandline, though it may or may not be used.
        return _appArgs->ExitCode();
    }

    void TerminalWindow::SetPersistedLayoutIdx(const uint32_t idx)
    {
        _loadFromPersistedLayoutIdx = idx;
        _cachedLayout = std::nullopt;
    }

    // Method Description;
    // - Checks if the current window is configured to load a particular layout
    // Arguments:
    // - settings: The settings to use as this may be called before the page is
    //   fully initialized.
    // Return Value:
    // - non-null if there is a particular saved layout to use
    std::optional<uint32_t> TerminalWindow::LoadPersistedLayoutIdx() const
    {
        return _settings.GlobalSettings().ShouldUsePersistedLayout() ? _loadFromPersistedLayoutIdx : std::nullopt;
    }

    WindowLayout TerminalWindow::LoadPersistedLayout()
    {
        if (_cachedLayout.has_value())
        {
            return *_cachedLayout;
        }

        if (const auto idx = LoadPersistedLayoutIdx())
        {
            const auto i = idx.value();
            const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();
            if (layouts && layouts.Size() > i)
            {
                auto layout = layouts.GetAt(i);

                // TODO: GH#12633: Right now, we're manually making sure that we
                // have at least one tab to restore. If we ever want to come
                // back and make it so that you can persist position and size,
                // but not the tabs themselves, we can revisit this assumption.
                _cachedLayout = (layout.TabLayout() && layout.TabLayout().Size() > 0) ? layout : nullptr;
                return *_cachedLayout;
            }
        }
        _cachedLayout = nullptr;
        return *_cachedLayout;
    }

    void TerminalWindow::RequestExitFullscreen()
    {
        _root->SetFullscreen(false);
    }

    bool TerminalWindow::AutoHideWindow()
    {
        return _settings.GlobalSettings().AutoHideWindow();
    }

    void TerminalWindow::UpdateSettingsHandler(const winrt::IInspectable& /*sender*/,
                                               const winrt::TerminalApp::SettingsLoadEventArgs& args)
    {
        UpdateSettings(args);
    }

    void TerminalWindow::IdentifyWindow()
    {
        if (_root)
        {
            _root->IdentifyWindow();
        }
    }

    void TerminalWindow::WindowName(const winrt::hstring& name)
    {
        const auto oldIsQuakeMode = _WindowProperties->IsQuakeWindow();
        _WindowProperties->WindowName(name);
        if (!_root)
        {
            return;
        }
        const auto newIsQuakeMode = _WindowProperties->IsQuakeWindow();
        if (newIsQuakeMode != oldIsQuakeMode)
        {
            // If we're entering Quake Mode from ~Focus Mode, then this will enter Focus Mode
            // If we're entering Quake Mode from Focus Mode, then this will do nothing
            // If we're leaving Quake Mode (we're already in Focus Mode), then this will do nothing
            _root->SetFocusMode(true);
            IsQuakeWindowChanged.raise(*this, nullptr);
        }
    }
    void TerminalWindow::WindowId(const uint64_t& id)
    {
        _WindowProperties->WindowId(id);
    }

    // Method Description:
    // - Deserialize this string of content into a list of actions to perform.
    //   If replaceFirstWithNewTab is true and the first serialized action is a
    //   `splitPane` action, we'll attempt to replace that action with the
    //   equivalent `newTab` action.
    IVector<Settings::Model::ActionAndArgs> TerminalWindow::_contentStringToActions(const winrt::hstring& content,
                                                                                    const bool replaceFirstWithNewTab)
    {
        try
        {
            const auto args = ActionAndArgs::Deserialize(content);
            if (args == nullptr ||
                args.Size() == 0)
            {
                return args;
            }

            const auto& firstAction = args.GetAt(0);
            const bool firstIsSplitPane{ firstAction.Action() == ShortcutAction::SplitPane };
            if (replaceFirstWithNewTab &&
                firstIsSplitPane)
            {
                // Create the equivalent NewTab action.
                const auto newAction = Settings::Model::ActionAndArgs{ Settings::Model::ShortcutAction::NewTab,
                                                                       Settings::Model::NewTabArgs(firstAction.Args() ?
                                                                                                       firstAction.Args().try_as<Settings::Model::SplitPaneArgs>().ContentArgs() :
                                                                                                       nullptr) };
                args.SetAt(0, newAction);
            }

            return args;
        }
        catch (...)
        {
            LOG_CAUGHT_EXCEPTION();
        }

        return nullptr;
    }

    void TerminalWindow::AttachContent(winrt::hstring content, uint32_t tabIndex)
    {
        if (_root)
        {
            // `splitPane` allows the user to specify which tab to split. In that
            // case, split specifically the requested pane.
            //
            // If there's not enough tabs, then just turn this pane into a new tab.
            //
            // If the first action is `newTab`, the index is always going to be 0,
            // so don't do anything in that case.

            const bool replaceFirstWithNewTab = tabIndex >= _root->NumberOfTabs();

            auto args = _contentStringToActions(content, replaceFirstWithNewTab);

            _root->AttachContent(std::move(args), tabIndex);
        }
    }
    void TerminalWindow::SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args)
    {
        if (_root)
        {
            _root->SendContentToOther(args);
        }
    }

    bool TerminalWindow::ShouldImmediatelyHandoffToElevated()
    {
        return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false;
    }

    // Method Description:
    // - Escape hatch for immediately dispatching requests to elevated windows
    //   when first launched. At this point in startup, the window doesn't exist
    //   yet, XAML hasn't been started, but we need to dispatch these actions.
    //   We can't just go through ProcessStartupActions, because that processes
    //   the actions async using the XAML dispatcher (which doesn't exist yet)
    // - DON'T CALL THIS if you haven't already checked
    //   ShouldImmediatelyHandoffToElevated. If you're thinking about calling
    //   this outside of the one place it's used, that's probably the wrong
    //   solution.
    // Arguments:
    // - settings: the settings we should use for dispatching these actions. At
    //   this point in startup, we hadn't otherwise been initialized with these,
    //   so use them now.
    // Return Value:
    // - <none>
    void TerminalWindow::HandoffToElevated()
    {
        if (_root)
        {
            _root->HandoffToElevated(_settings);
            return;
        }
    }

    void TerminalWindow::_WindowSizeChanged(const IInspectable&, winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs args)
    {
        winrt::Windows::Foundation::Size pixelSize = { static_cast<float>(args.Width()), static_cast<float>(args.Height()) };
        const auto scale = static_cast<float>(DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel());

        if (!FocusMode())
        {
            if (!_settings.GlobalSettings().AlwaysShowTabs())
            {
                // Hide the title bar = off, Always show tabs = off.
                static constexpr auto titlebarHeight = 10;
                pixelSize.Height += (titlebarHeight)*scale;
            }
            else if (!_settings.GlobalSettings().ShowTabsInTitlebar())
            {
                // Hide the title bar = off, Always show tabs = on.
                static constexpr auto titlebarAndTabBarHeight = 40;
                pixelSize.Height += (titlebarAndTabBarHeight)*scale;
            }
            // Hide the title bar = on, Always show tabs = on.
            // In this case, we don't add any height because
            // NonClientIslandWindow::GetTotalNonClientExclusiveSize() gets
            // called in AppHost::_resizeWindow and it already takes title bar
            // height into account.  In other cases above
            // IslandWindow::GetTotalNonClientExclusiveSize() is called, and it
            // doesn't take the title bar height into account, so we have to do
            // the calculation manually.
        }

        args.Width(static_cast<int32_t>(pixelSize.Width));
        args.Height(static_cast<int32_t>(pixelSize.Height));

        WindowSizeChanged.raise(*this, args);
    }

    void TerminalWindow::_RenameWindowRequested(const IInspectable&, const winrt::TerminalApp::RenameWindowRequestedArgs args)
    {
        WindowName(args.ProposedName());
    }

    winrt::hstring WindowProperties::WindowName() const noexcept
    {
        return _WindowName;
    }

    void WindowProperties::WindowName(const winrt::hstring& value)
    {
        if (_WindowName != value)
        {
            _WindowName = value;
            // If we get initialized with a window name, this will be called
            // before XAML is stood up, and constructing a
            // PropertyChangedEventArgs will throw.
            try
            {
                PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowName" });
                PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowNameForDisplay" });
            }
            CATCH_LOG();
        }
    }

    uint64_t WindowProperties::WindowId() const noexcept
    {
        return _WindowId;
    }

    void WindowProperties::WindowId(const uint64_t& value)
    {
        _WindowId = value;
    }

    // Method Description:
    // - Returns a label like "Window: 1234" for the ID of this window
    // Arguments:
    // - <none>
    // Return Value:
    // - a string for displaying the name of the window.
    winrt::hstring WindowProperties::WindowIdForDisplay() const noexcept
    {
        return til::hstring_format(FMT_COMPILE(L"{}: {}"), std::wstring_view(RS_(L"WindowIdLabel")), _WindowId);
    }

    // Method Description:
    // - Returns a label like "<unnamed window>" when the window has no name, or the name of the window.
    // Arguments:
    // - <none>
    // Return Value:
    // - a string for displaying the name of the window.
    winrt::hstring WindowProperties::WindowNameForDisplay() const noexcept
    {
        return _WindowName.empty() ? til::hstring_format(FMT_COMPILE(L"<{}>"), RS_(L"UnnamedWindowName")) : _WindowName;
    }

    bool WindowProperties::IsQuakeWindow() const noexcept
    {
        return _WindowName == L"_quake";
    }

};
