// src/transport/TransportProxy.ts
//
// TransportProxy - Runtime transport switching without page reload.
// Enables hot-swapping between transports while preserving subscriptions.
//
// Per TASK-5: Browser-side transport switching pattern for:
// - Development: WebSocket → direct IPC
// - Production: Local broker → federation broker
// - Recovery: Reconnect to different endpoint

import type {
  ITransportAdapter,
  TransportHandler,
  IWireTopicInfo
} from '../engine/remoting/ITransportAdapter.js';

/**
 * Tracked subscription info for migration.
 */
interface TrackedSubscription {
  readonly topic: string;
  readonly handler: TransportHandler;
  currentUnsub: () => void;
}

/**
 * Switch listener callback type.
 */
type SwitchListener = (newTransport: ITransportAdapter) => void;

interface ILegacyIdentityTransport {
  principal?: string;
  execToken?: string;
}

/**
 * TransportProxy - Proxy that enables runtime transport switching.
 *
 * Implements ITransportAdapter and delegates to an underlying transport.
 * Tracks all subscriptions so they can be migrated when switching transports.
 *
 * Usage:
 * ```typescript
 * const nats = new NatsTransport({ ... });
 * const proxy = new TransportProxy(nats);
 *
 * // Use proxy like any transport
 * proxy.subscribe('topic', handler);
 * proxy.publish('topic', data);
 *
 * // Later: hot-swap to different transport
 * const ws = new WebSocketTransport({ ... });
 * await proxy.switchTo(ws);
 * // All subscriptions migrated, handlers continue receiving
 * ```
 */
export class TransportProxy implements ITransportAdapter {
  private _currentTransport: ITransportAdapter;
  private readonly _subscriptions: Set<TrackedSubscription> = new Set();
  private readonly _switchListeners: Set<SwitchListener> = new Set();
  private _switching = false;

  constructor(initialTransport: ITransportAdapter) {
    this._currentTransport = initialTransport;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // ITransportAdapter Interface
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Topic prefix from underlying transport.
   */
  get topicPrefix(): string | undefined {
    return this._currentTransport.topicPrefix;
  }

  /**
   * Forward root from underlying transport.
   */
  get root(): string {
    return this._currentTransport.root;
  }

  /** @deprecated Use `root`. */
  get realm(): string {
    return this.root;
  }

  /**
   * Forward principal from underlying transport (for MXTOPIC-1).
   */
  get principal(): string | undefined {
    const legacy = this._currentTransport as ITransportAdapter & ILegacyIdentityTransport;
    return legacy.principal;
  }

  /**
   * Forward execToken from underlying transport (for MXTOPIC-1).
   */
  get execToken(): string | undefined {
    const legacy = this._currentTransport as ITransportAdapter & ILegacyIdentityTransport;
    return legacy.execToken;
  }

  /**
   * Get the underlying transport client if the current transport exposes one.
   * Used for low-level operations like cross-root mailbox publishing.
   *
   * @returns The raw transport client, or undefined if not available
   */
  getUnderlyingClient(): unknown {
    return this._currentTransport.getUnderlyingClient();
  }

  async disconnect(): Promise<void> {
    await this._currentTransport.disconnect();
  }

  async flush(): Promise<void> {
    await this._currentTransport.flush?.();
  }

  /**
   * Publish to current transport.
   */
  publish(topic: string, payload?: unknown): void {
    this._currentTransport.publish(topic, payload);
  }

  /**
   * Subscribe via current transport, tracking for migration.
   */
  subscribe(topic: string, handler: TransportHandler): () => void {
    // Subscribe on current transport
    const unsub = this._currentTransport.subscribe(topic, handler);

    // Track for migration
    const tracked: TrackedSubscription = {
      topic,
      handler,
      currentUnsub: unsub
    };
    this._subscriptions.add(tracked);

    // Return unsubscribe function that cleans up tracking
    return () => {
      this._subscriptions.delete(tracked);
      try {
        tracked.currentUnsub();
      } catch (err) {
        // eslint-disable-next-line no-console -- kernel transport proxy has no actor context for unsubscribe cleanup failures
        console.warn('[TransportProxy] Unsubscribe error:', err);
      }
    };
  }

  /**
   * Get wire topic info from underlying transport.
   */
  getWireTopicInfo?(topic: string): IWireTopicInfo {
    return this._currentTransport.getWireTopicInfo?.(topic) ?? {
      appTopic: topic,
      wireTopic: topic,
      prefix: ''
    };
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Transport Switching
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Switch to a new transport, migrating all subscriptions.
   *
   * @param newTransport - The new transport to switch to
   * @throws If a switch is already in progress
   */
  async switchTo(newTransport: ITransportAdapter): Promise<void> {
    if (this._switching) {
      throw new Error('Transport switch already in progress');
    }

    this._switching = true;

    try {
      // 1. Unsubscribe from old transport (collect errors, don't throw)
      for (const tracked of this._subscriptions) {
        try {
          tracked.currentUnsub();
        } catch (err) {
          // eslint-disable-next-line no-console -- kernel transport proxy has no actor context for old-transport unsubscribe failures
          console.warn('[TransportProxy] Old transport unsubscribe error:', err);
        }
      }

      // 2. Update current transport
      this._currentTransport = newTransport;

      // 3. Re-subscribe on new transport
      for (const tracked of this._subscriptions) {
        const newUnsub = newTransport.subscribe(tracked.topic, tracked.handler);
        // Update the tracked subscription's unsub function
        (tracked as { currentUnsub: () => void }).currentUnsub = newUnsub;
      }

      // 4. Notify listeners
      for (const listener of this._switchListeners) {
        try {
          listener(newTransport);
        } catch (err) {
          // eslint-disable-next-line no-console -- kernel transport proxy has no actor context for switch listener failures
          console.warn('[TransportProxy] Switch listener error:', err);
        }
      }
    } finally {
      this._switching = false;
    }
  }

  /**
   * Register a listener for transport switches.
   *
   * @param listener - Called when transport is switched
   * @returns Function to remove the listener
   */
  onSwitch(listener: SwitchListener): () => void {
    this._switchListeners.add(listener);
    return () => {
      this._switchListeners.delete(listener);
    };
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Status / Debugging
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Get number of tracked subscriptions.
   */
  getSubscriptionCount(): number {
    return this._subscriptions.size;
  }

  /**
   * Get unique topics currently subscribed.
   */
  getTrackedTopics(): string[] {
    const topics = new Set<string>();
    for (const sub of this._subscriptions) {
      topics.add(sub.topic);
    }
    return Array.from(topics);
  }

  /**
   * Check if a switch is in progress.
   */
  isSwitching(): boolean {
    return this._switching;
  }

  /**
   * Get the type name of the underlying transport.
   */
  getTransportType(): string {
    return this._currentTransport.constructor.name;
  }

  /**
   * Get the underlying transport instance.
   */
  getUnderlyingTransport(): ITransportAdapter {
    return this._currentTransport;
  }
}
