// ChottuLinkUnity6SceneHook.mm
//
// Intercepts UIWindowSceneDelegate deep link methods on Unity 6+ (6000.x).
//
// KEY INSIGHT from debugging:
//   UISceneWillConnectNotification fires AFTER scene:willConnectToSession:options:
//   is called on the delegate — despite Apple docs saying "shortly before".
//   For kill-state deep links the URL arrives in willConnectToSession, so we
//   MUST swizzle before that method is called, i.e. at constructor time.
//
//   All Obj-C classes in the same binary (UnityFramework) are registered by the
//   Obj-C runtime before any __attribute__((constructor)) functions run, so
//   NSClassFromString works reliably here.
//
// BACKWARD COMPATIBILITY:
//   Unity < 6 has no UIScene. findSceneDelegateClass() finds nothing and returns
//   nil. The whole file is a no-op. AppDelegate swizzles handle everything.

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

extern "C" void cl_forward_url_string(const char * _Nullable urlStr);

static void forwardURL(NSURL * _Nullable url) {
    if (!url) return;
    NSString *str = url.absoluteString;
    if (!str.length) return;
    NSLog(@"[ChottuLink] forwardURL: %@", str);
    cl_forward_url_string(str.UTF8String);
}

typedef void (*SceneOpenURLIMP)(id, SEL, UIScene *, NSSet *);
typedef void (*SceneWillConnectIMP)(id, SEL, UIScene *, UISceneSession *, UISceneConnectionOptions *);
typedef void (*SceneContinueActivityIMP)(id, SEL, UIScene *, NSUserActivity *);

static SceneOpenURLIMP          orig_openURLContexts  = NULL;
static SceneWillConnectIMP      orig_willConnect      = NULL;
static SceneContinueActivityIMP orig_continueActivity = NULL;

// MARK: - Replacement implementations

static void cl_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *ctxs) {
    NSLog(@"[ChottuLink] scene:openURLContexts: fired, count=%lu", (unsigned long)ctxs.count);
    for (UIOpenURLContext *ctx in ctxs) forwardURL(ctx.URL);
    if (orig_openURLContexts) orig_openURLContexts(self, _cmd, scene, ctxs);
}

static void cl_scene_willConnect(id self, SEL _cmd,
                                  UIScene *scene,
                                  UISceneSession *session,
                                  UISceneConnectionOptions *opts) {
    NSLog(@"[ChottuLink] scene:willConnectToSession:options: fired, URLContexts=%lu userActivities=%lu",
          (unsigned long)opts.URLContexts.count,
          (unsigned long)opts.userActivities.count);
    for (UIOpenURLContext *ctx in opts.URLContexts) forwardURL(ctx.URL);
    NSUserActivity *activity = opts.userActivities.anyObject;
    if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb])
        forwardURL(activity.webpageURL);
    if (orig_willConnect) orig_willConnect(self, _cmd, scene, session, opts);
}

static void cl_scene_continueActivity(id self, SEL _cmd,
                                       UIScene *scene,
                                       NSUserActivity *activity) {
    NSLog(@"[ChottuLink] scene:continueUserActivity: fired, type=%@", activity.activityType);
    if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb])
        forwardURL(activity.webpageURL);
    if (orig_continueActivity) orig_continueActivity(self, _cmd, scene, activity);
}

// MARK: - Swizzle

static void swizzleSceneDelegateClass(Class cls) {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        NSLog(@"[ChottuLink] swizzling: %@", NSStringFromClass(cls));
        { SEL s = @selector(scene:openURLContexts:);
          Method m = class_getInstanceMethod(cls, s);
          if (m) { orig_openURLContexts = (SceneOpenURLIMP)method_getImplementation(m);
                   method_setImplementation(m, (IMP)cl_scene_openURLContexts); }
          else   { class_addMethod(cls, s, (IMP)cl_scene_openURLContexts, "v@:@@"); } }

        { SEL s = @selector(scene:willConnectToSession:options:);
          Method m = class_getInstanceMethod(cls, s);
          if (m) { orig_willConnect = (SceneWillConnectIMP)method_getImplementation(m);
                   method_setImplementation(m, (IMP)cl_scene_willConnect); }
          else   { class_addMethod(cls, s, (IMP)cl_scene_willConnect, "v@:@@@"); } }

        { SEL s = @selector(scene:continueUserActivity:);
          Method m = class_getInstanceMethod(cls, s);
          if (m) { orig_continueActivity = (SceneContinueActivityIMP)method_getImplementation(m);
                   method_setImplementation(m, (IMP)cl_scene_continueActivity); }
          else   { class_addMethod(cls, s, (IMP)cl_scene_continueActivity, "v@:@@"); } }

        NSLog(@"[ChottuLink] swizzling complete on %@", NSStringFromClass(cls));
    });
}

// MARK: - Class discovery at boot time
//
// Finds the UIWindowSceneDelegate class at dyld load time. All Obj-C classes in
// the same binary are registered before any constructor runs, so NSClassFromString
// and class_getInstanceMethod are reliable here.
//
// Strategy:
//   1. Try known Unity class names (fastest path, no scanning needed).
//   2. Scan all registered classes for scene:willConnectToSession:options: —
//      this future-proofs against Unity renaming the class again.
static Class findSceneDelegateClass(void) {
    SEL targetSEL = @selector(scene:willConnectToSession:options:);

    // Known Unity scene delegate class names across versions
    for (NSString *name in @[@"UnityScene", @"UnityWindowSceneDelegate"]) {
        Class cls = NSClassFromString(name);
        if (cls && class_getInstanceMethod(cls, targetSEL)) {
            NSLog(@"[ChottuLink] found scene delegate by name: %@", name);
            return cls;
        }
    }

    // Fallback scan — skip obvious Apple/system prefixes
    NSSet *systemPrefixes = [NSSet setWithArray:@[@"UI", @"NS", @"CA", @"AV", @"CG",
                                                   @"CL", @"MK", @"SK", @"_", @"SF",
                                                   @"WK", @"PK", @"MT", @"SW"]];
    int count = objc_getClassList(NULL, 0);
    if (count <= 0) return nil;
    Class *classes = (Class *)malloc(sizeof(Class) * (size_t)count);
    if (!classes) return nil;
    objc_getClassList(classes, count);

    Class found = nil;
    for (int i = 0; i < count; i++) {
        if (!class_getInstanceMethod(classes[i], targetSEL)) continue;
        NSString *n = NSStringFromClass(classes[i]);
        BOOL isSystem = NO;
        for (NSString *pfx in systemPrefixes) {
            if ([n hasPrefix:pfx]) { isSystem = YES; break; }
        }
        if (!isSystem) {
            NSLog(@"[ChottuLink] found scene delegate by scan: %@", n);
            found = classes[i];
            break;
        }
    }
    free(classes);
    return found;
}

// MARK: - Bootstrap

__attribute__((constructor))
static void cl_scene_autoboot(void) {
    // Swizzle at dyld load time — before main(), before any lifecycle callbacks.
    // This is the only reliable way to intercept scene:willConnectToSession:options:
    // on cold start, since UISceneWillConnectNotification fires AFTER that method.
    Class cls = findSceneDelegateClass();
    if (cls) {
        swizzleSceneDelegateClass(cls);
    } else {
        NSLog(@"[ChottuLink] no UIScene delegate found — Unity < 6, AppDelegate path active");
    }
}
