/**
 * ServiceCascadeResolver — walks up the mount path hierarchy to find
 * the nearest `system.{service}` provider.
 *
 * With DNS hierarchical addressing, service resolution follows the mount tree:
 *   apps.chat → apps.system.llm → system.agents (root fallback for llm)
 *
 * Without an Actor Directory, uses NATS request with short timeout at each level.
 * First responder wins.
 */

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

export interface IServiceCascadeOptions {
  /** Timeout per level in milliseconds (default: 500) */
  readonly timeoutPerLevelMs?: number;
}

/**
 * Resolve a service by walking up the mount hierarchy.
 *
 * @param mount - Current actor's mount path (e.g., 'apps.chat.messages')
 * @param service - Service name to resolve (e.g., 'llm')
 * @param transport - Transport adapter for probing
 * @param options - Resolution options
 * @returns Resolved service mount path, or root fallback
 *
 * @example
 * // From mount 'apps.chat', looking for 'llm':
 * // Tries: apps.chat.system.llm → apps.system.llm → system.agents
 * const llmMount = await resolveService('apps.chat', 'llm', transport);
 */
export async function resolveService(
  mount: string,
  service: string,
  transport: ITransportAdapter,
  options?: IServiceCascadeOptions,
): Promise<string> {
  const timeoutMs = options?.timeoutPerLevelMs ?? 500;
  const segments = mount.split('.');
  const rootFallback = service === 'llm' ? 'system.agents' : `system.${service}`;

  // Walk up: try each ancestor level
  while (segments.length > 0) {
    const candidate = [...segments, 'system', service].join('.');
    const found = await probeService(candidate, transport, timeoutMs);
    if (found) return candidate;
    segments.pop();
  }

  // Root fallback: system.{service}
  return rootFallback;
}

/**
 * Probe whether a service exists at the given mount path.
 * Sends a $ping to the candidate's $inbox and waits for $pong.
 */
async function probeService(
  candidate: string,
  transport: ITransportAdapter,
  timeoutMs: number,
): Promise<boolean> {
  return new Promise<boolean>((resolve) => {
    const correlationId = `probe-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    let settled = false;

    const timer = setTimeout(() => {
      if (!settled) {
        settled = true;
        unsub();
        resolve(false);
      }
    }, timeoutMs);

    const unsub = transport.subscribe(`$replies.${correlationId}`, (payload) => {
      if (!settled) {
        settled = true;
        clearTimeout(timer);
        unsub();
        resolve(true);
      }
    });

    transport.publish(`${candidate}/$inbox`, {
      op: '$ping',
      correlationId,
      replyTo: `$replies.${correlationId}`,
    });
  });
}
