import ExpoModulesJSI

/**
 A protocol for any type-erased module that provides functions used by the core.
 */
public protocol AnyModule: AnyObject, AnyArgument {
  /**
   The default initializer. Must be public, but the module class does *not* need to
   define it as it is implemented in protocol composition, see `BaseModule` class.
   */
  init(appContext: AppContext)

  /**
   A DSL-like function that returns a `ModuleDefinition` which can be built up from module's name, constants or functions.
   The `@ModuleDefinitionBuilder` wrapper is *not* required in the implementation — it is implicitly inferred from the protocol.
   */
  @ModuleDefinitionBuilder
  func definition() -> ModuleDefinition

  /// The module's JavaScript name, emitted as a static constant by the `@ExpoModule` macro from the
  /// class name or the `@ExpoModule("CustomName")` argument. Core reads it as the last resort when
  /// naming the module, after the `Name(…)` DSL entry. Framework-internal (leading underscore).
  /// Modules that don't use the macro fall back to the default, which is the class name.
  static var _jsName: String { get }

  /// Returns definitions synthesized from `@JS`-annotated members by the `@ExpoModule` macro.
  /// Framework-internal: the leading underscore signals this is not part of the public API and
  /// should only be called by `expo-modules-core` itself. Modules that don't use the macro fall
  /// back to the default empty implementation.
  func _synthesizedDefinition() -> [AnyDefinition]

  /// Binds the module's `@JS` members directly into its JavaScript object, generated by the
  /// `@ExpoModule` macro. Core calls this after building the module object so the synthesized
  /// host functions are installed alongside the DSL-described surface. Framework-internal
  /// (leading underscore). Modules that don't use the macro fall back to the default no-op.
  @JavaScriptActor
  func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws
}

public extension AnyModule {
  static var _jsName: String {
    return String(describing: self)
  }

  /// An empty definition by default, so `@ExpoModule`-macro modules that describe their whole surface
  /// through `@JS` members don't have to write an empty `definition()` of their own. Modules that use
  /// the DSL provide their own.
  @ModuleDefinitionBuilder
  func definition() -> ModuleDefinition {}

  func _synthesizedDefinition() -> [AnyDefinition] {
    return []
  }

  @JavaScriptActor
  func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws {
    // No-op by default — only `@ExpoModule`-macro modules synthesize a real implementation.
  }
}
