// Copyright 2023 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_media_capture_permission_request.h"

#import "base/task/bind_post_task.h"
#import "base/task/sequenced_task_runner.h"
#import "ios/web/public/permissions/permissions.h"
#import "ios/web/public/web_client.h"
#import "ios/web/web_state/web_state_impl.h"

namespace {

// Converts WKMediaCaptureType to an array of Permissions.
NSArray<NSNumber*>* GetPermissionsFromWKMediaCaptureType(
    WKMediaCaptureType media_capture_type) {
  switch (media_capture_type) {
    case WKMediaCaptureTypeCamera:
      return @[ @(web::PermissionCamera) ];
    case WKMediaCaptureTypeMicrophone:
      return @[ @(web::PermissionMicrophone) ];
    case WKMediaCaptureTypeCameraAndMicrophone:
      return @[ @(web::PermissionCamera), @(web::PermissionMicrophone) ];
  }
}

}  // namespace

@implementation CRWMediaCapturePermissionRequest {
  // Task runner the decision handler should run on.
  scoped_refptr<base::SequencedTaskRunner> _taskRunner;
  // Handler of user's media capture decision.
  void (^_decisionHandler)(WKPermissionDecision);
  // Track whether the decision handler has been called.
  BOOL _decisionHandlerInvoked;
}

- (instancetype)initWithDecisionHandler:
                    (void (^)(WKPermissionDecision decision))decisionHandler
                           onTaskRunner:
                               (const scoped_refptr<base::SequencedTaskRunner>&)
                                   taskRunner {
  if ((self = [super init])) {
    _taskRunner = taskRunner;
    _decisionHandler = decisionHandler;
    _decisionHandlerInvoked = NO;
  }
  return self;
}

- (void)dealloc {
  // Deny permission if decision handler has never been invoked.
  if (!_decisionHandlerInvoked) {
    [self handleDecision:WKPermissionDecisionDeny];
  }
}

- (void)displayPromptForMediaCaptureType:(WKMediaCaptureType)mediaCaptureType
                                  origin:(const GURL&)origin {
  // This block strongly captures `self` intentionally to ensure that
  // `_decisionHandler` is always invoked, even if the scope of `self` has
  // deallocated.
  __block GURL originCopy = origin;
  _taskRunner->PostTask(
      FROM_HERE, base::BindOnce(^{
        [self displayPromptForMediaCaptureTypeOnTaskRunner:mediaCaptureType
                                                    origin:originCopy];
      }));
}

#pragma mark - Private

// Helper method that only executes on `_taskRunner`'s sequence.
- (void)displayPromptForMediaCaptureTypeOnTaskRunner:
            (WKMediaCaptureType)mediaCaptureType
                                              origin:(const GURL&)origin {
  if (!_presenter) {
    [self handleDecision:WKPermissionDecisionDeny];
    return;
  }
  web::WebStateImpl* webState = _presenter.presentingWebState;
  if (webState && !webState->IsBeingDestroyed()) {
    web::GetWebClient()->WillDisplayMediaCapturePermissionPrompt(webState);

    // Calling WillDisplayMediaCapturePermissionPrompt(...) may cause the
    // WebState to be closed. Fetch the value from the presenter again (as it
    // uses a weak pointer internally) to avoid having a dangling pointer.
    webState = _presenter.presentingWebState;
  }
  // By this point, the WebState may have been destroyed. If this is the case,
  // then `webState->IsBeingDestroyed` will be YES.
  if (!webState || webState->IsBeingDestroyed()) {
    [self handleDecision:WKPermissionDecisionDeny];
    return;
  }

  // This block strongly captures `self` intentionally to ensure that
  // `_decisionHandler` is always invoked, even if the scope of `self` has
  // deallocated.
  webState->RequestPermissionsWithDecisionHandler(
      GetPermissionsFromWKMediaCaptureType(mediaCaptureType), origin,
      ^(WKPermissionDecision decision) {
        [self handleDecision:decision];
      });
}

// Handle user response to media capture request.
- (void)handleDecision:(WKPermissionDecision)decision {
  if (_decisionHandlerInvoked) {
    return;
  }
  _decisionHandlerInvoked = YES;
  WKPermissionDecision finalDecision =
      _presenter ? decision : WKPermissionDecisionDeny;
  auto decisionHandler = _decisionHandler;
  // Post the decision handler asynchronously to prevent synchronous re-entrancy
  // and stack overflow if WebKit immediately initiates another permission
  // request upon decision completion.
  _taskRunner->PostTask(FROM_HERE, base::BindOnce(
                                       ^(WKPermissionDecision webkit_decision) {
                                         decisionHandler(webkit_decision);
                                       },
                                       finalDecision));
}

@end
