// src/transport/MultiplexingTransport.ts
//
// VFS-style mount table transport — routes messages to the correct transport
// adapter by longest-prefix match on the mount path extracted from topics.
// Implements ITransportAdapter as a drop-in replacement for any single transport.
//
// See: ARCHITECTURE/ARCHITECTURE-KERNEL.md §4.2-4.6

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

interface MountEntry {
  readonly prefix: string;
  readonly transport: ITransportAdapter;
}

interface TrackedSubscription {
  readonly topic: string;
  readonly transport: ITransportAdapter;
  readonly unsubscribe: () => void;
}

/**
 * Extract the actor mount path from a semantic topic string.
 * Semantic topics follow the pattern: mount.path/$inbox, mount.path/$events, etc.
 * We strip the /$facet suffix to get the mount path for prefix matching.
 */
function extractMountFromTopic(topic: string): string {
  const dollarIdx = topic.indexOf('/$');
  return dollarIdx >= 0 ? topic.slice(0, dollarIdx) : topic;
}

export class MultiplexingTransport implements ITransportAdapter {
  readonly root: string;

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

  /** Sorted by prefix length descending for longest-prefix-first matching. */
  private _mounts: MountEntry[] = [];
  private readonly _default: ITransportAdapter;
  private readonly _subscriptions = new Map<string, TrackedSubscription>();
  private _subCounter = 0;

  constructor(defaultTransport: ITransportAdapter, mounts?: ReadonlyArray<{ prefix: string; transport: ITransportAdapter }>) {
    this._default = defaultTransport;
    this.root = defaultTransport.root;

    if (mounts) {
      for (const m of mounts) {
        this._mounts.push({ prefix: m.prefix, transport: m.transport });
      }
      this._sortMounts();
    }
  }

  /**
   * Resolve which transport handles a given topic.
   * Longest prefix match — same algorithm as Linux VFS path lookup.
   */
  resolve(topic: string): ITransportAdapter {
    const mount = extractMountFromTopic(topic);
    for (const entry of this._mounts) {
      if (mount === entry.prefix || mount.startsWith(entry.prefix + '.')) {
        return entry.transport;
      }
    }
    return this._default;
  }

  // ── ITransportAdapter implementation ────────────────────────────────────

  publish(topic: string, payload?: unknown): void {
    this.resolve(topic).publish(topic, payload);
  }

  subscribe(topic: string, handler: TransportHandler): () => void {
    const transport = this.resolve(topic);
    const unsub = transport.subscribe(topic, handler);
    const id = `sub-${++this._subCounter}`;
    this._subscriptions.set(id, { topic, transport, unsubscribe: unsub });
    return () => {
      const tracked = this._subscriptions.get(id);
      if (tracked) {
        tracked.unsubscribe();
        this._subscriptions.delete(id);
      }
    };
  }

  getWireTopicInfo?(topic: string): IWireTopicInfo {
    const transport = this.resolve(topic);
    if (transport.getWireTopicInfo) {
      return transport.getWireTopicInfo(topic);
    }
    return { appTopic: topic, wireTopic: topic, prefix: '' };
  }

  get topicPrefix(): string | undefined {
    return this._default.topicPrefix;
  }

  getUnderlyingClient(): unknown {
    return this._default.getUnderlyingClient();
  }

  async flush(): Promise<void> {
    const transports = new Set<ITransportAdapter>([this._default]);
    for (const entry of this._mounts) {
      transports.add(entry.transport);
    }
    await Promise.all(Array.from(transports).map((transport) => transport.flush?.()));
  }

  async disconnect(): Promise<void> {
    // Unsubscribe all tracked subscriptions
    for (const [id, tracked] of this._subscriptions) {
      tracked.unsubscribe();
      this._subscriptions.delete(id);
    }
    // Disconnect all mounted transports
    const transports = new Set<ITransportAdapter>([this._default]);
    for (const entry of this._mounts) {
      transports.add(entry.transport);
    }
    await Promise.all(Array.from(transports).map((t) => t.disconnect()));
  }

  // ── Mount table management ──────────────────────────────────────────────

  addMount(prefix: string, transport: ITransportAdapter): void {
    // Remove existing mount at same prefix if any
    this._mounts = this._mounts.filter((m) => m.prefix !== prefix);
    this._mounts.push({ prefix, transport });
    this._sortMounts();
  }

  removeMount(prefix: string): void {
    // Check for active subscriptions on this prefix
    for (const [, tracked] of this._subscriptions) {
      const mount = extractMountFromTopic(tracked.topic);
      if (mount === prefix || mount.startsWith(prefix + '.')) {
        throw new Error(`EBUSY: cannot unmount "${prefix}" — active subscriptions exist`);
      }
    }
    this._mounts = this._mounts.filter((m) => m.prefix !== prefix);
  }

  listMounts(): Array<{ prefix: string; root: string; realm?: string }> {
    return [
      { prefix: '(default)', root: this._default.root, realm: this._default.root },
      ...this._mounts.map((m) => ({ prefix: m.prefix, root: m.transport.root, realm: m.transport.root })),
    ];
  }

  private _sortMounts(): void {
    // Sort by prefix length descending for longest-prefix-first matching
    this._mounts.sort((a, b) => b.prefix.length - a.prefix.length);
  }
}

export { extractMountFromTopic };
