//
//  NitroChuckerCapture.m
//
//  Installs Wormholy's URLSession capture under STATIC frameworks.
//
//  Wormholy normally swizzles +[NSURLSessionConfiguration defaultSessionConfiguration]
//  from a C `__attribute__((constructor))` in NSURLSessionConfiguration+Wormholy.m. That
//  file declares NO Obj-C class/category, so `-ObjC` cannot anchor it and the linker
//  dead-strips the object file when Wormholy is built as a static framework — the swizzle
//  never runs and React Native's traffic is never captured.
//
//  We re-create that swizzle here inside a real Obj-C class's +load (which `-ObjC` DOES
//  load), calling Wormholy's public +[Wormholy setEnabled:sessionConfiguration:] to inject
//  its CustomHTTPProtocol into every session configuration the app creates. +load runs at
//  image load, before RN builds its NSURLSession, so RN's default-config traffic is caught.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@import Wormholy;

typedef NSURLSessionConfiguration *(*NCSessionConfigCtor)(id, SEL);

static NCSessionConfigCtor nc_orig_defaultSessionConfiguration;
static NCSessionConfigCtor nc_orig_ephemeralSessionConfiguration;

// Wormholy retains every transaction — including full request/response bodies —
// in an in-memory array that is UNBOUNDED by default (Storage.limit == nil), and
// it never truncates bodies. Long sessions therefore grow until the app is
// OOM-killed. Storage enforces `limit` as a FIFO ring buffer (insert at 0, then
// removeLast once per insert), but only for inserts that happen AFTER the limit
// is set — lowering it later cannot shrink an existing backlog. So the default
// has to be installed here, at image load, before the first request: our Swift
// side is not constructed until JS first touches the hybrid, which may be
// minutes into the session. JS can override this via setMaxLogCount().
static const NSInteger kNitroChuckerDefaultMaxLogCount = 200;

// Install unconditionally, exactly as Wormholy's own (dead-stripped) constructor
// does. Do NOT gate this on isWormholyEnabled(): URLProtocol.registerClass is
// ignored by NSURLSession, so a configuration that misses the protocol here can
// never be retrofitted, and setEnabled(true) could not undo the omission —
// capture for that session would be dead for the rest of the process. Pausing is
// already handled downstream: CustomHTTPProtocol.canInit and startLoading both
// return early while Wormholy.isEnabled is false, so a present-but-inert
// protocol class costs one canInit call and keeps pause/resume reversible.
static void nc_install(NSURLSessionConfiguration *config) {
  if (config != nil) {
    [Wormholy setEnabled:YES sessionConfiguration:config];
  }
}

static NSURLSessionConfiguration *nc_defaultSessionConfiguration(id self, SEL _cmd) {
  NSURLSessionConfiguration *config = nc_orig_defaultSessionConfiguration(self, _cmd);
  nc_install(config);
  return config;
}

static NSURLSessionConfiguration *nc_ephemeralSessionConfiguration(id self, SEL _cmd) {
  NSURLSessionConfiguration *config = nc_orig_ephemeralSessionConfiguration(self, _cmd);
  nc_install(config);
  return config;
}

@interface NitroChuckerCapture : NSObject
@end

@implementation NitroChuckerCapture

// Last-resort guard against an OOM kill. Wormholy exposes no way to free what it
// has already retained (Storage and clearRequests() are internal and not @objc),
// and the retention cap only trims on subsequent inserts — so the one lever left
// under memory pressure is to stop capturing more. Registered here rather than
// from the Swift side because the Swift object is not constructed until JS first
// touches the hybrid, which can be minutes into the session; the pressure that
// kills the app can arrive long before that. Reversible: capture resumes via
// setEnabled(true), because the protocol stays installed in every configuration.
static void nc_observeMemoryPressure(void) {
  [NSNotificationCenter.defaultCenter
      addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
                  object:nil
                   queue:NSOperationQueue.mainQueue
              usingBlock:^(NSNotification *_Nonnull __unused note) {
                if (![Wormholy isWormholyEnabled]) return;
                [Wormholy setEnabled:NO];
                NSLog(@"NitroChucker: memory warning — capture paused to avoid an OOM "
                      @"kill. Already-captured transactions cannot be freed (no "
                      @"Wormholy API); call setEnabled(true) to resume, or lower "
                      @"setMaxLogCount().");
              }];
}

+ (void)load {
  static dispatch_once_t once;
  dispatch_once(&once, ^{
    // Bound retention before any traffic can be captured. Wormholy's setter hops
    // to the main actor internally, so this lands before the first response body
    // is stored (URLSession cannot deliver before the runloop starts).
    [Wormholy setLimit:@(kNitroChuckerDefaultMaxLogCount)];
    nc_observeMemoryPressure();

    Method def = class_getClassMethod([NSURLSessionConfiguration class],
                                      @selector(defaultSessionConfiguration));
    if (def) {
      nc_orig_defaultSessionConfiguration = (NCSessionConfigCtor)method_getImplementation(def);
      method_setImplementation(def, (IMP)nc_defaultSessionConfiguration);
    }

    Method eph = class_getClassMethod([NSURLSessionConfiguration class],
                                      @selector(ephemeralSessionConfiguration));
    if (eph) {
      nc_orig_ephemeralSessionConfiguration = (NCSessionConfigCtor)method_getImplementation(eph);
      method_setImplementation(eph, (IMP)nc_ephemeralSessionConfiguration);
    }
  });
}

@end
