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

  /// A lifecycle hook that is called once the module is initialized and registered in the app context.
  /// An equivalent of the `OnCreate` DSL component.
  func didCreate()

  /// A lifecycle hook that is called when the module is about to be deallocated.
  /// An equivalent of the `OnDestroy` DSL component.
  func willDestroy()

  /// A lifecycle hook that is called when the first JavaScript listener for the given event is added.
  /// An equivalent of the `OnStartObserving` DSL component.
  func didStartListening(event: String)

  /// A lifecycle hook that is called when the last JavaScript listener for the given event is removed.
  /// An equivalent of the `OnStopObserving` DSL component.
  func didStopListening(event: String)
}

extension AnyModule {
  public 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
  public func definition() -> ModuleDefinition {}

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

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

  // No-op defaults of the lifecycle hooks for modules that conform without inheriting
  // `BaseModule`, e.g. plain `@ExpoModule` classes whose conformance comes from the macro's
  // extension. These defaults are statically bound into the conformance witness, so
  // `BaseModule` descendants are backed by its dynamically-dispatched open methods instead,
  // which take precedence during witness resolution.

  public func didCreate() {}

  public func willDestroy() {}

  public func didStartListening(event: String) {}

  public func didStopListening(event: String) {}
}
