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

#import "ios/chrome/browser/intelligence/bwg/ui/gemini_first_run_wrapper_view_controller.h"

#import <algorithm>

#import "base/check.h"
#import "ios/chrome/browser/intelligence/bwg/metrics/gemini_metrics.h"
#import "ios/chrome/browser/intelligence/bwg/ui/gemini_consent_configuration.h"
#import "ios/chrome/browser/intelligence/bwg/ui/gemini_consent_view_controller.h"
#import "ios/chrome/browser/intelligence/bwg/ui/gemini_consent_view_controller_delegate.h"
#import "ios/chrome/browser/intelligence/bwg/ui/gemini_first_run_mutator.h"
#import "ios/chrome/browser/intelligence/bwg/ui/gemini_promo_view_controller.h"
#import "ios/chrome/browser/intelligence/bwg/utils/gemini_constants.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui/button_stack/button_stack_action_delegate.h"
#import "ios/chrome/common/ui/button_stack/button_stack_configuration.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/lottie/lottie_animation_api.h"
#import "ios/public/provider/chrome/browser/lottie/lottie_animation_configuration.h"
#import "ui/base/l10n/l10n_util.h"

namespace {
// Sheet Presentation corner radius.
const CGFloat kPreferredCornerRadius = 16.0;

// Logos size, spacing.
const CGFloat kLogoPointSize = 70.0;
const CGFloat kLottieAnimationContainerWidth = 150.0;
const CGFloat kLogoTopGap = 32.0;
const CGFloat kExtraSpacingTitleContent = 8.0;

// Slide in configuration
const CGFloat kSlideDuration = 1.0;
const CGFloat kSpringDamping = 0.85;

// Multipliers for the detent height.
const CGFloat kMaxDetentRatio = 1.0;
const CGFloat kMinDetentRatio = 0.25;

// Adjustment taking into account the inset between the content and buttons.
const CGFloat kInsetAdjustment = 20;

}  // namespace

@interface GeminiFirstRunWrapperViewController () <
    ButtonStackActionDelegate,
    GeminiConsentViewControllerDelegate>

// Scroll view that contains the horizontal stack view for transitions.
@property(nonatomic, strong) UIScrollView* horizontalScrollView;

// Returns the button stack configuration for the promo screen.
+ (ButtonStackConfiguration*)buttonsConfigurationForPromo;

@end

@implementation GeminiFirstRunWrapperViewController {
  // The Gemini Promo View Controller.
  GeminiPromoViewController* _promoViewController;
  // The Gemini Consent View Controller.
  GeminiConsentViewController* _consentViewController;
  // If YES, the promo view is shown initially. Otherwise, we skip it.
  BOOL _showPromo;
  // Type of Gemini First Run.
  GeminiFirstRunType _firstRunType;
  // Configuration for the Gemini Consent view.
  GeminiConsentConfiguration* _consentConfiguration;
  // The main stack view containing the logos.
  UIStackView* _mainStackView;
  // Horizontal stack view holding the promo and consent views.
  UIStackView* _horizontalStackView;
  // Currently active child view controller.
  __weak UIViewController<GeminiFirstRunViewControllerProtocol>*
      _currentChildViewController;
  // Stack View containing the logos.
  UIStackView* _logosStackView;
  // The Lottie animation for the logo.
  id<LottieAnimation> _logoAnimation;
  // Content height constraint for the current view.
  NSLayoutConstraint* _contentHeightConstraint;
  // Whether an accordion item has been expanded at least once.
  BOOL _hasExpandedAccordion;
}

- (instancetype)initWithPromo:(BOOL)showPromo
                 firstRunType:(GeminiFirstRunType)firstRunType
         consentConfiguration:
             (GeminiConsentConfiguration*)consentConfiguration {
  ButtonStackConfiguration* configuration =
      showPromo
          ? [GeminiFirstRunWrapperViewController buttonsConfigurationForPromo]
          : [GeminiConsentViewController
                buttonStackConfigurationForConfiguration:consentConfiguration];

  self = [super initWithConfiguration:configuration];
  if (self) {
    _showPromo = showPromo;
    _firstRunType = firstRunType;
    _consentConfiguration = consentConfiguration;
  }
  return self;
}

#pragma mark - UIViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  self.view.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
  self.actionDelegate = self;

  [self setupChildViewControllers];
  [self setupSubviews];
  [self configureSheetPresentation];
  [self updateButtonConfiguration];
  [self updateAccessibilityVisibility];
}

- (void)viewWillTransitionToSize:(CGSize)size
       withTransitionCoordinator:
           (id<UIViewControllerTransitionCoordinator>)coordinator {
  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  __weak __typeof(self) weakSelf = self;
  [coordinator
      animateAlongsideTransition:^(
          id<UIViewControllerTransitionCoordinatorContext> context) {
        [weakSelf updateContentHeightConstraint];
      }
                      completion:nil];
}

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  [self updateContentHeightConstraint];
  [self.view layoutIfNeeded];
  [self.sheetPresentationController invalidateDetents];
}

// Re-calculates and updates the sheet's content height constraint after the
// actual screen layout (width) is resolved, ensuring text wrapping is accounted
// for.
- (void)viewDidLayoutSubviews {
  [super viewDidLayoutSubviews];
  [self updateContentHeightConstraint];
}

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  if (_currentChildViewController == _promoViewController) {
    [self.mutator didShowGeminiPromo];
  }
  // The related WebState can be hidden asynchronously while this animated view
  // is being shown. `GeminiTabHelper::WasHidden()` causes the related
  // coordinator to shut down, causing the `mutator` to be nil, and leaves the
  // view in a broken state once shown. This check ensures that if the view is
  // in a broken state, automatically dismiss it.
  if (!self.mutator) {
    [self dismissViewControllerAnimated:YES completion:nil];
  }
}

#pragma mark - Private

// Updates the content height constraint and invalidates the sheet presentation
// detents if the height has changed.
- (void)updateContentHeightConstraint {
  CGFloat newHeight = [_currentChildViewController contentHeight];
  if (newHeight != _contentHeightConstraint.constant) {
    _contentHeightConstraint.constant = newHeight;
    [self.sheetPresentationController invalidateDetents];
  }
}

// Creates and returns the stack view containing the animated logos.
- (UIStackView*)createLogosStackView {
  UIStackView* logosStackView = [[UIStackView alloc] init];
  logosStackView.alignment = UIStackViewAlignmentCenter;
  logosStackView.translatesAutoresizingMaskIntoConstraints = NO;
  logosStackView.layoutMarginsRelativeArrangement = YES;

  UIView* logoBrandContainer =
      [self animatedLogoContainerWithLottie:kLottieAnimationFirstRunBannerName];
  [logosStackView addArrangedSubview:logoBrandContainer];

  [NSLayoutConstraint
      activateConstraints:@[ [logosStackView.heightAnchor
                              constraintEqualToConstant:kLogoPointSize] ]];

  return logosStackView;
}

// Creates a container view for a Lottie animation with the given JSON name.
- (UIView*)animatedLogoContainerWithLottie:(NSString*)JSONName {
  UIView* container = [[UIView alloc] init];
  container.translatesAutoresizingMaskIntoConstraints = NO;
  [NSLayoutConstraint activateConstraints:@[
    [container.widthAnchor
        constraintEqualToConstant:kLottieAnimationContainerWidth],
    [container.heightAnchor constraintEqualToConstant:kLogoPointSize],
  ]];

  LottieAnimationConfiguration* configuration =
      [[LottieAnimationConfiguration alloc] init];
  configuration.animationName = JSONName;

  id<LottieAnimation> wrapper =
      ios::provider::GenerateLottieAnimation(configuration);
  wrapper.animationView.translatesAutoresizingMaskIntoConstraints = NO;
  wrapper.animationView.contentMode = UIViewContentModeScaleAspectFit;
  [wrapper play];
  [container addSubview:wrapper.animationView];
  AddSameConstraints(wrapper.animationView, container);

  UIImageView* logoView = [[UIImageView alloc] init];
  logoView.contentMode = UIViewContentModeScaleAspectFit;
  logoView.translatesAutoresizingMaskIntoConstraints = NO;
  [container addSubview:logoView];

  _logoAnimation = wrapper;

  return container;
}

// Constructs the main view hierarchy. The different steps are contained in a
// horizontal stack and transitioned using a horizontal scrollview.
- (void)setupSubviews {
  UIStackView* wrapperStackView = [[UIStackView alloc] init];
  wrapperStackView.axis = UILayoutConstraintAxisVertical;
  wrapperStackView.layoutMarginsRelativeArrangement = YES;
  wrapperStackView.translatesAutoresizingMaskIntoConstraints = NO;
  wrapperStackView.directionalLayoutMargins =
      NSDirectionalEdgeInsetsMake(kLogoTopGap, 0, 0, 0);
  wrapperStackView.accessibilityIdentifier =
      kGeminiFirstRunWrapperStackAccessibilityIdentifier;
  [self.contentView addSubview:wrapperStackView];
  [self.contentView setContentHuggingPriority:UILayoutPriorityRequired
                                      forAxis:UILayoutConstraintAxisVertical];
  AddSameConstraints(wrapperStackView, self.contentView);

  if (_firstRunType != GeminiFirstRunType::kLive) {
    _logosStackView = [self createLogosStackView];
    [wrapperStackView addArrangedSubview:_logosStackView];
    [wrapperStackView setCustomSpacing:kExtraSpacingTitleContent
                             afterView:_logosStackView];
  }

  self.horizontalScrollView = [[UIScrollView alloc] init];
  self.horizontalScrollView.translatesAutoresizingMaskIntoConstraints = NO;
  self.horizontalScrollView.showsHorizontalScrollIndicator = NO;
  self.horizontalScrollView.scrollEnabled = NO;
  [wrapperStackView addArrangedSubview:self.horizontalScrollView];

  _horizontalStackView = [[UIStackView alloc] init];
  _horizontalStackView.translatesAutoresizingMaskIntoConstraints = NO;
  _horizontalStackView.axis = UILayoutConstraintAxisHorizontal;
  _horizontalStackView.distribution = UIStackViewDistributionFill;
  _horizontalStackView.alignment = UIStackViewAlignmentTop;
  [self.horizontalScrollView addSubview:_horizontalStackView];

  if (_promoViewController) {
    [self addChildViewController:_promoViewController];
    [_horizontalStackView addArrangedSubview:_promoViewController.view];
    [_promoViewController didMoveToParentViewController:self];
    // Force the promo view to be exactly one page wide
    [_promoViewController.view.widthAnchor
        constraintEqualToAnchor:self.horizontalScrollView.frameLayoutGuide
                                    .widthAnchor]
        .active = YES;
    // Hide the other view to prevent invalid height computation.
    _consentViewController.view.hidden = YES;
  }

  [self addChildViewController:_consentViewController];
  [_horizontalStackView addArrangedSubview:_consentViewController.view];
  [_consentViewController didMoveToParentViewController:self];
  // Force the consent view to be exactly one page wide
  [_consentViewController.view.widthAnchor
      constraintEqualToAnchor:self.horizontalScrollView.frameLayoutGuide
                                  .widthAnchor]
      .active = YES;

  _contentHeightConstraint = [self.horizontalScrollView.heightAnchor
      constraintEqualToConstant:[_currentChildViewController contentHeight]];
  _contentHeightConstraint.active = YES;
  AddSameConstraints(_horizontalStackView,
                     self.horizontalScrollView.contentLayoutGuide);
}

// Instantiates and configures the child view controllers.
- (void)setupChildViewControllers {
  if (_showPromo) {
    _promoViewController = [[GeminiPromoViewController alloc] init];
    _promoViewController.mutator = self.mutator;
  }

  _consentViewController = [[GeminiConsentViewController alloc]
      initWithConfiguration:_consentConfiguration];
  _consentViewController.mutator = self.mutator;
  _consentViewController.delegate = self;

  _currentChildViewController =
      _showPromo ? _promoViewController : _consentViewController;
}

// Configures modal presentation settings. We use a custom detent with a height
// based on the visible content while taking into account the scrollview inset.
- (void)configureSheetPresentation {
  __weak __typeof(self) weakSelf = self;
  auto resolver = ^CGFloat(
      id<UISheetPresentationControllerDetentResolutionContext> context) {
    __typeof(self) strongSelf = weakSelf;
    if (!strongSelf) {
      return 0;
    }

    CGFloat maxDetentValue = kMaxDetentRatio * context.maximumDetentValue;

    if (strongSelf->_hasExpandedAccordion) {
      return maxDetentValue;
    }

    CGFloat height = [strongSelf preferredHeightForContent];
    // Apply adjustment when needed. super.addsContentViewBottomInset adds
    // insets which lead to an overly generous spacing between the content and
    // the buttons stack for non-scrollable iPhone layouts.
    if (context.containerTraitCollection.userInterfaceIdiom !=
        UIUserInterfaceIdiomPad) {
      height -= kInsetAdjustment;
    }
    CGFloat minDetentValue = kMinDetentRatio * context.maximumDetentValue;
    return std::clamp(height, minDetentValue, maxDetentValue);
  };

  UISheetPresentationControllerDetent* detent =
      [UISheetPresentationControllerDetent
          customDetentWithIdentifier:kGeminiPromoConsentFullDetentIdentifier
                            resolver:resolver];
  self.sheetPresentationController.detents = @[ detent ];
  self.modalInPresentation = YES;
  self.modalPresentationStyle = UIModalPresentationPageSheet;
  self.sheetPresentationController.selectedDetentIdentifier = detent.identifier;
  [self configureCornerRadius];
}

// Configures the correct preferred corner radius given the form factor.
- (void)configureCornerRadius {
  CGFloat preferredCornerRadius =
      IsSplitToolbarMode(self.presentingViewController)
          ? kPreferredCornerRadius
          : UISheetPresentationControllerAutomaticDimension;
  self.navigationController.sheetPresentationController.preferredCornerRadius =
      preferredCornerRadius;
}

// Updates VoiceOver focus to the consent view after promo transition.
- (void)updateAccessibilityFocus {
  CHECK(_consentViewController);

  UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
                                  _consentViewController.view);
}

// Manages which view is visible to VoiceOver.
- (void)updateAccessibilityVisibility {
  if (_promoViewController) {
    _promoViewController.view.accessibilityElementsHidden =
        (_currentChildViewController != _promoViewController);
  }

  _consentViewController.view.accessibilityElementsHidden =
      (_currentChildViewController != _consentViewController);
}

#pragma mark - ButtonStackActionDelegate

// Handles the primary action from the promo screen. It transitions the view
// to the next step and animates the scroll view horizontally.
- (void)didAcceptPromo {
  _currentChildViewController = _consentViewController;
  _consentViewController.view.hidden = NO;
  [self updateAccessibilityVisibility];
  [self updateButtonConfiguration];

  // Fixed offset on both LTR and RTL languages after setting `hidden = NO`.
  [self.view layoutIfNeeded];
  CGFloat start = _promoViewController.view.frame.origin.x;
  self.horizontalScrollView.contentOffset = CGPointMake(start, 0);

  __weak __typeof(self) weakSelf = self;
  [self.sheetPresentationController animateChanges:^{
    [weakSelf updateContentHeightConstraint];
  }];

  GeminiPromoViewController* promoViewController = _promoViewController;
  CGFloat target = _consentViewController.view.frame.origin.x;
  [UIView animateWithDuration:kSlideDuration
      delay:0.0
      usingSpringWithDamping:kSpringDamping
      initialSpringVelocity:0.0
      options:UIViewAnimationOptionCurveEaseInOut
      animations:^{
        weakSelf.horizontalScrollView.contentOffset = CGPointMake(target, 0);
      }
      completion:^(BOOL finished) {
        promoViewController.view.hidden = YES;
        if (finished && UIAccessibilityIsVoiceOverRunning()) {
          [weakSelf updateAccessibilityFocus];
        }
      }];
}

- (void)didTapPrimaryActionButton {
  if (_currentChildViewController == _promoViewController) {
    RecordFirstRunPromoAction(IOSGeminiFirstRunAction::kAccept);
    [self didAcceptPromo];
  } else if (_currentChildViewController == _consentViewController) {
    RecordFirstRunConsentAction(IOSGeminiFirstRunAction::kAccept);
    if (_firstRunType == GeminiFirstRunType::kLive) {
      [self.mutator didConsentToLiveGemini];
    } else {
      [self.mutator didConsentGemini];
    }
  }
}

- (void)didTapSecondaryActionButton {
  if (_currentChildViewController == _promoViewController) {
    RecordFirstRunPromoAction(IOSGeminiFirstRunAction::kDismiss);
    [self.mutator didCloseGeminiPromo];
  } else if (_currentChildViewController == _consentViewController) {
    RecordFirstRunConsentAction(IOSGeminiFirstRunAction::kDismiss);
    if (_firstRunType == GeminiFirstRunType::kLive) {
      [self.mutator didRefuseLiveOnboarding];
    } else {
      [self.mutator didRefuseGeminiConsent];
    }
  }
}

- (void)didTapTertiaryActionButton {
  // Not used.
}

- (void)didDismissButtonStackViewController {
  // Not used.
}

// Generates the configuration required by `ButtonStackViewController` for the
// promo screen's primary & secondary actions.
+ (ButtonStackConfiguration*)buttonsConfigurationForPromo {
  ButtonStackConfiguration* configuration =
      [[ButtonStackConfiguration alloc] init];
  configuration.primaryActionString =
      l10n_util::GetNSString(IDS_IOS_BWG_PROMO_PRIMARY_BUTTON);
  configuration.secondaryActionString =
      l10n_util::GetNSString(IDS_IOS_BWG_PROMO_SECONDARY_BUTTON);
  return configuration;
}

- (void)updateButtonConfiguration {
  BOOL onPromo = _currentChildViewController == _promoViewController;
  ButtonStackConfiguration* configuration =
      onPromo
          ? [GeminiFirstRunWrapperViewController buttonsConfigurationForPromo]
          : [_consentViewController buttonStackConfiguration];
  [self updateConfiguration:configuration];
}

#pragma mark - GeminiConsentViewControllerDelegate

- (void)consentViewControllerDidExpandAccordionItem:
    (GeminiConsentViewController*)viewController {
  _hasExpandedAccordion = YES;
  __weak __typeof(self) weakSelf = self;
  [self.sheetPresentationController animateChanges:^{
    [weakSelf updateContentHeightConstraint];
  }];
}

@end
