/**
 * TransportSelector - Automatic transport selection
 *
 * Selection order by federation mode:
 * - auto: local broker → public broker → in-memory
 * - local: local broker only
 * - public: public broker only
 * - disabled: in-memory only
 */

import type { RootRegistry, RootEndpoint } from './root_registry';
import { TransportProbe } from './transport_probe';
// Deprecated aliases for backward compat
/** @deprecated Use RootRegistry */
type RealmRegistry = RootRegistry;
/** @deprecated Use RootEndpoint */
type RealmEndpoint = RootEndpoint;

/**
 * Federation mode determines how the realm connects to transports.
 * Duplicated from Matrix-3 core to keep federation package self-contained.
 */
export type FederationMode = 'auto' | 'local' | 'public' | 'disabled';

export type TransportEvent = 'connecting' | 'connected' | 'disconnected' | 'fallback';
export type TransportEventCallback = (event: TransportEvent, endpoint?: RootEndpoint) => void;

export class TransportUnavailableError extends Error {
  constructor(root: string, mode: FederationMode) {
    super(`No transport available for root "${root}" in mode "${mode}"`);
    this.name = 'TransportUnavailableError';
  }
}

export class TransportSelector {
  private _probe: TransportProbe;
  private _connected: Map<string, RootEndpoint> = new Map();
  private _listeners: Set<TransportEventCallback> = new Set();

  constructor(
    private _registry: RootRegistry,
    private _mode: FederationMode
  ) {
    this._probe = new TransportProbe();
  }

  /**
   * Select the best available transport for a target realm.
   * Creates transport lazily on first call for each realm.
   */
  async select(targetRoot: string): Promise<RootEndpoint> {
    // Check if already connected
    const existing = this._connected.get(targetRoot);
    if (existing) {
      return existing;
    }

    this._emit('connecting');

    // Get candidate endpoints
    const candidates = await this._getCandidates(targetRoot);

    // Try each candidate
    for (const endpoint of candidates) {
      const result = await this._probe.probe(endpoint);
      if (result.available) {
        this._connected.set(targetRoot, endpoint);
        this._emit('connected', endpoint);
        return endpoint;
      }
    }

    throw new TransportUnavailableError(targetRoot, this._mode);
  }

  /**
   * Subscribe to transport events.
   */
  on(callback: TransportEventCallback): () => void {
    this._listeners.add(callback);
    return () => this._listeners.delete(callback);
  }

  /**
   * Mark transport as disconnected (triggers fallback on next select).
   */
  markDisconnected(root: string): void {
    const endpoint = this._connected.get(root);
    this._connected.delete(root);
    this._emit('disconnected', endpoint);
  }

  private async _getCandidates(targetRoot: string): Promise<RootEndpoint[]> {
    const candidates: RootEndpoint[] = [];

    switch (this._mode) {
      case 'disabled':
        // In-memory only
        candidates.push({ type: 'memory', url: '', priority: 1 });
        break;

      case 'local': {
        // Local broker only
        const localEndpoints = this._registry.resolveAll(targetRoot);
        candidates.push(...localEndpoints.filter(e => e.type === 'ws'));
        break;
      }

      case 'public': {
        // Public broker only
        const publicEndpoint = this._registry.resolve('_public');
        if (publicEndpoint) {
          candidates.push(publicEndpoint);
        }
        break;
      }

      case 'auto':
      default: {
        // Try in order: local -> public -> memory
        const allEndpoints = this._registry.resolveAll(targetRoot);
        candidates.push(...allEndpoints);

        const fallback = this._registry.resolve('_public');
        if (fallback) {
          candidates.push(fallback);
        }

        // Final fallback: in-memory
        candidates.push({ type: 'memory', url: '', priority: 999 });
        break;
      }
    }

    return candidates;
  }

  private _emit(event: TransportEvent, endpoint?: RootEndpoint): void {
    for (const cb of this._listeners) {
      try {
        cb(event, endpoint);
      } catch (err) {
        console.error('TransportSelector listener error:', err);
      }
    }
  }
}
