// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "pch.h"
#include "ReactNativeHost.h"
#include "ReactNativeHost.g.cpp"

#include "ReactPackageBuilder.h"
#include "RedBox.h"
#include "TurboModulesProvider.h"

#include <future/futureWinRT.h>
#include <winrt/Windows.Foundation.Collections.h>
#include "IReactContext.h"
#include "ReactInstanceSettings.h"
#ifdef RNW_XAML_ISLAND
#include "XamlApplication.h"
#endif // RNW_XAML_ISLAND

#include <Fabric/Composition/Modal/WindowsModalHostViewComponentView.h>
#include <Fabric/WindowsComponentDescriptorRegistry.h>
#include <ReactPackageBuilder.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;

namespace winrt::Microsoft::ReactNative::implementation {

ReactNativeHost::ReactNativeHost() noexcept : m_reactHost{Mso::React::MakeReactHost()} {
#if _DEBUG
  facebook::react::InitializeLogging([](facebook::react::RCTLogLevel /*logLevel*/, const char *message) {
    std::string str = std::string("ReactNative:") + message;
    OutputDebugStringA(str.c_str());
  });
#endif
}

/*static*/ ReactNative::ReactNativeHost ReactNativeHost::FromContext(
    ReactNative::IReactContext const &reactContext) noexcept {
  return GetReactNativeHost(ReactPropertyBag{reactContext.Properties()});
}

IVector<IReactPackageProvider> ReactNativeHost::PackageProviders() noexcept {
  return InstanceSettings().PackageProviders();
}

ReactNative::ReactInstanceSettings ReactNativeHost::InstanceSettings() noexcept {
  if (!m_instanceSettings) {
    m_instanceSettings = make<ReactInstanceSettings>();
  }

  return m_instanceSettings;
}

void ReactNativeHost::InstanceSettings(ReactNative::ReactInstanceSettings const &value) noexcept {
  m_instanceSettings = value;
}

IAsyncAction ReactNativeHost::LoadInstance() noexcept {
  return ReloadInstance();
}

winrt::Microsoft::ReactNative::ReactPropertyId<
    winrt::Microsoft::ReactNative::ReactNonAbiValue<winrt::weak_ref<ReactNativeHost>>>
ReactNativeHostProperty() noexcept {
  static winrt::Microsoft::ReactNative::ReactPropertyId<
      winrt::Microsoft::ReactNative::ReactNonAbiValue<winrt::weak_ref<ReactNativeHost>>>
      propId{L"ReactNative.ReactNativeHost", L"ReactNativeHost"};
  return propId;
}

/*static*/ winrt::Microsoft::ReactNative::ReactNativeHost ReactNativeHost::GetReactNativeHost(
    ReactPropertyBag const &properties) noexcept {
  if (auto wkHost = properties.Get(ReactNativeHostProperty()).Value()) {
    if (auto abiHost = wkHost.get()) {
      return abiHost.as<winrt::Microsoft::ReactNative::ReactNativeHost>();
    }
  }
  return nullptr;
}

IAsyncAction ReactNativeHost::ReloadInstance() noexcept {
  auto turboModulesProvider = std::make_shared<TurboModulesProvider>();

  auto uriImageManager =
      std::make_shared<winrt::Microsoft::ReactNative::Composition::implementation::UriImageManager>();
  auto componentregistry = std::make_shared<::Microsoft::ReactNative::WindowsComponentDescriptorRegistry>();
  auto componentDescriptorRegistry = std::make_shared<facebook::react::ComponentDescriptorProviderRegistry>();

  ::Microsoft::ReactNative::WindowsComponentDescriptorRegistry::AddToProperties(
      ReactPropertyBag(m_instanceSettings.Properties()), componentregistry);

  m_packageBuilder = make<ReactPackageBuilder>(turboModulesProvider, componentregistry, uriImageManager);

  winrt::Microsoft::ReactNative::Composition::implementation::RegisterWindowsModalHostNativeComponent(m_packageBuilder);

  if (auto packageProviders = InstanceSettings().PackageProviders()) {
    for (auto const &packageProvider : packageProviders) {
      packageProvider.CreatePackage(m_packageBuilder);
    }
  }

#ifdef RNW_XAML_ISLAND
  if (componentregistry->isXamlSupportRequired()) {
    winrt::Microsoft::ReactNative::Xaml::implementation::XamlApplication::EnsureCreated();
  }
#endif // RNW_XAML_ISLAND

  ReactPropertyBag(m_instanceSettings.Properties()).Set(ReactNativeHostProperty(), get_weak());

  Mso::React::ReactOptions reactOptions{};
  reactOptions.Properties = m_instanceSettings.Properties();
  reactOptions.Notifications = m_instanceSettings.Notifications();
  reactOptions.SetUseDeveloperSupport(m_instanceSettings.UseDeveloperSupport());
  reactOptions.DeveloperSettings.SourceBundleName = to_string(m_instanceSettings.DebugBundlePath());
  reactOptions.SetUseDirectDebugger(m_instanceSettings.UseDirectDebugger());
  reactOptions.SetDebuggerBreakOnNextLine(m_instanceSettings.DebuggerBreakOnNextLine());
  reactOptions.SetUseFastRefresh(m_instanceSettings.UseFastRefresh());
  reactOptions.SetUseLiveReload(m_instanceSettings.UseLiveReload());
  reactOptions.EnableJITCompilation = m_instanceSettings.EnableJITCompilation();
  reactOptions.BundleRootPath = to_string(m_instanceSettings.BundleRootPath());
  reactOptions.DeveloperSettings.DebuggerPort = m_instanceSettings.DebuggerPort();
  reactOptions.DeveloperSettings.DebuggerRuntimeName = to_string(m_instanceSettings.DebuggerRuntimeName());
  if (m_instanceSettings.RedBoxHandler()) {
    reactOptions.RedBoxHandler = Mso::React::CreateRedBoxHandler(m_instanceSettings.RedBoxHandler());
  }
  if (m_instanceSettings.NativeLogger()) {
    reactOptions.OnLogging = [handler = m_instanceSettings.NativeLogger()](
                                 Mso::React::LogLevel logLevel, const char *message) {
      static_assert(LogLevel::Error == static_cast<LogLevel>(Mso::React::LogLevel::Error));
      static_assert(LogLevel::Fatal == static_cast<LogLevel>(Mso::React::LogLevel::Fatal));
      static_assert(LogLevel::Info == static_cast<LogLevel>(Mso::React::LogLevel::Info));
      static_assert(LogLevel::Trace == static_cast<LogLevel>(Mso::React::LogLevel::Trace));
      static_assert(LogLevel::Warning == static_cast<LogLevel>(Mso::React::LogLevel::Warning));
      handler(static_cast<LogLevel>(logLevel), winrt::to_hstring(message));
    };
  }
  reactOptions.DeveloperSettings.SourceBundleHost = to_string(m_instanceSettings.SourceBundleHost());
  reactOptions.DeveloperSettings.SourceBundlePort = m_instanceSettings.SourceBundlePort();
  reactOptions.DeveloperSettings.RequestInlineSourceMap = m_instanceSettings.RequestInlineSourceMap();
  reactOptions.DeveloperSettings.BundleAppId = to_string(m_instanceSettings.BundleAppId());
  reactOptions.DeveloperSettings.DevBundle = m_instanceSettings.RequestDevBundle();

  reactOptions.ByteCodeFileUri = to_string(m_instanceSettings.ByteCodeFileUri());
  reactOptions.EnableByteCodeCaching = m_instanceSettings.EnableByteCodeCaching();
  reactOptions.SetEnableDefaultCrashHandler(m_instanceSettings.EnableDefaultCrashHandler());
  reactOptions.SetJsiEngine(static_cast<Mso::React::JSIEngine>(m_instanceSettings.JSIEngineOverride()));

  reactOptions.TurboModuleProvider = turboModulesProvider;

  reactOptions.UriImageManager = uriImageManager;

  reactOptions.OnInstanceCreated = [](Mso::CntPtr<Mso::React::IReactContext> &&context) {
    auto notifications = context->Notifications();
    ReactInstanceSettings::RaiseInstanceCreated(
        notifications, winrt::make<InstanceCreatedEventArgs>(std::move(context)));
  };
  reactOptions.OnInstanceLoaded = [](Mso::CntPtr<Mso::React::IReactContext> &&context, const Mso::ErrorCode &err) {
    auto notifications = context->Notifications();
    ReactInstanceSettings::RaiseInstanceLoaded(
        notifications, winrt::make<InstanceLoadedEventArgs>(std::move(context), !!err));
  };
  reactOptions.OnInstanceDestroyed = [](Mso::CntPtr<Mso::React::IReactContext> &&context) {
    auto notifications = context->Notifications();
    ReactInstanceSettings::RaiseInstanceDestroyed(
        notifications, winrt::make<InstanceDestroyedEventArgs>(std::move(context)));
  };

  std::string jsBundleFile = to_string(m_instanceSettings.JavaScriptBundleFile());
  if (jsBundleFile.empty()) {
    jsBundleFile = "index.windows";
  }

  reactOptions.Identity = jsBundleFile;
  return make<Mso::AsyncActionFutureAdapter>(m_reactHost->ReloadInstanceWithOptions(std::move(reactOptions)));
}

IAsyncAction ReactNativeHost::UnloadInstance() noexcept {
  return make<Mso::AsyncActionFutureAdapter>(m_reactHost->UnloadInstance());
}

Mso::React::IReactHost *ReactNativeHost::ReactHost() noexcept {
  return m_reactHost.Get();
}

} // namespace winrt::Microsoft::ReactNative::implementation
