// Copyright 2022-present 650 Industries. All rights reserved.

#import <ExpoModulesJSI/EXJavaScriptObject.h>

#import <ExpoModulesCore/ExpoModulesHostObject.h>
#import <ExpoModulesCore/LazyObject.h>
#import <ExpoModulesCore/EXAppContextProtocol.h>

namespace expo {

ExpoModulesHostObject::ExpoModulesHostObject(EXAppContext *appContext) : appContext(appContext) {}

ExpoModulesHostObject::~ExpoModulesHostObject() {
  modulesCache.clear();
  // Cast to protocol to access _runtime property without importing Swift.h
  id<EXAppContextProtocol> appContext = (id<EXAppContextProtocol>)this->appContext;
  appContext._runtime = nil;
}

jsi::Value ExpoModulesHostObject::get(jsi::Runtime &runtime, const jsi::PropNameID &name) {
  std::string moduleName = name.utf8(runtime);
  NSString *nsModuleName = [NSString stringWithUTF8String:moduleName.c_str()];

  // Cast to protocol to access methods without importing Swift.h
  id<EXAppContextProtocol> appContext = (id<EXAppContextProtocol>)this->appContext;
  
  if (![appContext hasModule:nsModuleName]) {
    // The module object can already be cached but no longer registered — we remove it from the cache in that case.
    modulesCache.erase(moduleName);
    return jsi::Value::undefined();
  }
  if (UniqueJSIObject &cachedObject = modulesCache[moduleName]) {
    return jsi::Value(runtime, *cachedObject);
  }

  // Create a lazy object for the specific module. It defers initialization of the final module object.
  LazyObject::Shared moduleLazyObject = std::make_shared<LazyObject>(^SharedJSIObject(jsi::Runtime &runtime) {
    return [[appContext getNativeModuleObjectUnsafe:nsModuleName] getShared];
  });

  // Save the module's lazy host object for later use.
  modulesCache[moduleName] = std::make_unique<jsi::Object>(jsi::Object::createFromHostObject(runtime, moduleLazyObject));

  return jsi::Value(runtime, *modulesCache[moduleName]);
}

void ExpoModulesHostObject::set(jsi::Runtime &runtime, const jsi::PropNameID &name, const jsi::Value &value) {
  std::string message("RuntimeError: Cannot override the host object for expo module '");
  message += name.utf8(runtime);
  message += "'.";
  throw jsi::JSError(runtime, message);
}

std::vector<jsi::PropNameID> ExpoModulesHostObject::getPropertyNames(jsi::Runtime &runtime) {
  // Cast to protocol to access methods without importing Swift.h
  id<EXAppContextProtocol> appContext = (id<EXAppContextProtocol>)this->appContext;
  NSArray<NSString *> *moduleNames = [appContext getModuleNames];
  std::vector<jsi::PropNameID> propertyNames;

  propertyNames.reserve([moduleNames count]);

  for (NSString *moduleName in moduleNames) {
    propertyNames.push_back(jsi::PropNameID::forAscii(runtime, [moduleName UTF8String]));
  }
  return propertyNames;
}

} // namespace expo
