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

import type { TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";
import { BrickError } from "./BrickError";

/**
 * Base interface that all Brick module specs must extend
 */
export interface BrickModuleInterface extends TurboModule {
  readonly moduleName: 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;

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

/**
 * Enhanced typed module interface with event listeners
 */
export type BrickModuleWithEvents<T extends BrickModuleInterface> = T;

/**
 * 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();

  // Cache constants for this module (computed once, reused)
  let constantsCache: Record<string, any> | null = null;

  // 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;
      }

      // New-style event subscription: onXxx((event) => void) → EventSubscription
      if (
        property.startsWith("on") &&
        property.length > 2 &&
        property[2] === property[2].toUpperCase()
      ) {
        return (listener: (event: unknown) => void) => {
          // Respect RN CodegenTypes.EventEmitter: call `${moduleName}_onXxx` and return as-is
          const nativeEventMethod = `${moduleName}_${property}`;
          const nativeFn = (nativeModuleInstance as any)[nativeEventMethod];
          if (typeof nativeFn === "function") {
            return nativeFn(listener);
          }
          throw new Error(
            `${nativeEventMethod} is not available. Did you run RN codegen?`
          );
        };
      }

      // Special handling for getConstants method (with caching)
      if (property === "getConstants") {
        return () => {
          if (constantsCache !== null) {
            return constantsCache;
          }

          const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
          const moduleConstants: Record<string, any> = {};
          const constantPrefix = `${moduleName}_`;

          for (const key in allConstants) {
            if (key.startsWith(constantPrefix)) {
              const propName = key.substring(constantPrefix.length);
              moduleConstants[propName] = allConstants[key];
            }
          }

          constantsCache = moduleConstants;
          return moduleConstants;
        };
      }

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

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

          // Check if result is a wrapped sync method result
          // Sync methods have explicit "~sync": true marker for reliable detection
          // Format: { "~sync": true, success: true, value: T } | { "~sync": true, success: false, error: {...} }
          if (
            result &&
            typeof result === "object" &&
            result["~sync"] === true
          ) {
            if (result.success === true) {
              // Success: return the unwrapped value
              return result.value;
            } else {
              // Failure: throw a BrickError with all properties from error object
              const errorInfo = result.error || {};
              const message = errorInfo.message || errorInfo.errorMessage || "Unknown error";
              const code = errorInfo.code || errorInfo.errorCode || "BRICK_ERROR";

              // Build userInfo from additional properties
              const userInfo: Record<string, unknown> = { code };
              for (const key of Object.keys(errorInfo)) {
                if (key !== "code" && key !== "message" && key !== "errorCode" && key !== "errorMessage") {
                  userInfo[key] = errorInfo[key];
                }
              }

              throw new BrickError(message, code, userInfo, moduleName);
            }
          }

          // Async method: wrap Promise and convert to BrickError
          if (result && typeof result.then === "function") {
            return result.catch((error: unknown) => {
              // Convert to BrickError with moduleName for type-safe error handling
              throw BrickError.from(error, moduleName);
            });
          }

          // Not wrapped: return as-is
          return result;
        }

        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
