/**
 * BusIntentProvider — Resolves services by scanning mounted actors.
 *
 * Provider #2 in the intent chain. When ContextIntentProvider (cached)
 * misses, this provider sends $introspect to known system actors and
 * checks if any declares a matching contract interface.
 *
 * Results are cached on the context so subsequent lookups are instant.
 *
 * This is COM's Running Object Table — a registry of live objects
 * that can be queried by interface.
 */

import type { IIntentProvider, IActorService, ServiceContract } from './ServiceContract';
import type { IMatrixContext } from './IMatrixContext';
import { ActorServiceProxy } from './ActorServiceProxy';

interface IBusIntrospectResult {
  contract?: {
    interfaces?: Record<string, unknown>;
  };
  accepts?: string[];
}

export class BusIntentProvider implements IIntentProvider {
  /** Known system actor mounts to scan (populated at boot). */
  private _knownMounts: string[] = [];

  constructor(private _ctx: IMatrixContext) {}

  /** Register a mount path as scannable for intent resolution. */
  addKnownMount(mount: string): void {
    if (!this._knownMounts.includes(mount)) {
      this._knownMounts.push(mount);
    }
  }

  /** Register multiple mounts at once. */
  addKnownMounts(mounts: string[]): void {
    for (const m of mounts) this.addKnownMount(m);
  }

  async resolve(contract: ServiceContract): Promise<IActorService | undefined> {
    // Extract the interface name from the contract ops
    // e.g. IHealthService has ops 'health.status' → interface name 'health'
    const opNames = Object.keys(contract.ops);
    if (opNames.length === 0) return undefined;

    const firstOp = opNames[0];
    const dotIdx = firstOp.indexOf('.');
    const interfaceName = dotIdx > 0 ? firstOp.slice(0, dotIdx) : firstOp;

    // Scan known mounts — send $introspect and check for matching contract
    for (const mount of this._knownMounts) {
      try {
        const { RequestReply } = await import('../remoting/RequestReply');
        const result = await Promise.race<IBusIntrospectResult | null>([
          RequestReply.execute<Record<string, never>, IBusIntrospectResult>(this._ctx, mount, '$introspect', {}),
          new Promise<null>((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000)),
        ]);

        if (!result) continue;

        // Check if the actor's contract declares the interface we need
        if (result.contract?.interfaces?.[interfaceName]) {
          // Match! Create a proxy and cache it on context
          const proxy = new ActorServiceProxy(this._ctx, mount, contract);
          this._ctx.setService?.(contract.id, proxy);
          return proxy;
        }

        // Fallback: check if actor's accepts include the ops we need
        const actorAccepts = result.accepts as string[] | undefined;
        if (actorAccepts && opNames.every(op => actorAccepts.includes(op))) {
          const proxy = new ActorServiceProxy(this._ctx, mount, contract);
          this._ctx.setService?.(contract.id, proxy);
          return proxy;
        }
      } catch {
        // Actor unreachable or timeout — skip
        continue;
      }
    }

    return undefined;
  }
}
