#import "DynamicClientScreenshotProtection.h"
#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>
#import <DynamicClientSpec/DynamicClientSpec.h>

@interface DynamicClientScreenshotProtection () <NativeScreenshotProtectionSpec> {
  // Screenshot/recording protection state (see setEnabled:). All ivars are only
  // touched on the main thread. `_secureField` is long-lived: once created it is
  // NEVER torn down, because destroying a secure field whose layer has been
  // parked under the CA context root leads to an over-release of its backing
  // layer and a later EXC_BAD_ACCESS during UIKit hit-testing (the layer zombies
  // while UIKit still holds a hit-test route to it). Disable therefore only
  // restores the window layer and leaves the field in place; `_protecting` (not
  // the field's existence) is the "are we currently protecting?" guard.
  // `_originalWindowSuperlayer` is captured on each enable so disable can
  // restore the window layer to exactly where it was reparented from.
  // `_retiredSecureFields` pins fields abandoned on a window change for the
  // process lifetime, so a rogue release can never drop their layer to zero.
  UITextField *_secureField;
  UIWindow *_secureWindow;
  CALayer *_originalWindowSuperlayer;
  BOOL _protecting;
  NSMutableArray<UITextField *> *_retiredSecureFields;
}
@end

@implementation DynamicClientScreenshotProtection

RCT_EXPORT_MODULE()

- (void)setEnabled:(BOOL)enabled {
  // iOS has no public equivalent of Android's FLAG_SECURE, so we use the
  // well-established "secure text entry canvas" technique: a UITextField with
  // secureTextEntry == YES owns a special render layer that the system excludes
  // from screenshots and screen recordings. By reparenting the key window's
  // layer underneath that secure canvas, the entire window renders blank in any
  // capture while staying fully visible on the physical screen. This relies on
  // the secure canvas being the text field layer's first sublayer — undocumented
  // but stable across iOS releases and free of private API symbols. Best-effort:
  // if the window/layer can't be resolved we silently skip rather than block the
  // calling flow.
  //
  // UIKit layer surgery must run on the main thread.
  dispatch_async(dispatch_get_main_queue(), ^{
    if (enabled) {
      // Already protecting — setEnabled:YES is idempotent.
      if (self->_protecting) {
        return;
      }
      UIWindow *window = [self dynamicKeyWindow];
      CALayer *originalSuperlayer = window.layer.superlayer;
      if (!window || !originalSuperlayer) {
        return;
      }
      // The key window changed since the last cycle. The old field's layer is
      // parked under the old window's context root; touching it (remove/dealloc)
      // is exactly what triggers the over-release crash, so pin it for the
      // process lifetime and abandon it in place. One field per window change —
      // effectively a handful of objects over an app's life.
      if (self->_secureField && self->_secureWindow != window) {
        if (!self->_retiredSecureFields) {
          self->_retiredSecureFields = [NSMutableArray array];
        }
        [self->_retiredSecureFields addObject:self->_secureField];
        self->_secureField = nil;
        self->_secureWindow = nil;
      }
      if (!self->_secureField) {
        UITextField *field = [[UITextField alloc] initWithFrame:window.bounds];
        field.secureTextEntry = YES;
        // Purely a protection shim — must never intercept touches.
        field.userInteractionEnabled = NO;
        [window addSubview:field];
        // The secure-entry "canvas" (the layer iOS excludes from screenshots/
        // recordings) is a private subview that UIKit does NOT create until the
        // field has been laid out inside a window. Force a layout pass first,
        // otherwise the lookup below finds nothing and protection silently
        // no-ops.
        [field layoutIfNeeded];
        if (![self secureCanvasLayerForField:field]) {
          // UIKit internals changed and the secure canvas couldn't be found —
          // bail without reparenting. The field's layer is still consistently
          // inside the window's layer tree here, so removing the subview is the
          // one teardown that is safe.
          [field removeFromSuperview];
          return;
        }
        self->_secureField = field;
        self->_secureWindow = window;
      }
      CALayer *secureLayer =
          [self secureCanvasLayerForField:self->_secureField];
      if (!secureLayer) {
        return;
      }
      // Keep the reused field covering the window even if its bounds changed
      // (rotation, resize) between cycles.
      self->_secureField.frame = window.bounds;
      // Re-park the field's layer under the current context root on EVERY
      // enable: UIKit may rebuild the root's sublayer list between cycles
      // (scene updates, backgrounding), silently dropping our layer from the
      // render tree. addSublayer: is a move, so this is safe when it is already
      // there. Then nest the window's layer under the secure canvas so the
      // whole window inherits the "not captured" property.
      [originalSuperlayer addSublayer:self->_secureField.layer];
      [secureLayer addSublayer:window.layer];
      self->_originalWindowSuperlayer = originalSuperlayer;
      self->_protecting = YES;
    } else {
      // Not protecting (or never were) — nothing to undo.
      if (!self->_protecting) {
        return;
      }
      // Move the window layer back out of the secure canvas to its original
      // parent. Deliberately keep the field and its layer alive and in place:
      // tearing them down is what caused the over-release/zombie-layer crash,
      // and a stale hit-test over a live, invisible, non-interactive layer is
      // harmless. The field is reused on the next enable.
      [self->_originalWindowSuperlayer addSublayer:self->_secureWindow.layer];
      self->_originalWindowSuperlayer = nil;
      self->_protecting = NO;
    }
  });
}

// Finds the secure-entry "canvas" layer of a secureTextEntry field — the layer
// iOS excludes from screenshots/recordings. Its position in the field's
// view/layer tree varies across iOS versions, so probe defensively: prefer a
// subview whose class name marks it as the canvas/content host, then fall back
// to the first sublayer of the field's own layer. Returns nil if neither is
// present (UIKit internals changed), so the caller can skip cleanly.
- (CALayer *)secureCanvasLayerForField:(UITextField *)field {
  for (UIView *subview in field.subviews) {
    NSString *className = NSStringFromClass(subview.class);
    if ([className containsString:@"Canvas"] ||
        [className containsString:@"Content"]) {
      return subview.layer;
    }
  }
  return field.layer.sublayers.firstObject;
}

// Resolves the foreground-active key window across connected scenes, falling
// back to the first window of any window scene. Used by setEnabled: to apply
// screenshot protection at the window level.
- (UIWindow *)dynamicKeyWindow {
  UIWindow *fallback = nil;
  for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
    if (![scene isKindOfClass:[UIWindowScene class]]) {
      continue;
    }
    UIWindowScene *windowScene = (UIWindowScene *)scene;
    for (UIWindow *window in windowScene.windows) {
      if (window.isKeyWindow) {
        return window;
      }
      if (!fallback) {
        fallback = window;
      }
    }
  }
  return fallback;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params {
  return std::make_shared<facebook::react::NativeScreenshotProtectionSpecJSI>(
      params);
}

+ (BOOL)requiresMainQueueSetup {
  return NO;
}

@end
