/*******************************************************
 * Copyright (C) 2026 XMind Ltd. - All Rights Reserved
 *
 * Sign in with Apple using ASWebAuthenticationSession
 *******************************************************/

#import <Foundation/Foundation.h>
#import <AuthenticationServices/AuthenticationServices.h>
#import <AppKit/AppKit.h>
#import <napi.h>
#import "./siwa.h"

NSString* ToNSString(const std::string& value) {
  return [[NSString alloc] initWithUTF8String:value.c_str()];
}

std::string ToStdString(NSString* value) {
  if (value == nil) return "";
  return std::string([value UTF8String]);
}

@interface SiwaPresentationContextProvider : NSObject <ASWebAuthenticationPresentationContextProviding>
@end

@implementation SiwaPresentationContextProvider
- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session {
  NSApplication *app = [NSApplication sharedApplication];
  return app.keyWindow ?: app.mainWindow ?: app.windows.firstObject;
}
@end

// Single-threaded, reused across sessions
static ASWebAuthenticationSession* currentSession = nil;
static SiwaPresentationContextProvider* contextProvider = nil;

Napi::Value SiwaIsAvailable(const Napi::CallbackInfo& info) {
  return Napi::Boolean::New(info.Env(), true);
}

Napi::Value SiwaSignIn(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  if (info.Length() < 3) {
    Napi::TypeError::New(env, "Expected 3 arguments: url, callbackURLScheme, callback")
        .ThrowAsJavaScriptException();
    return env.Undefined();
  }

  if (!info[0].IsString() || !info[1].IsString() || !info[2].IsFunction()) {
    Napi::TypeError::New(env, "Invalid argument types: expected (string, string, function)")
        .ThrowAsJavaScriptException();
    return env.Undefined();
  }

  std::string urlStr = info[0].As<Napi::String>().Utf8Value();
  std::string callbackSchemeStr = info[1].As<Napi::String>().Utf8Value();
  Napi::Function callback = info[2].As<Napi::Function>();

  auto tsfn = Napi::ThreadSafeFunction::New(env, callback, "SiwaCallback", 0, 1);

  @autoreleasepool {
    NSURL* authURL = [NSURL URLWithString:ToNSString(urlStr)];
    NSString* callbackScheme = ToNSString(callbackSchemeStr);

    if (authURL == nil) {
      tsfn.BlockingCall([](Napi::Env env, Napi::Function jsCallback) {
        jsCallback.Call({env.Null(), Napi::String::New(env, "Invalid authentication URL")});
      });
      tsfn.Release();
      return env.Undefined();
    }

    if (currentSession != nil) {
      [currentSession cancel];
      currentSession = nil;
    }

    if (contextProvider == nil) {
      contextProvider = [[SiwaPresentationContextProvider alloc] init];
    }

    currentSession = [[ASWebAuthenticationSession alloc]
        initWithURL:authURL
        callbackURLScheme:callbackScheme
        completionHandler:^(NSURL* _Nullable callbackURL, NSError* _Nullable error) {
          if (error != nil) {
            NSInteger errorCode = [error code];
            if (errorCode == ASWebAuthenticationSessionErrorCodeCanceledLogin) {
              tsfn.BlockingCall([](Napi::Env env, Napi::Function jsCallback) {
                Napi::Object result = Napi::Object::New(env);
                result.Set("cancelled", Napi::Boolean::New(env, true));
                jsCallback.Call({result, env.Null()});
              });
            } else {
              std::string errorMsg = ToStdString([error localizedDescription]);
              tsfn.BlockingCall([errorMsg](Napi::Env env, Napi::Function jsCallback) {
                jsCallback.Call({env.Null(), Napi::String::New(env, errorMsg)});
              });
            }
          } else if (callbackURL != nil) {
            std::string callbackURLStr = ToStdString([callbackURL absoluteString]);
            tsfn.BlockingCall([callbackURLStr](Napi::Env env, Napi::Function jsCallback) {
              Napi::Object result = Napi::Object::New(env);
              result.Set("cancelled", Napi::Boolean::New(env, false));
              result.Set("callbackURL", Napi::String::New(env, callbackURLStr));
              jsCallback.Call({result, env.Null()});
            });
          } else {
            NSLog(@"[siwa] Unexpected: no callbackURL and no error");
            tsfn.BlockingCall([](Napi::Env env, Napi::Function jsCallback) {
              jsCallback.Call({env.Null(), Napi::String::New(env, "Unknown error: no callback URL")});
            });
          }

          tsfn.Release();
          currentSession = nil;
        }];

    [currentSession setPresentationContextProvider:contextProvider];
    [currentSession setPrefersEphemeralWebBrowserSession:NO];

    if (![currentSession start]) {
      tsfn.BlockingCall([](Napi::Env env, Napi::Function jsCallback) {
        jsCallback.Call({env.Null(), Napi::String::New(env, "Failed to start authentication session")});
      });
      tsfn.Release();
      currentSession = nil;
    }
  }

  return env.Undefined();
}
