// src/engine/messaging/ControlPlane.ts
//
// Control Plane - Discovery and introspection layer (Two-Channel Architecture)
//
// Separate from data plane (mailboxes) for:
// - Read-only operations (safe to cache, multi-subscribe)
// - Interface discovery
// - Actor registry queries

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

/**
 * Interface descriptor for control plane discovery.
 */
export interface IInterfaceDescriptor {
  /** Component mount path */
  mount: string;
  /** Component class name */
  className: string;
  /** Commands the component accepts */
  accepts: string[];
  /** Events the component emits */
  emits: string[];
  /** Additional metadata */
  meta?: Record<string, unknown>;
}

/**
 * Actor info returned from registry queries.
 */
export interface IActorInfo {
  /** Actor mount path */
  mount: string;
  /** Mailbox address for sending commands */
  mailbox: string;
  /** Interface descriptor */
  interface: IInterfaceDescriptor;
}

/**
 * Filter for actor queries.
 */
export interface IActorFilter {
  /** Filter by class name pattern */
  className?: string | RegExp;
  /** Filter by accepted command */
  accepts?: string;
  /** Filter by emitted event */
  emits?: string;
  /** Filter by mount path pattern */
  mount?: string | RegExp;
}

/**
 * Control Plane - Discovery and introspection layer.
 *
 * The Control Plane provides read-only access to actor metadata:
 * - Interface descriptors (what commands/events an actor supports)
 * - Actor discovery (find actors by capability)
 * - Registry queries
 *
 * This is separate from the Data Plane (mailboxes) which handles
 * state-changing commands with exclusive ownership.
 *
 * Topic structure:
 * - {mount}.$meta.interface → IInterfaceDescriptor
 * - {mount}.$meta.accepts → string[]
 * - {mount}.$meta.emits → string[]
 * - $registry.query → Query for actors
 * - $registry.list → List all actors
 */
export class ControlPlane {
  private readonly actors = new Map<string, IInterfaceDescriptor>();

  constructor(private readonly transport: ITransportAdapter) {
    // Subscribe to registry queries
    this.transport.subscribe('$registry.query', (payload) => {
      this.handleQuery(payload as IActorFilter);
    });

    this.transport.subscribe('$registry.list', () => {
      this.handleList();
    });
  }

  /**
   * Register an actor's interface for discovery.
   *
   * @param mount - Actor's mount path
   * @param descriptor - Interface descriptor
   */
  register(mount: string, descriptor: IInterfaceDescriptor): void {
    this.actors.set(mount, descriptor);

    // Publish to control plane topics
    this.publishInterface(mount, descriptor);
    this.publishAccepts(mount, descriptor.accepts);
    this.publishEmits(mount, descriptor.emits);
  }

  /**
   * Unregister an actor (on dispose).
   *
   * @param mount - Actor's mount path
   */
  unregister(mount: string): void {
    this.actors.delete(mount);

    // Publish empty descriptors to indicate actor is gone
    this.transport.publish(`${mount}.$meta.interface`, null);
  }

  /**
   * Publish actor's interface descriptor for discovery.
   */
  publishInterface(mount: string, descriptor: IInterfaceDescriptor): void {
    const topic = `${mount}.$meta.interface`;
    this.transport.publish(topic, descriptor);
  }

  /**
   * Publish what commands an actor accepts.
   */
  publishAccepts(mount: string, accepts: string[]): void {
    const topic = `${mount}.$meta.accepts`;
    this.transport.publish(topic, accepts);
  }

  /**
   * Publish what events an actor emits.
   */
  publishEmits(mount: string, emits: string[]): void {
    const topic = `${mount}.$meta.emits`;
    this.transport.publish(topic, emits);
  }

  /**
   * Subscribe to interface updates for an actor.
   */
  onInterfaceChange(mount: string, handler: (descriptor: IInterfaceDescriptor | null) => void): () => void {
    const topic = `${mount}.$meta.interface`;
    return this.transport.subscribe(topic, (payload) => {
      handler(payload as IInterfaceDescriptor | null);
    });
  }

  /**
   * Query actors by filter.
   *
   * @param filter - Query filter
   * @returns Matching actor info
   */
  query(filter: IActorFilter): IActorInfo[] {
    const results: IActorInfo[] = [];

    for (const [mount, descriptor] of this.actors) {
      if (this.matchesFilter(mount, descriptor, filter)) {
        results.push({
          mount,
          mailbox: `${mount}.$inbox`,
          interface: descriptor,
        });
      }
    }

    return results;
  }

  /**
   * List all registered actors.
   */
  list(): IActorInfo[] {
    return this.query({});
  }

  /**
   * Get interface descriptor for an actor.
   *
   * @param mount - Actor's mount path
   */
  getInterface(mount: string): IInterfaceDescriptor | undefined {
    return this.actors.get(mount);
  }

  /**
   * Check if an actor matches a filter.
   */
  private matchesFilter(mount: string, descriptor: IInterfaceDescriptor, filter: IActorFilter): boolean {
    // Filter by mount path
    if (filter.mount) {
      const pattern = filter.mount;
      if (typeof pattern === 'string') {
        if (!mount.includes(pattern)) return false;
      } else if (pattern instanceof RegExp) {
        if (!pattern.test(mount)) return false;
      }
    }

    // Filter by class name
    if (filter.className) {
      const pattern = filter.className;
      if (typeof pattern === 'string') {
        if (descriptor.className !== pattern) return false;
      } else if (pattern instanceof RegExp) {
        if (!pattern.test(descriptor.className)) return false;
      }
    }

    // Filter by accepted command
    if (filter.accepts) {
      if (!descriptor.accepts.includes(filter.accepts)) return false;
    }

    // Filter by emitted event
    if (filter.emits) {
      if (!descriptor.emits.includes(filter.emits)) return false;
    }

    return true;
  }

  /**
   * Handle registry query request.
   */
  private handleQuery(filter: IActorFilter): void {
    const results = this.query(filter);
    this.transport.publish('$registry.query.response', results);
  }

  /**
   * Handle registry list request.
   */
  private handleList(): void {
    const results = this.list();
    this.transport.publish('$registry.list.response', results);
  }
}
