// src/engine/core/ControlPlane.ts

/**
 * Control Plane - Topic-based pub/sub for discovery and global events.
 *
 * ═══════════════════════════════════════════════════════════════════════════
 * TWO-CHANNEL ARCHITECTURE (Vision 32-ACTOR-MAILBOX-SEMANTICS)
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * The Matrix architecture uses two distinct communication channels:
 *
 * 1. CONTROL PLANE (this module) - Topic pub/sub
 *    - Discovery: "Who handles command X?"
 *    - Presence: "Actor Y is now available"
 *    - Global events: "System shutting down"
 *    - Introspection queries
 *
 * 2. DATA PLANE (Mailbox) - Exclusive actor mailboxes
 *    - Direct actor messaging
 *    - Request-reply patterns
 *    - Serialized message delivery
 *
 * Why two channels?
 * - Topics are broadcast (1-to-many) → good for discovery
 * - Mailboxes are exclusive (1-to-1) → good for guaranteed delivery
 * - Separation prevents discovery traffic from blocking execution
 *
 * @module
 */

import type { ITransportAdapter } from '../remoting/ITransportAdapter';
import { TopicRouter } from '../messaging/TopicRouter';

/**
 * Actor registration info in the control plane.
 */
export interface IActorRegistration {
  /** Actor's mount path (e.g., "app.counter") */
  mount: string;
  /** Actor's mailbox address (e.g., "app.counter.$inbox") */
  mailbox: string;
  /** Commands this actor accepts */
  accepts: string[];
  /** Events this actor emits */
  emits: string[];
  /** When the actor registered */
  registeredAt: number;
  /** Optional metadata */
  meta?: Record<string, unknown>;
}

/** @deprecated Use IActorRegistration instead */
export type ActorRegistration = IActorRegistration;

/**
 * Query for finding actors.
 */
export interface IActorQuery {
  /** Filter by command they accept */
  acceptsCommand?: string;
  /** Filter by event they emit */
  emitsEvent?: string;
  /** Filter by mount path prefix */
  mountPrefix?: string;
}

/** @deprecated Use IActorQuery instead */
export type ActorQuery = IActorQuery;

/**
 * ControlPlane manages actor discovery and global coordination.
 *
 * Usage:
 * ```typescript
 * const controlPlane = new ControlPlane(transport);
 *
 * // Register an actor
 * controlPlane.register({
 *   mount: 'app.counter',
 *   mailbox: 'app.counter.$inbox',
 *   accepts: ['increment', 'query'],
 *   emits: ['changed'],
 * });
 *
 * // Find actors that handle a command
 * const handlers = controlPlane.findByCommand('increment');
 *
 * // Subscribe to presence
 * controlPlane.onActorAvailable((actor) => {
 *   console.log(`Actor available: ${actor.mount}`);
 * });
 * ```
 */
export class ControlPlane {
  private readonly _transport: ITransportAdapter;
  private readonly _actors = new Map<string, IActorRegistration>();
  private readonly _unsubs = new Set<() => void>();

  /** Topic for actor availability announcements */
  static readonly TOPIC_AVAILABLE = 'matrix.$control.available';
  /** Topic for actor removal announcements */
  static readonly TOPIC_REMOVED = 'matrix.$control.removed';
  /** Topic prefix for introspection requests */
  static readonly TOPIC_INTROSPECT = 'matrix.$control.introspect';

  constructor(transport: ITransportAdapter) {
    this._transport = transport;
    this._setupSubscriptions();
  }

  /**
   * Register an actor in the control plane.
   *
   * This broadcasts the actor's presence to all listeners and
   * indexes it for later discovery queries.
   *
   * @param info - Actor registration info
   */
  register(info: Omit<IActorRegistration, 'registeredAt'>): void {
    const registration: IActorRegistration = {
      ...info,
      registeredAt: Date.now(),
    };

    this._actors.set(info.mount, registration);

    // Broadcast availability
    this._transport.publish(ControlPlane.TOPIC_AVAILABLE, registration);
  }

  /**
   * Unregister an actor from the control plane.
   *
   * This broadcasts the actor's removal and removes it from the index.
   *
   * @param mount - Actor's mount path
   */
  unregister(mount: string): void {
    const registration = this._actors.get(mount);
    if (!registration) return;

    this._actors.delete(mount);

    // Broadcast removal
    this._transport.publish(ControlPlane.TOPIC_REMOVED, {
      mount,
      removedAt: Date.now(),
    });
  }

  /**
   * Find actors by query criteria.
   *
   * @param query - Query criteria
   * @returns Matching actor registrations
   */
  find(query: IActorQuery): IActorRegistration[] {
    const results: IActorRegistration[] = [];

    for (const actor of this._actors.values()) {
      let matches = true;

      if (query.acceptsCommand) {
        matches = matches && actor.accepts.includes(query.acceptsCommand);
      }
      if (query.emitsEvent) {
        matches = matches && actor.emits.includes(query.emitsEvent);
      }
      if (query.mountPrefix) {
        matches = matches && actor.mount.startsWith(query.mountPrefix);
      }

      if (matches) {
        results.push(actor);
      }
    }

    return results;
  }

  /**
   * Find actors that accept a specific command.
   *
   * @param command - Command name
   * @returns Matching actor registrations
   */
  findByCommand(command: string): IActorRegistration[] {
    return this.find({ acceptsCommand: command });
  }

  /**
   * Find actors that emit a specific event.
   *
   * @param event - Event name
   * @returns Matching actor registrations
   */
  findByEvent(event: string): IActorRegistration[] {
    return this.find({ emitsEvent: event });
  }

  /**
   * Get an actor by mount path.
   *
   * @param mount - Actor's mount path
   * @returns Actor registration or undefined
   */
  get(mount: string): IActorRegistration | undefined {
    return this._actors.get(mount);
  }

  /**
   * Check if an actor is registered.
   *
   * @param mount - Actor's mount path
   */
  has(mount: string): boolean {
    return this._actors.has(mount);
  }

  /**
   * Get all registered actors.
   */
  getAll(): IActorRegistration[] {
    return Array.from(this._actors.values());
  }

  /**
   * Subscribe to actor availability events.
   *
   * @param handler - Called when an actor becomes available
   * @returns Unsubscribe function
   */
  onActorAvailable(handler: (actor: IActorRegistration) => void): () => void {
    return this._transport.subscribe(ControlPlane.TOPIC_AVAILABLE, (payload) => {
      handler(payload as IActorRegistration);
    });
  }

  /**
   * Subscribe to actor removal events.
   *
   * @param handler - Called when an actor is removed
   * @returns Unsubscribe function
   */
  onActorRemoved(handler: (info: { mount: string; removedAt: number }) => void): () => void {
    return this._transport.subscribe(ControlPlane.TOPIC_REMOVED, (payload) => {
      handler(payload as { mount: string; removedAt: number });
    });
  }

  /**
   * Request introspection from an actor.
   *
   * @param mount - Actor's mount path
   * @param timeoutMs - Timeout in milliseconds
   * @returns Introspection result or null if timeout
   */
  async introspect(mount: string, timeoutMs = 5000): Promise<unknown | null> {
    const correlationId = `introspect-${mount}-${Date.now()}`;
    const replyTopic = `${ControlPlane.TOPIC_INTROSPECT}.reply.${correlationId}`;

    return new Promise((resolve) => {
      let resolved = false;

      const timeout = setTimeout(() => {
        if (!resolved) {
          resolved = true;
          resolve(null);
        }
      }, timeoutMs);

      const unsub = this._transport.subscribe(replyTopic, (payload) => {
        if (!resolved) {
          resolved = true;
          clearTimeout(timeout);
          unsub();
          resolve(payload);
        }
      });

      // Send introspect command to actor's $inbox
      this._transport.publish(TopicRouter.inbox(mount), {
        op: 'introspect',
        payload: {},
        correlationId,
        replyTo: replyTopic,
      });
    });
  }

  /**
   * Dispose the control plane.
   */
  dispose(): void {
    for (const unsub of this._unsubs) {
      try { unsub(); } catch {}
    }
    this._unsubs.clear();
    this._actors.clear();
  }

  /**
   * Set up internal subscriptions.
   */
  private _setupSubscriptions(): void {
    // Listen for availability announcements to build local index
    const availUnsub = this._transport.subscribe(ControlPlane.TOPIC_AVAILABLE, (payload) => {
      const registration = payload as IActorRegistration;
      // Only add if not already tracking (prevents duplicates from our own publishes)
      if (!this._actors.has(registration.mount)) {
        this._actors.set(registration.mount, registration);
      }
    });
    this._unsubs.add(availUnsub);

    // Listen for removal announcements to clean up local index
    const removeUnsub = this._transport.subscribe(ControlPlane.TOPIC_REMOVED, (payload) => {
      const info = payload as { mount: string };
      this._actors.delete(info.mount);
    });
    this._unsubs.add(removeUnsub);
  }
}

/**
 * Create a control plane instance.
 *
 * @param transport - Transport adapter to use
 */
export function createControlPlane(transport: ITransportAdapter): ControlPlane {
  return new ControlPlane(transport);
}
