import type { IRouteConfig, IRouteAction, IRouteTarget } from './models/route-types.js';
import type { IRustRouteConfig } from './models/rust-types.js';
import { serializeRouteForRust } from './utils/rust-config.js';

/**
 * Preprocesses routes before sending them to Rust.
 *
 * Strips non-serializable fields (functions, callbacks) and classifies
 * routes that must be handled by TypeScript (socket-handler, dynamic host/port).
 */
export class RoutePreprocessor {
  /**
   * Map of route name/id → original route config (with JS functions preserved).
   * Used by the socket handler server to look up the original handler.
   */
  private originalRoutes = new Map<string, IRouteConfig>();

  /**
   * Preprocess routes for the Rust binary.
   *
   * - Routes with `socketHandler` callbacks are marked as socket-handler type
   *   (Rust will relay these back to TS)
   * - Routes with dynamic `host`/`port` functions are converted to socket-handler
   *   type (Rust relays, TS resolves the function)
   * - Non-serializable fields are stripped
   * - Original routes are preserved in the local map for handler lookup
   */
  public preprocessForRust(routes: IRouteConfig[]): IRustRouteConfig[] {
    this.originalRoutes.clear();
    return routes.map((route, index) => this.preprocessRoute(route, index));
  }

  /**
   * Get the original route config (with JS functions) by route name or id.
   */
  public getOriginalRoute(routeKey: string): IRouteConfig | undefined {
    return this.originalRoutes.get(routeKey);
  }

  /**
   * Get all original routes that have socket handlers or dynamic functions.
   */
  public getHandlerRoutes(): Map<string, IRouteConfig> {
    return new Map(this.originalRoutes);
  }

  private preprocessRoute(route: IRouteConfig, index: number): IRustRouteConfig {
    const routeKey = route.name || route.id || `route_${index}`;

    // Check if this route needs TS-side handling
    const needsTsHandling = this.routeNeedsTsHandling(route);

    if (needsTsHandling) {
      // Store the original route for handler lookup
      this.originalRoutes.set(routeKey, route);
    }

    // Create a clean copy for Rust
    const cleanRoute: IRouteConfig = {
      ...route,
      action: this.cleanAction(route.action, needsTsHandling),
    };

    // Ensure we have a name for handler lookup
    if (!cleanRoute.name && !cleanRoute.id) {
      cleanRoute.name = routeKey;
    }

    return serializeRouteForRust(cleanRoute);
  }

  private routeNeedsTsHandling(route: IRouteConfig): boolean {
    // Socket handler routes always need TS
    if (route.action.type === 'socket-handler' && route.action.socketHandler) {
      return true;
    }

    // Datagram handler routes always need TS
    if (route.action.type === 'socket-handler' && route.action.datagramHandler) {
      return true;
    }

    // Routes with dynamic host/port functions need TS
    if (route.action.targets) {
      for (const target of route.action.targets) {
        if (typeof target.host === 'function' || typeof target.port === 'function') {
          return true;
        }
      }
    }

    return false;
  }

  private cleanAction(action: IRouteAction, needsTsHandling: boolean): IRouteAction {
    let cleanAction: IRouteAction = { ...action };

    if (needsTsHandling) {
      // Convert to socket-handler type for Rust (Rust will relay back to TS)
      const { socketHandler: _socketHandler, datagramHandler: _datagramHandler, ...serializableAction } = cleanAction;
      cleanAction = {
        ...serializableAction,
        type: 'socket-handler',
      };
    }

    // Clean targets - replace functions with static values
    if (cleanAction.targets) {
      cleanAction.targets = cleanAction.targets.map(t => this.cleanTarget(t));
    }

    return cleanAction;
  }

  private cleanTarget(target: IRouteTarget): IRouteTarget {
    const clean: IRouteTarget = { ...target };

    // Replace function host with placeholder
    if (typeof clean.host === 'function') {
      clean.host = 'localhost';
    }

    // Replace function port with placeholder
    if (typeof clean.port === 'function') {
      clean.port = 0;
    }

    return clean;
  }
}
