// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <memory>

#import "base/check_op.h"
#import "base/debug/dump_without_crashing.h"
#import "base/message_loop/message_pump_apple.h"
#import "base/strings/sys_string_conversions.h"
#import "components/keyed_service/ios/browser_state_dependency_manager.h"
#import "google_apis/google_api_keys.h"
#import "ios/web/public/init/web_main.h"
#import "ios/web_view/internal/browser_state_keyed_service_factories.h"
#import "ios/web_view/internal/cwv_global_state_internal.h"
#import "ios/web_view/internal/web_view_web_client.h"
#import "ios/web_view/internal/web_view_web_main_delegate.h"
#if !defined(CWV_UNIT_TEST)
#import "testing/coverage_util_ios.h"
#endif  // defined(CWV_UNIT_TEST)

@implementation CWVEarlyInitFlags

- (instancetype)init {
  self = [super init];
  if (self) {
    _autofillAcrossIframesEnabled = YES;
    _autofillStrikeSystemEnabled = YES;
  }
  return self;
}

@end

@implementation CWVGlobalState {
  std::unique_ptr<ios_web_view::WebViewWebClient> _web_client;
  std::unique_ptr<ios_web_view::WebViewWebMainDelegate> _web_main_delegate;
  std::unique_ptr<web::WebMain> _web_main;
  BOOL _isEarlyInitialized;
  BOOL _isStarted;
  NSString* _customUserAgent;
  NSString* _userAgentProduct;
  NSInteger _mainThreadInitialNestingLevel;
}

+ (instancetype)sharedInstance {
  static CWVGlobalState* globalState = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    globalState = [[CWVGlobalState alloc] init];
  });
  return globalState;
}

- (NSString*)customUserAgent {
  return _customUserAgent;
}

- (void)setCustomUserAgent:(NSString*)customUserAgent {
  _customUserAgent = [customUserAgent copy];
}

- (NSString*)userAgentProduct {
  return _userAgentProduct;
}

- (void)setUserAgentProduct:(NSString*)userAgentProduct {
  _userAgentProduct = [userAgentProduct copy];
}

- (void)setGoogleAPIKey:(NSString*)googleAPIKey
               clientID:(NSString*)clientID
           clientSecret:(NSString*)clientSecret {
  google_apis::InitializeAndOverrideAPIKeyAndOAuthClient(
      base::SysNSStringToUTF8(googleAPIKey), base::SysNSStringToUTF8(clientID),
      base::SysNSStringToUTF8(clientSecret));
}

- (void)setDumpWithoutCrashingHandler:(void (*)(void))handler {
  base::debug::SetDumpWithoutCrashingFunction(handler);
}

- (BOOL)isStarted {
#if defined(CWV_UNIT_TEST)
  // Global state initialization is not needed in a unit test environment.
  return YES;
#else
  return _isStarted;
#endif  // defined(CWV_UNIT_TEST)
}

- (BOOL)isEarlyInitialized {
#if defined(CWV_UNIT_TEST)
  // Global state initialization is not needed in a unit test environment.
  return YES;
#else
  return _isEarlyInitialized;
#endif  // defined(CWV_UNIT_TEST)
}

- (void)earlyInitWithFlags:(CWVEarlyInitFlags*)flags {
#if defined(CWV_UNIT_TEST)
  // Global state initialization is not needed in a unit test environment.
#else
  // Set flags before doing anything else.
  CHECK(flags);
  _autofillAcrossIframesEnabled = flags.autofillAcrossIframesEnabled;
  _delayLoadingResources = flags.delayLoadingResources;
  _mainThreadInitialNestingLevel = flags.mainThreadInitialNestingLevel;
  _autofillStrikeSystemEnabled = flags.autofillStrikeSystemEnabled;

  DCHECK([NSThread isMainThread]);

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    DCHECK(!_isEarlyInitialized);

    // This is for generating coverage data for tests only.
    coverage_util::ConfigureCoverageReportPath();

    _web_client = std::make_unique<ios_web_view::WebViewWebClient>();
    web::SetWebClient(_web_client.get());
    _web_main_delegate =
        std::make_unique<ios_web_view::WebViewWebMainDelegate>();

    if (_mainThreadInitialNestingLevel > 1) {
      base::MessagePumpUIApplication::SetNextInitialNestingLevelForCurrentThread(
          _mainThreadInitialNestingLevel);
    }

    web::WebMainParams params(_web_main_delegate.get());
    _web_main = std::make_unique<web::WebMain>(std::move(params));

    _isEarlyInitialized = YES;
  });
#endif  // defined(CWV_UNIT_TEST)
}

- (void)earlyInit {
  [self earlyInitWithFlags:[[CWVEarlyInitFlags alloc] init]];
}

- (void)start {
#if defined(CWV_UNIT_TEST)
  // Global state initialization is not needed in a unit test environment.
#else
  DCHECK([NSThread isMainThread]);

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    DCHECK(_isEarlyInitialized);
    DCHECK(!_isStarted);

    _web_main->Startup();

    _isStarted = YES;
  });
#endif  // defined(CWV_UNIT_TEST)
}

- (void)stop {
#if defined(CWV_UNIT_TEST)
  // Global state initialization is not needed in a unit test environment.
#else
  DCHECK([NSThread isMainThread]);

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    // In reverse order to creation.
    _web_main.reset();
    _web_main_delegate.reset();
    _web_client.reset();
  });
#endif  // defined(CWV_UNIT_TEST)
}

@end
