// Copyright 2018 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/web/web_state/ui/crw_web_view_content_view.h"

#import "ios/web/common/crw_viewport_controller.h"
#import "ios/web/web_state/crw_web_view.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

@interface CRWWebViewContentView (Testing)
@property(nonatomic, assign) WebViewResizingType webViewResizingType;
- (void)setMinimumViewportInset:(UIEdgeInsets)minInset
           maximumViewportInset:(UIEdgeInsets)maxInset;
@end

namespace {

using CRWWebViewContentViewTest = PlatformTest;

// Tests the ContentInset method when shouldUseViewContentInset is set to YES.
TEST_F(CRWWebViewContentViewTest, ContentInsetWithInsetForPadding) {
  CRWWebView* webView = [[CRWWebView alloc] init];
  UIScrollView* scrollView = [[UIScrollView alloc] init];
  [webView addSubview:scrollView];
  CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
      initWithWebView:webView
           scrollView:scrollView
      fullscreenState:CrFullscreenState::kNotInFullScreen];
  contentView.shouldUseViewContentInset = YES;

  const UIEdgeInsets contentInset = UIEdgeInsetsMake(10, 10, 10, 10);
  scrollView.contentInset = contentInset;
  EXPECT_TRUE(
      UIEdgeInsetsEqualToEdgeInsets(contentInset, contentView.contentInset));

  scrollView.contentInset = UIEdgeInsetsZero;
  contentView.contentInset = contentInset;
  EXPECT_TRUE(
      UIEdgeInsetsEqualToEdgeInsets(contentInset, scrollView.contentInset));
}

// Tests that viewport insets are not applied to the web view if its frame is
// too small, and that they are applied once the frame is expanded.
TEST_F(CRWWebViewContentViewTest, ViewportInsetsDeferredWhenFrameTooSmall) {
  CRWWebView* webView = [[CRWWebView alloc] initWithFrame:CGRectZero];
  webView.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  UIScrollView* scrollView = [[UIScrollView alloc] init];
  [webView addSubview:scrollView];
  id mockWebView = OCMPartialMock(webView);

  CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
      initWithWebView:webView
           scrollView:scrollView
      fullscreenState:CrFullscreenState::kNotInFullScreen];

  contentView.webViewResizingType = WebViewResizingType::kContentInset;

  UIWindow* window = [[UIWindow alloc] init];
  [window addSubview:contentView];
  [contentView layoutIfNeeded];

  UIEdgeInsets minInset = UIEdgeInsetsMake(0, 1, 0, 0);
  UIEdgeInsets maxInset = UIEdgeInsetsMake(100, 1, 300, 0);

  // Frame is CGRectZero (width 0, height 0).
  // The inset requested is much larger. Verify it is safely caught and NOT
  // passed through to the WKWebView.
  __block int insetCallCount = 0;
  [[[mockWebView stub] andDo:^(NSInvocation* invocation) {
    insetCallCount++;
  }] setMinimumViewportInset:minInset maximumViewportInset:maxInset];

  [contentView setMinimumViewportInset:minInset maximumViewportInset:maxInset];
  EXPECT_EQ(0, insetCallCount);

  // Expand the frame to be large enough (e.g. 800x1000).
  // Expect the pending inset to finally be passed through during
  // layoutSubviews.
  contentView.bounds = CGRectMake(0, 0, 800, 1000);
  [contentView layoutSubviews];
  EXPECT_EQ(1, insetCallCount);
}

// Tests that viewport insets are safely deferred even when the 64-bit
// frame is microscopically larger than the inset.
TEST_F(CRWWebViewContentViewTest,
       ViewportInsetsDeferredWhenFloatSizeIsTruncated) {
  CRWWebView* webView =
      [[CRWWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 100.0000001)];
  webView.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  UIScrollView* scrollView = [[UIScrollView alloc] init];
  [webView addSubview:scrollView];
  id mockWebView = OCMPartialMock(webView);

  CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
      initWithWebView:webView
           scrollView:scrollView
      fullscreenState:CrFullscreenState::kNotInFullScreen];

  contentView.webViewResizingType = WebViewResizingType::kContentInset;

  UIWindow* window = [[UIWindow alloc] init];
  [window addSubview:contentView];
  [contentView layoutIfNeeded];

  UIEdgeInsets minInset = UIEdgeInsetsMake(0, 1, 0, 0);
  UIEdgeInsets maxInset = UIEdgeInsetsMake(100, 1, 0, 0);

  __block int insetCallCount = 0;
  [[[mockWebView stub] andDo:^(NSInvocation* invocation) {
    insetCallCount++;
  }] setMinimumViewportInset:minInset maximumViewportInset:maxInset];

  [contentView setMinimumViewportInset:minInset maximumViewportInset:maxInset];

  // The call should be safely deferred.
  EXPECT_EQ(0, insetCallCount);

  // Expand the frame to be safely large enough.
  contentView.bounds = CGRectMake(0, 0, 800, 1000);
  [contentView layoutSubviews];
  EXPECT_EQ(1, insetCallCount);
}

// Tests that viewport insets are safely deferred when maxInset exceeds the
// frame but minInset is valid.
TEST_F(CRWWebViewContentViewTest,
       ViewportInsetsDeferredWhenMaxInsetExceedsFrame) {
  CRWWebView* webView =
      [[CRWWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 500)];
  webView.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  UIScrollView* scrollView = [[UIScrollView alloc] init];
  [webView addSubview:scrollView];
  id mockWebView = OCMPartialMock(webView);

  CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
      initWithWebView:webView
           scrollView:scrollView
      fullscreenState:CrFullscreenState::kNotInFullScreen];

  contentView.webViewResizingType = WebViewResizingType::kContentInset;

  UIWindow* window = [[UIWindow alloc] init];
  [window addSubview:contentView];
  [contentView layoutIfNeeded];

  // minInset is safely smaller than the 500pt frame.
  UIEdgeInsets minInset = UIEdgeInsetsMake(100, 1, 0, 0);
  // maxInset (mimicking a massive keyboard) exceeds the 500pt frame.
  UIEdgeInsets maxInset = UIEdgeInsetsMake(600, 1, 0, 0);

  __block int insetCallCount = 0;
  [[[mockWebView stub] andDo:^(NSInvocation* invocation) {
    insetCallCount++;
  }] setMinimumViewportInset:minInset maximumViewportInset:maxInset];

  [contentView setMinimumViewportInset:minInset maximumViewportInset:maxInset];

  // The call should be safely deferred.
  EXPECT_EQ(0, insetCallCount);

  // Expand the frame to be safely large enough to fit the keyboard.
  contentView.bounds = CGRectMake(0, 0, 800, 1000);
  [contentView layoutSubviews];
  EXPECT_EQ(1, insetCallCount);
}

// Tests that setting obscuredInsets forwards directly when not animating.
TEST_F(CRWWebViewContentViewTest, ObscuredInsetsWithoutAnimation) {
  if (@available(iOS 26, *)) {
    CRWWebView* webView = [[CRWWebView alloc] initWithFrame:CGRectZero];
    UIScrollView* scrollView = [[UIScrollView alloc] init];
    [webView addSubview:scrollView];
    id mockWebView = OCMPartialMock(webView);

    CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
        initWithWebView:webView
             scrollView:scrollView
        fullscreenState:CrFullscreenState::kNotInFullScreen];
    contentView.webViewResizingType = WebViewResizingType::kContentInset;

    UIEdgeInsets insets = UIEdgeInsetsMake(10, 20, 30, 40);
    __block int callCount = 0;
    [[[mockWebView stub] andDo:^(NSInvocation* invocation) {
      callCount++;
    }] setObscuredContentInsets:insets];

    contentView.obscuredInsets = insets;
    EXPECT_EQ(1, callCount);
    EXPECT_TRUE(
        UIEdgeInsetsEqualToEdgeInsets(insets, contentView.obscuredInsets));
    EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets(insets, scrollView.contentInset));
  }
}

// Tests that scroll view contentInset accounts for safe area insets in
// kContentInset mode.
TEST_F(CRWWebViewContentViewTest, ContentInsetAdjustedForSafeArea) {
  if (!@available(iOS 26, *)) {
    GTEST_SKIP() << "kContentInset mode is only supported on iOS 26+";
  }
  CRWWebView* webView = [[CRWWebView alloc] init];
  UIScrollView* scrollView = [[UIScrollView alloc] init];
  [webView addSubview:scrollView];
  CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
      initWithWebView:webView
           scrollView:scrollView
      fullscreenState:CrFullscreenState::kNotInFullScreen];
  contentView.webViewResizingType = WebViewResizingType::kContentInset;

  id mockContentView = OCMPartialMock(contentView);
  const UIEdgeInsets safeArea = UIEdgeInsetsMake(0, 20, 0, 30);
  OCMStub([mockContentView safeAreaInsets]).andReturn(safeArea);

  const UIEdgeInsets obscuredInsets = UIEdgeInsetsMake(10, 5, 10, 5);
  [mockContentView setObscuredInsets:obscuredInsets];

  const UIEdgeInsets expectedContentInset = UIEdgeInsetsMake(10, 20, 10, 30);
  EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets(expectedContentInset,
                                            scrollView.contentInset));
}

// Tests that min/max viewport insets passed to WKWebView are adjusted for safe
// area insets.
TEST_F(CRWWebViewContentViewTest, ViewportInsetsAdjustedForSafeArea) {
  CRWWebView* webView =
      [[CRWWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 1000)];
  webView.autoresizingMask =
      UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  UIScrollView* scrollView = [[UIScrollView alloc] init];
  [webView addSubview:scrollView];
  id mockWebView = OCMPartialMock(webView);

  CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
      initWithWebView:webView
           scrollView:scrollView
      fullscreenState:CrFullscreenState::kNotInFullScreen];
  contentView.webViewResizingType = WebViewResizingType::kContentInset;

  UIWindow* window = [[UIWindow alloc] init];
  [window addSubview:contentView];
  contentView.frame = CGRectMake(0, 0, 800, 1000);
  [contentView layoutIfNeeded];

  id mockContentView = OCMPartialMock(contentView);
  const UIEdgeInsets safeArea = UIEdgeInsetsMake(0, 25, 0, 35);
  OCMStub([mockContentView safeAreaInsets]).andReturn(safeArea);

  const UIEdgeInsets minInset = UIEdgeInsetsMake(10, 5, 10, 5);
  const UIEdgeInsets maxInset = UIEdgeInsetsMake(50, 10, 50, 10);
  const UIEdgeInsets expectedMinInset = UIEdgeInsetsMake(10, 25, 10, 35);
  const UIEdgeInsets expectedMaxInset = UIEdgeInsetsMake(50, 25, 50, 35);

  OCMExpect([mockWebView setMinimumViewportInset:expectedMinInset
                            maximumViewportInset:expectedMaxInset]);
  [mockContentView setMinimumViewportInset:minInset
                      maximumViewportInset:maxInset];
  EXPECT_OCMOCK_VERIFY(mockWebView);
}

// Tests that the web view frame is adjusted for safe area insets in kFrame mode
// when obscured insets are non-zero.
TEST_F(CRWWebViewContentViewTest, FrameAdjustedForSafeAreaInFrameMode) {
  CRWWebView* webView = [[CRWWebView alloc] init];
  UIScrollView* scrollView = [[UIScrollView alloc] init];
  [webView addSubview:scrollView];
  CRWWebViewContentView* contentView = [[CRWWebViewContentView alloc]
      initWithWebView:webView
           scrollView:scrollView
      fullscreenState:CrFullscreenState::kNotInFullScreen];
  contentView.webViewResizingType = WebViewResizingType::kFrame;
  contentView.frame = CGRectMake(0, 0, 800, 600);

  id mockContentView = OCMPartialMock(contentView);
  const UIEdgeInsets safeArea = UIEdgeInsetsMake(0, 20, 0, 30);
  OCMStub([mockContentView safeAreaInsets]).andReturn(safeArea);

  const UIEdgeInsets obscuredInsets = UIEdgeInsetsMake(50, 5, 0, 5);
  [mockContentView setObscuredInsets:obscuredInsets];

  // Frame should be inset by top: 50, left: 20, bottom: 0, right: 30.
  const CGRect expectedFrame = CGRectMake(20, 50, 750, 550);
  EXPECT_TRUE(CGRectEqualToRect(expectedFrame, webView.frame));
}

}  // namespace
