/**
 * Brick Module Framework - Main API
 * Type-safe access to native modules with automatic code generation
 */

import type { TurboModule } from "react-native";
import { TurboModuleRegistry, NativeEventEmitter } from "react-native";

/**
 * Base interface that all Brick module specs must extend
 */
export interface BrickModuleInterface extends TurboModule {
  readonly moduleName: string;
  readonly supportedEvents?: readonly string[];
}

/**
 * Type alias for module specifications
 * Use this as the base interface when defining your module specs
 * @example
 * export interface MyModuleSpec extends BrickModuleSpec {
 *   readonly moduleName: "MyModule";
 *   readonly supportedEvents: ["eventA", "eventB"];
 *   myMethod(param: string): Promise<string>;
 * }
 */
export type BrickModuleSpec = BrickModuleInterface;

// Module-level state (previously static class members)
const moduleCache = new Map<string, any>();
let nativeModule: any = null;
let eventEmitter: NativeEventEmitter | null = null;

/**
 * Gets the native TurboModule instance
 * @private
 */
function getNativeModule() {
  if (!nativeModule) {
    nativeModule = TurboModuleRegistry.getEnforcing("BrickModule");
  }
  return nativeModule;
}

function getEventEmitter() {
  if (!eventEmitter) {
    const nativeModuleInstance = getNativeModule();
    eventEmitter = new NativeEventEmitter(nativeModuleInstance);
    console.log("eventEmitter", eventEmitter);
  }
  return eventEmitter;
}

/**
 * Enhanced typed module interface with event listeners
 */
export type BrickModuleWithEvents<T extends BrickModuleInterface> = T & {
  /**
   * Adds a type-safe event listener for this module
   * @param eventName - One of the supported events defined in the module spec
   * @param listener - Callback function to handle the event
   * @returns Unsubscription function to remove the listener
   */
  addEventListener<TEvent = unknown>(
    eventName: T["supportedEvents"] extends readonly (infer U)[] ? U : never,
    listener: (event: TEvent) => void
  ): () => void;
};

/**
 * Gets a typed module instance by name with explicit type parameter
 * @param moduleName - The exact name of the module as defined in its spec
 * @returns Typed module interface with all methods, constants, and event listeners
 */
function get<T extends BrickModuleInterface>(
  moduleName: string
): BrickModuleWithEvents<T> {
  // Check cache first
  const cacheKey = moduleName;
  if (moduleCache.has(cacheKey)) {
    return moduleCache.get(cacheKey);
  }

  const nativeModuleInstance = getNativeModule();

  // Create a proxy that intercepts method calls and forwards them to the native module
  const moduleProxy = new Proxy({} as BrickModuleWithEvents<T>, {
    get: (_target, property: string | symbol) => {
      if (typeof property !== "string") {
        return undefined;
      }

      // Handle event listener methods
      if (property === "addEventListener") {
        return (eventName: string, listener: (event: unknown) => void) => {
          const emitter = getEventEmitter();
          const subscription = emitter.addListener(
            `${moduleName}_${eventName}`,
            listener
          );

          return () => {
            subscription.remove();
          };
        };
      }

      // Handle individual constants - check if this property exists as a constant
      const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
      const constantKey = `${moduleName}_${property}`;
      if (constantKey in allConstants) {
        return allConstants[constantKey];
      }

      // Handle method calls
      return (...args: any[]) => {
        const methodKey = `${moduleName}_${property}`;

        // Try direct method call first (generated by codegen)
        if (typeof nativeModuleInstance[methodKey] === "function") {
          return nativeModuleInstance[methodKey](...args);
        }

        throw new Error(`Method ${methodKey} not found`);
      };
    },

    has: (_target, property) => {
      return typeof property === "string";
    },

    ownKeys: (_target) => {
      // Return empty array since we're proxying all property access
      return [];
    },
  });

  // Cache the proxy
  moduleCache.set(cacheKey, moduleProxy);
  return moduleProxy;
}

/**
 * Gets list of all registered modules
 * @returns Promise resolving to array of module names
 */
function getRegisteredModules(): string[] {
  const nativeModuleInstance = getNativeModule();
  return nativeModuleInstance?.getRegisteredModules() ?? [];
}

/**
 * Clears the module cache (useful for testing or hot reloading)
 * @internal
 */
function clearCache(): void {
  moduleCache.clear();
}

/**
 * Main Brick Module API object
 * Provides type-safe access to native modules
 */
export const BrickModule = {
  get,
  getRegisteredModules,
  clearCache,
} as const;

/**
 * Default export for convenience
 */
export default BrickModule;

// Re-export types handled by main index.ts
