// src/engine/remoting/RequestReply.ts

import type { IMatrixContext } from '../core/IMatrixContext';
import { TopicRouter } from '../messaging/TopicRouter';
import type { IMethodSpec } from '../reflection/IInterfaceDescriptor';
import type { ITransportAdapter } from './ITransportAdapter';
import { RUNTIME_ADDRESS_CONTEXT, type RuntimeAddressContext } from './RuntimeAddressContext';
import {
  startSpan,
  getActiveTraceContext,
  type TraceContext
} from '../../telemetry/tracing';

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

interface INatsMessageLike {
  readonly subject: string;
  readonly data: Uint8Array;
  readonly reply?: string;
}

interface INatsRequestOptionsLike {
  readonly timeout?: number;
}

interface INatsSubscriptionLike {
  unsubscribe?(): void;
}

interface INatsClientLike {
  publish(subject: string, data?: Uint8Array): void;
  request?(
    subject: string,
    data?: Uint8Array,
    options?: INatsRequestOptionsLike,
  ): Promise<INatsMessageLike>;
  subscribe(
    subject: string,
    options: {
      callback: (err: Error | null, msg: INatsMessageLike) => void;
    },
  ): INatsSubscriptionLike;
  flush?(): Promise<void>;
}

interface IFederationInvokeClientLike {
  invoke<T = unknown>(
    targetRoot: string,
    toPath: string,
    body: unknown,
    timeoutMs?: number,
  ): Promise<T>;
}

function isNatsClientLike(value: unknown): value is INatsClientLike {
  if (!value || typeof value !== 'object') return false;
  const candidate = value as { publish?: unknown; subscribe?: unknown };
  return (
    typeof candidate.publish === 'function' &&
    typeof candidate.subscribe === 'function'
  );
}

function isFederationInvokeClientLike(value: unknown): value is IFederationInvokeClientLike {
  if (!value || typeof value !== 'object') return false;
  const candidate = value as { invoke?: unknown };
  return typeof candidate.invoke === 'function';
}

function getNatsClient(transport: ITransportAdapter): INatsClientLike | null {
  const rawClient = transport.getUnderlyingClient();
  return isNatsClientLike(rawClient) ? rawClient : null;
}

function getFederationInvokeClient(transport: ITransportAdapter): IFederationInvokeClientLike | null {
  const rawClient = transport.getUnderlyingClient();
  return isFederationInvokeClientLike(rawClient) ? rawClient : null;
}

function encodeNatsPayload(payload: unknown): Uint8Array {
  if (payload === undefined) {
    return new Uint8Array();
  }
  if (typeof payload === 'string') {
    return textEncoder.encode(payload);
  }
  return textEncoder.encode(JSON.stringify(payload));
}

function decodeNatsPayload(data: Uint8Array): unknown {
  if (data.length === 0) {
    return undefined;
  }
  const raw = textDecoder.decode(data);
  if (raw.length === 0) {
    return undefined;
  }
  try {
    return JSON.parse(raw);
  } catch {
    return raw;
  }
}

function publishReplyPayload(
  transport: ITransportAdapter,
  topic: string,
  payload: Record<string, unknown>,
): void {
  if (topic.includes('.$reply.') || topic.startsWith('_INBOX.') || topic.startsWith('_R_.')) {
    const natsClient = getNatsClient(transport);
    if (natsClient) {
      natsClient.publish(topic, encodeNatsPayload(payload));
      return;
    }
  }
  transport.publish(topic, payload);
}

function randomInboxToken(): string {
  const cryptoLike = globalThis.crypto as { randomUUID?: () => string; getRandomValues: (array: Uint8Array) => Uint8Array };
  if (typeof cryptoLike?.randomUUID === 'function') {
    return cryptoLike.randomUUID().replace(/-/g, '');
  }
  const byteArray = new Uint8Array(16);
  cryptoLike.getRandomValues(byteArray);
  return [...byteArray].map((byte) => byte.toString(16).padStart(2, '0')).join('');
}

function createRawReplyInbox(): string {
  return `_INBOX.${randomInboxToken()}`;
}

function normalizePingReply(payload: unknown, sourceRoot?: string): Record<string, unknown> {
  const normalized = payload && typeof payload === 'object' && !Array.isArray(payload)
    ? { ...(payload as Record<string, unknown>) }
    : {};
  normalized.op = '$pong';
  if (sourceRoot && sourceRoot.length > 0) {
    normalized.sourceRoot = sourceRoot;
  }
  return normalized;
}

export interface IRrRequestOptions {
  timeoutMs?: number;
  signal?: AbortSignal;
  IMethodSpec?: IMethodSpec;
  /**
   * Explicit rooted target for cross-root routing.
   * When set, `targetMount` is treated as the mount/path portion only.
   */
  targetRoot?: string;
  /**
   * Use native NATS request/reply even when targetRoot equals the transport
   * root. Required for same-authority service-import boundaries where the
   * responder is reached through NATS account routing and must reply via the
   * broker-owned reply subject.
   */
  protocolRequest?: boolean;
}

type RequestReplyError = Error & {
  code?: unknown;
  correlationId?: string;
  requestId?: string;
  traceId?: string;
  targetMount?: string;
  op?: string;
  stage?: string;
  retryable?: boolean;
  errorData?: Record<string, unknown>;
};

function makeRequestReplyError(
  message: string,
  meta: Partial<RequestReplyError> = {},
): RequestReplyError {
  const error = new Error(message) as RequestReplyError;
  Object.assign(error, meta);
  return error;
}

/**
 * Canonical transport‑based Request/Reply implementation.
 *
 * All RR traffic (local or remote, every scheme) goes through this type.
 *
 * Cross-root requests (where target root differs from transport's root)
 * are routed through native NATS subjects.
 */
// Timing instrumentation (enable via env or global)
let _enableTimingLogs = typeof process !== 'undefined' && process.env?.RR_TIMING === '1';
export function setRRTimingLogs(enable: boolean) { _enableTimingLogs = enable; }

const MAX_TIMER_DELAY_MS = 2_147_483_647;

function scheduleRequestReplyTimeout(timeoutMs: number, onTimeout: () => void): () => void {
  let timer: ReturnType<typeof setTimeout> | undefined;
  let cancelled = false;
  const deadline = Date.now() + timeoutMs;

  const schedule = (): void => {
    if (cancelled) {
      return;
    }
    const remainingMs = deadline - Date.now();
    if (remainingMs <= 0) {
      onTimeout();
      return;
    }
    timer = setTimeout(schedule, Math.min(remainingMs, MAX_TIMER_DELAY_MS));
  };

  schedule();
  return () => {
    cancelled = true;
    if (timer) {
      clearTimeout(timer);
    }
  };
}

function writeRRTimingDiagnostic(message: string): void {
  if (typeof process !== 'undefined' && process.stderr) {
    process.stderr.write(`${message}\n`);
  }
}

function isHostedAuthoritySingletonRead(targetPath: string, op: string): boolean {
  if (targetPath === 'system.registry') {
    return op === 'registry.query'
      || op === 'registry.list'
      || op === 'registry.resolve'
      || op === 'registry.status';
  }
  if (targetPath === 'system.devices') {
    return op === 'devices.list'
      || op === 'devices.get'
      || op === 'devices.runtimes.list';
  }
  if (targetPath === 'system.runtimes') {
    return op === 'runtimes.list'
      || op === 'runtimes.get'
      || op === 'runtimes.registered';
  }
  return false;
}

export class RequestReply {
  private static _resolveMountAlias(
    context: IMatrixContext,
    targetMount: string,
    opts: Pick<IRrRequestOptions, 'targetRoot'> = {},
  ): string {
    const getService = (context as { getService?: <T>(key: string) => T | null | undefined }).getService;
    if (typeof getService !== 'function') return targetMount;

    const resolver = getService.call(context, 'mount-resolver') as ((mount: string) => string) | null | undefined;
    if (!resolver) return targetMount;

    if (opts.targetRoot) {
      return resolver(targetMount);
    }

    const slashIdx = targetMount.indexOf('/');
    if (slashIdx <= 0) {
      return resolver(targetMount);
    }

    const targetRoot = targetMount.slice(0, slashIdx);
    const targetPath = targetMount.slice(slashIdx + 1);
    return `${targetRoot}/${resolver(targetPath)}`;
  }

  private static _splitTarget(
    targetMount: string,
    opts: Pick<IRrRequestOptions, 'targetRoot'> = {},
  ): { targetRoot?: string; targetPath: string } {
    if (opts.targetRoot) {
      return { targetRoot: opts.targetRoot, targetPath: targetMount };
    }

    const slashIdx = targetMount.indexOf('/');
    if (slashIdx > 0) {
      return {
        targetRoot: targetMount.slice(0, slashIdx),
        targetPath: targetMount.slice(slashIdx + 1),
      };
    }

    return { targetPath: targetMount };
  }

  private static _rootedInboxSubject(targetRoot: string, targetPath: string): string {
    return targetPath ? `${targetRoot}.${targetPath}.$inbox` : `${targetRoot}.$inbox`;
  }

  private static _assertBrowserDurableSystemTargetRoot(
    context: IMatrixContext,
    targetMount: string,
    opts: Pick<IRrRequestOptions, 'targetRoot'> = {},
  ): void {
    if (opts.targetRoot || targetMount.includes('/') || !targetMount.startsWith('system.')) {
      return;
    }
    if (targetMount.startsWith('system.runtimes.RUNTIME-BROWSER-')) {
      return;
    }
    const getService = (context as { getService?: <T>(key: symbol) => T | null | undefined }).getService;
    if (typeof getService !== 'function') {
      return;
    }
    const address = getService.call(context, RUNTIME_ADDRESS_CONTEXT) as RuntimeAddressContext | null | undefined;
    if (!address?.runtimeInstanceRoot) {
      return;
    }
    const transportRoot = context.transport.root || context.transport.realm || '';
    if (transportRoot !== address.runtimeInstanceRoot) {
      return;
    }
    throw makeRequestReplyError(
      `Browser runtime durable system service call '${targetMount}' requires opts.targetRoot`,
      {
        code: 'MISSING_TARGET_ROOT_FOR_DURABLE_SYSTEM_SERVICE',
        targetMount,
        stage: 'request-reply',
        retryable: false,
      },
    );
  }

  static async execute<TReq, TRes>(
    context: IMatrixContext,
    targetMount: string,
    op: string,
    params: TReq,
    opts: IRrRequestOptions = {}
  ): Promise<TRes> {
    return RequestReply._executeInner<TReq, TRes>(context, targetMount, op, params, opts);
  }

  private static _executeInner<TReq, TRes>(
    context: IMatrixContext,
    targetMount: string,
    op: string,
    params: TReq,
    opts: IRrRequestOptions = {}
  ): Promise<TRes> {
    const transport = context.transport;
    const timeoutMs = opts.timeoutMs ?? 5000;
    const startTime = _enableTimingLogs ? performance.now() : 0;

    // RESOLVE RELATIVE PATHS (e.g., "./db" → "parent.db")
    // Relative paths start with "./" and should be resolved relative to context.mount
    let resolvedMount = targetMount;
    if (targetMount.startsWith('./')) {
      const relativePath = targetMount.slice(2); // Remove "./"
      const contextMount = context.mount;
      // Context mount is clean dot-delimited path (no scheme, no root)
      // "./db" from mount "app.services" → "app.db" (sibling resolution)
      // "./db" from root mount "" → "db" (root-level sibling)
      if (contextMount) {
        // If the relative path IS an ancestor of the current context (or the
        // context itself), treat it as an absolute address. This prevents
        // double-mount bugs like "flowpad-page.flowpad-page" when addressing
        // the parent from a derived child context (e.g., flowpad-page._runner).
        if (contextMount === relativePath || contextMount.startsWith(relativePath + '.')) {
          resolvedMount = relativePath;
        } else {
          const parentParts = contextMount.split('.');
          parentParts.pop(); // Remove last segment to get parent
          const parent = parentParts.join('.');
          resolvedMount = parent ? `${parent}.${relativePath}` : relativePath;
        }
      } else {
        // Root context: ./X resolves to just X
        resolvedMount = relativePath;
      }
    }
    targetMount = RequestReply._resolveMountAlias(context, resolvedMount, opts);
    RequestReply._assertBrowserDurableSystemTargetRoot(context, targetMount, opts);

    // Start tracing span for this RPC call
    const span = startSpan(`rpc.client ${targetMount}.${op}`);
    span.setAttribute('rpc.service', targetMount);
    span.setAttribute('rpc.method', op);

    // Request channel name
    const acceptsSuffix = opts.IMethodSpec?.acceptsChannelSuffix;
    const acceptsName =
      typeof acceptsSuffix === 'string' ? `${op}${acceptsSuffix}` : op;

    // CROSS-ROOT FEDERATION DETECTION
    // Parse target URI to check if it's a cross-root request
    // Cross-root requests MUST go through the mailbox (per ARCHITECTURE-NOTES.md)
    //
    // Extract root from URIs like:
    //   principal@root/path  → root
    //   root/path            → root
    const transportRoot = transport.root || undefined;
    const { targetRoot, targetPath } = RequestReply._splitTarget(targetMount, opts);

    const isCrossRoot = !!(targetRoot && transportRoot && targetRoot !== transportRoot);
    const useProtocolRequest = !!(
      targetRoot
      && transportRoot
      && (
        isCrossRoot
        || opts.protocolRequest === true
        || isHostedAuthoritySingletonRead(targetPath, acceptsName)
      )
    );

    // CROSS-ROOT: use NATS protocol replies so service-import responses return
    // through the broker-owned reply path instead of a payload-level root topic.
    if (useProtocolRequest && targetRoot) {
      const natsClient = getNatsClient(transport);
      const federationClient = natsClient ? null : getFederationInvokeClient(transport);

      if (natsClient) {
        return (async () => {
        try {
          let result: TRes;
          {
            const correlationId = randomInboxToken();
            // DNS hierarchical wire format: {root}.{mount}.$inbox
            const reqSubject = RequestReply._rootedInboxSubject(targetRoot, targetPath);

            const paramsObj = params as Record<string, unknown> | undefined;
            const capability = paramsObj?.capability;
            const restParams = capability
              ? Object.fromEntries(
                  Object.entries(paramsObj ?? {}).filter(([k]) => k !== 'capability')
                )
              : params;

            const envelope: Record<string, unknown> = {
              op: acceptsName,
              payload: restParams,
              correlationId,
              ...(capability ? { capability } : {}),
            };
            if (span.traceId) {
              envelope.traceId = span.traceId;
              envelope.spanId = span.spanId;
              if (span.parentSpanId) envelope.parentSpanId = span.parentSpanId;
            }

            result = await new Promise<TRes>((resolve, reject) => {
              let settled = false;
              let onAbort: (() => void) | undefined;
              let replySubscription: INatsSubscriptionLike | null = null;
              let cancelTimer = (): void => {};

              const settle = (err?: Error, responsePayload?: unknown): void => {
                if (settled) return;
                settled = true;
                cancelTimer();
                if (replySubscription) {
                  replySubscription.unsubscribe?.();
                  replySubscription = null;
                }
                if (opts.signal && onAbort) {
                  opts.signal.removeEventListener('abort', onAbort);
                }

                if (err) {
                  reject(err);
                  return;
                }

                const payloadObj = responsePayload as Record<string, unknown> | undefined;
                if (payloadObj?.ok === false) {
                  const errorObj = payloadObj.error as Record<string, unknown> | undefined;
                  const message = (errorObj?.message as string) ?? `RR error for ${op}`;
                  const e = makeRequestReplyError(message, {
                    code: errorObj?.code,
                    correlationId,
                    requestId: correlationId,
                    traceId: span.traceId,
                    targetMount,
                    op,
                    stage: typeof errorObj?.stage === 'string' ? errorObj.stage : undefined,
                    retryable: typeof errorObj?.retryable === 'boolean' ? errorObj.retryable : undefined,
                    errorData: errorObj,
                  });
                  reject(e);
                  return;
                }

                const resolvedResult = payloadObj?.ok ? payloadObj.result : responsePayload;
                if (acceptsName === '$ping') {
                  const sourceRoot = typeof payloadObj?.sourceRoot === 'string'
                    ? payloadObj.sourceRoot
                    : targetRoot;
                  resolve(normalizePingReply(resolvedResult, sourceRoot) as TRes);
                  return;
                }
                resolve(resolvedResult as TRes);
              };

              const handleProtocolResponse = (decoded: unknown): void => {
                const payloadObj = decoded as Record<string, unknown> | undefined;
                if (payloadObj?.correlationId && payloadObj.correlationId !== correlationId) {
                  settle(makeRequestReplyError(
                    `RR correlation mismatch for '${op}' at ${targetMount}`,
                    {
                      code: 'RR_CORRELATION_MISMATCH',
                      correlationId,
                      requestId: correlationId,
                      traceId: span.traceId,
                      targetMount,
                      op,
                      stage: 'request-reply',
                      retryable: false,
                      errorData: payloadObj,
                    },
                  ));
                  return;
                }
                settle(undefined, decoded);
              };

              const handleRequestError = (err: unknown): void => {
                const message = err instanceof Error ? err.message : String(err);
	                if (message.toLowerCase().includes('timeout')) {
	                  const traceHint = span.traceId
	                    ? `Trace: ${span.traceId}\n`
	                    : '';
	                  settle(makeRequestReplyError(
	                    `RR timeout for '${op}' at ${targetMount} on subject '${reqSubject}' after ${timeoutMs}ms.\n` +
	                    `Possible causes:\n` +
	                    `  1. No component mounted at this path\n` +
	                    `  2. Component doesn't handle '${op}'\n` +
                    `  3. Component crashed before responding\n` +
                    traceHint +
                    `Check: matrix status OR run with MATRIX_TRACE=1`,
                    {
                      code: 'RR_TIMEOUT',
                      correlationId,
                      requestId: correlationId,
	                      traceId: span.traceId,
	                      targetMount,
	                      op,
	                      stage: 'request-reply',
	                      retryable: true,
	                      errorData: { subject: reqSubject },
	                    },
	                  ));
	                  return;
	                }
	                settle(makeRequestReplyError(
	                  `RR request failed for '${op}' at ${targetMount} on subject '${reqSubject}': ${message}`,
	                  {
	                    code: 'RR_REQUEST_FAILED',
	                    correlationId,
                    requestId: correlationId,
                    traceId: span.traceId,
	                    targetMount,
	                    op,
	                    stage: 'request-reply',
	                    retryable: true,
	                    errorData: { subject: reqSubject },
	                  },
	                ));
              };

              if (typeof natsClient.request === 'function') {
                void natsClient.request(reqSubject, encodeNatsPayload(envelope), { timeout: Math.min(timeoutMs, MAX_TIMER_DELAY_MS) })
                  .then((msg) => {
                    handleProtocolResponse(decodeNatsPayload(msg.data));
                  })
                  .catch(handleRequestError);
              } else {
                const replySubject = createRawReplyInbox();
                replySubscription = natsClient.subscribe(replySubject, {
                  callback: (err: Error | null, msg: INatsMessageLike) => {
                    if (err) {
                      handleRequestError(err);
                      return;
                    }
                    handleProtocolResponse(decodeNatsPayload(msg.data));
                  },
                });
                natsClient.publish(reqSubject, encodeNatsPayload({
                  ...envelope,
                  replyTo: replySubject,
                }));
                void natsClient.flush?.().catch(handleRequestError);
              }

              cancelTimer = scheduleRequestReplyTimeout(timeoutMs, () => {
                const traceHint = span.traceId
                  ? `Trace: ${span.traceId}\n`
                  : '';
                settle(makeRequestReplyError(
                  `RR timeout for '${op}' at ${targetMount} after ${timeoutMs}ms.\n` +
                  `Possible causes:\n` +
                  `  1. No component mounted at this path\n` +
                  `  2. Component doesn't handle '${op}'\n` +
                  `  3. Component crashed before responding\n` +
                  traceHint +
                  `Check: matrix status OR run with MATRIX_TRACE=1`,
                  {
                    code: 'RR_TIMEOUT',
                    correlationId,
                    requestId: correlationId,
                    traceId: span.traceId,
                    targetMount,
                    op,
                    stage: 'request-reply',
                    retryable: true,
                  },
                ));
              });

              if (opts.signal) {
                if (opts.signal.aborted) {
                  settle(makeRequestReplyError(`RR aborted for ${op} at ${targetMount}`, {
                    code: 'RR_ABORTED',
                    correlationId,
                    requestId: correlationId,
                    traceId: span.traceId,
                    targetMount,
                    op,
                    stage: 'request-reply',
                    retryable: false,
                  }));
                  return;
                }
                onAbort = () => {
                  settle(makeRequestReplyError(`RR aborted for ${op} at ${targetMount}`, {
                    code: 'RR_ABORTED',
                    correlationId,
                    requestId: correlationId,
                    traceId: span.traceId,
                    targetMount,
                    op,
                    stage: 'request-reply',
                    retryable: false,
                  }));
                };
                opts.signal.addEventListener('abort', onAbort, { once: true });
              }
            });
          }

          if (_enableTimingLogs) {
            const elapsed = performance.now() - startTime;
            writeRRTimingDiagnostic(`[RR-TIMING] ${targetMount}.${op}: cross-root total=${elapsed.toFixed(2)}ms`);
          }

          span.setStatus('ok');
          span.end();
          return result;
        } catch (err) {
          const errMsg = err instanceof Error ? err.message : String(err);
          if (_enableTimingLogs) {
            const elapsed = performance.now() - startTime;
            writeRRTimingDiagnostic(`[RR-TIMING] ${targetMount}.${op}: cross-root error after ${elapsed.toFixed(2)}ms`);
          }
          span.setStatus('error', errMsg);
          span.end();
          throw err;
        }
      })();
      } // end if (natsClient)
      if (federationClient) {
        return (async () => {
          const correlationId = randomInboxToken();
          try {
            if (opts.signal?.aborted) {
              throw makeRequestReplyError(`RR aborted for ${op} at ${targetMount}`, {
                code: 'RR_ABORTED',
                correlationId,
                requestId: correlationId,
                traceId: span.traceId,
                targetMount,
                op,
                stage: 'request-reply',
                retryable: false,
              });
            }

            const paramsObj = params as Record<string, unknown> | undefined;
            const capability = paramsObj?.capability;
            const restParams = capability
              ? Object.fromEntries(
                  Object.entries(paramsObj ?? {}).filter(([k]) => k !== 'capability')
                )
              : params;
            const acceptsPath = `${targetPath}.accepts.${acceptsName}`;
            const result = await federationClient.invoke<TRes>(
              targetRoot,
              acceptsPath,
              restParams,
              timeoutMs,
            );

            if (_enableTimingLogs) {
              const elapsed = performance.now() - startTime;
              writeRRTimingDiagnostic(`[RR-TIMING] ${targetMount}.${op}: federation total=${elapsed.toFixed(2)}ms`);
            }

            span.setStatus('ok');
            span.end();
            return result;
          } catch (err) {
            const errMsg = err instanceof Error ? err.message : String(err);
            if (_enableTimingLogs) {
              const elapsed = performance.now() - startTime;
              writeRRTimingDiagnostic(`[RR-TIMING] ${targetMount}.${op}: federation error after ${elapsed.toFixed(2)}ms`);
            }
            span.setStatus('error', errMsg);
            span.end();
            throw err;
          }
        })();
      }
      if (opts.protocolRequest === true) {
        throw makeRequestReplyError(
          `Protocol RR requires NATS request support for '${op}' at ${targetMount}`,
          {
            code: 'RR_NATS_REQUEST_UNAVAILABLE',
            targetMount,
            op,
            stage: 'request-reply',
            retryable: false,
          },
        );
      }
      // else: no NATS client for cross-root — fall through to local transport path below
    }

    // LOCAL: Use $inbox publish with MxEnvelope format
    // Use targetPath (root stripped) for local routing — same-root cross-root
    // addresses like "FLOWPAD-ROOT/flowpad-page" must resolve to just "flowpad-page"
    const reqTopic = TopicRouter.inbox(targetPath);

    // Generate correlation ID and unique reply topic
    const correlationId = randomInboxToken();

    // Local mode: use a root-scoped reply topic. The transport itself prefixes
    // topics with the current root, so adding principal@root here is both
    // unnecessary and incorrect for same-root RR.
    //
    // Important architecture note: this is still only the isolated-environment
    // answer. Shared-root multi-runtime operation still needs runtime-scoped
    // reply lanes at the transport/protocol layer; the runner does not solve
    // that by itself.
    const primaryResTopic = `$replies.${correlationId}`;
    const replyToTopic = primaryResTopic;

    return new Promise<TRes>((resolve, reject) => {
      let settled = false;
      let subscribeTime = 0;
      let publishTime = 0;
      let cancelTimer = (): void => {};

      const settle = (err?: Error, payload?: unknown) => {
        if (settled) return;
        settled = true;
        const settleTime = _enableTimingLogs ? performance.now() : 0;

        cancelTimer();
        offPrimary();

        if (opts.signal && onAbort) {
          opts.signal.removeEventListener('abort', onAbort);
        }

        if (_enableTimingLogs) {
          const totalMs = settleTime - startTime;
          const subToPublishMs = publishTime - subscribeTime;
          const waitMs = settleTime - publishTime;
          writeRRTimingDiagnostic(`[RR-TIMING] ${targetMount}.${op}: total=${totalMs.toFixed(2)}ms (sub=${(subscribeTime-startTime).toFixed(2)}ms, pub=${subToPublishMs.toFixed(2)}ms, wait=${waitMs.toFixed(2)}ms)`);
        }

        if (err) {
          span.setStatus('error', err.message);
          span.end();
          reject(err);
          return;
        }

        const payloadObj = payload as Record<string, unknown> | undefined;
        if (payloadObj?.ok === false) {
          const errorObj = payloadObj.error as Record<string, unknown> | undefined;
          const message = (errorObj?.message as string) ?? `RR error for ${op}`;
          const e = makeRequestReplyError(message, {
            code: errorObj?.code,
            correlationId,
            requestId: correlationId,
            traceId: span.traceId,
            targetMount,
            op,
            stage: typeof errorObj?.stage === 'string' ? errorObj.stage : undefined,
            retryable: typeof errorObj?.retryable === 'boolean' ? errorObj.retryable : undefined,
            errorData: errorObj,
          });
          span.setStatus('error', e.message);
          span.end();
          reject(e);
          return;
        }

        const result = payloadObj?.ok ? payloadObj.result : payload;
        if (acceptsName === '$ping') {
          resolve(normalizePingReply(result, transportRoot) as TRes);
          return;
        }
        span.setStatus('ok');
        span.end();
        resolve(result as TRes);
      };

      const handleResponse = (payload: unknown) => {
        const payloadObj = payload as Record<string, unknown> | undefined;
        if (payloadObj?.correlationId && payloadObj.correlationId !== correlationId) {
          return;
        }
        settle(undefined, payload);
      };

      const offPrimary = transport.subscribe(primaryResTopic, handleResponse);
      subscribeTime = _enableTimingLogs ? performance.now() : 0;

      cancelTimer = scheduleRequestReplyTimeout(timeoutMs, () => {
        const traceHint = span.traceId
          ? `Trace: ${span.traceId}\n`
          : '';
        settle(makeRequestReplyError(
          `RR timeout for '${op}' at ${targetMount} after ${timeoutMs}ms.\n` +
          `Possible causes:\n` +
          `  1. No component mounted at this path\n` +
          `  2. Component doesn't handle '${op}'\n` +
          `  3. Component crashed before responding\n` +
          traceHint +
          `Check: matrix status OR run with MATRIX_TRACE=1`,
          {
            code: 'RR_TIMEOUT',
            correlationId,
            requestId: correlationId,
            traceId: span.traceId,
            targetMount,
            op,
            stage: 'request-reply',
            retryable: true,
          },
        ));
      });

      let onAbort: (() => void) | undefined;
      if (opts.signal) {
        if (opts.signal.aborted) {
          settle(makeRequestReplyError(`RR aborted for ${op} at ${targetMount}`, {
            code: 'RR_ABORTED',
            correlationId,
            requestId: correlationId,
            traceId: span.traceId,
            targetMount,
            op,
            stage: 'request-reply',
            retryable: false,
          }));
        } else {
          onAbort = () => {
            settle(makeRequestReplyError(`RR aborted for ${op} at ${targetMount}`, {
              code: 'RR_ABORTED',
              correlationId,
              requestId: correlationId,
              traceId: span.traceId,
              targetMount,
              op,
              stage: 'request-reply',
              retryable: false,
            }));
          };
          opts.signal.addEventListener('abort', onAbort, { once: true });
        }
      }

      // Vision 27: Extract capability from params and put at top level
      const paramsObj = params as Record<string, unknown> | undefined;
      const capability = paramsObj?.capability;
      const restParams = capability
        ? Object.fromEntries(
            Object.entries(paramsObj!).filter(([k]) => k !== 'capability')
          )
        : params;

      // Build MxEnvelope with first-class trace context fields
      const envelope: Record<string, unknown> = {
        op: acceptsName,
        payload: restParams,
        correlationId,
        replyTo: replyToTopic,
        ...(capability ? { capability } : {}),
      };
      // Set trace context as first-class envelope fields (not nested object)
      if (span.traceId) {
        envelope.traceId = span.traceId;
        envelope.spanId = span.spanId;
        if (span.parentSpanId) envelope.parentSpanId = span.parentSpanId;
      }
      transport.publish(reqTopic, envelope);
      publishTime = _enableTimingLogs ? performance.now() : 0;
    });
  }

  /**
   * ADR-022: Fire-and-forget send to a target's $inbox.
   * Handles both local and cross-root routing (same routing as execute()),
   * but does NOT wait for a reply. Used for event-driven prompt delivery.
   */
  static sendToInbox(
    context: IMatrixContext,
    targetMount: string,
    op: string,
    payload: Record<string, unknown>,
    opts: Pick<IRrRequestOptions, 'targetRoot'> = {},
  ): void {
    const transport = context.transport;

    // Resolve relative paths
    let resolvedMount = targetMount;
    if (targetMount.startsWith('./')) {
      const relativePath = targetMount.slice(2);
      const contextMount = context.mount;
      if (contextMount) {
        if (contextMount === relativePath || contextMount.startsWith(relativePath + '.')) {
          resolvedMount = relativePath;
        } else {
          const parentParts = contextMount.split('.');
          parentParts.pop();
          const parent = parentParts.join('.');
          resolvedMount = parent ? `${parent}.${relativePath}` : relativePath;
        }
      } else {
        resolvedMount = relativePath;
      }
    }
    targetMount = RequestReply._resolveMountAlias(context, resolvedMount, opts);

    // Cross-root detection
    const transportRoot = transport.root || undefined;
    const { targetRoot, targetPath } = RequestReply._splitTarget(targetMount, opts);

    const isCrossRoot = !!(targetRoot && transportRoot && targetRoot !== transportRoot);

    if (isCrossRoot && transportRoot && targetRoot) {
      const natsClient = getNatsClient(transport);
      if (!natsClient) {
        throw new Error(
          `Cross-root send requires NATS transport: ${transportRoot} -> ${targetRoot}`,
        );
      }

      // DNS hierarchical wire format: {root}.{mount}.$inbox
      const reqSubject = RequestReply._rootedInboxSubject(targetRoot, targetPath);
      const traceCtx = getActiveTraceContext();
      natsClient.publish(reqSubject, encodeNatsPayload({
        op,
        payload,
        from: context.mount,
        ...(traceCtx ? { traceId: traceCtx.traceId, spanId: traceCtx.spanId } : {}),
      }));
      return;
    }

    // Local: publish MxEnvelope to $inbox (no correlationId, no replyTo)
    const reqTopic = TopicRouter.inbox(targetPath);
    const traceCtx = getActiveTraceContext();
    transport.publish(reqTopic, {
      op,
      payload,
      from: context.mount,
      ...(traceCtx ? { traceId: traceCtx.traceId, spanId: traceCtx.spanId } : {}),
    });
  }

  /**
   * Bind an RR handler at a given mount + op.
   * Returns an unsubscribe function.
   */
  static bind<TReq, TRes>(
    context: IMatrixContext,
    mount: string,
    op: string,
    handler: (params: TReq) => Promise<TRes> | TRes,
    IMethodSpec?: IMethodSpec
  ): () => void {
    const transport = context.transport;

    const acceptsSuffix = IMethodSpec?.acceptsChannelSuffix;
    const acceptsName =
      typeof acceptsSuffix === 'string' ? `${op}${acceptsSuffix}` : op;

    const emitsSuffix = IMethodSpec?.emitsChannelSuffix;
    const primaryResultName =
      typeof emitsSuffix === 'string' ? `${op}${emitsSuffix}` : `${op}Result`;

    const reqTopic = TopicRouter.inbox(mount);

    return transport.subscribe(reqTopic, async payload => {
      // Filter by op — only handle envelopes matching this binding
      const envelope = payload as Record<string, unknown>;
      if (!envelope || envelope.op !== acceptsName) return;

      const correlationId = envelope.correlationId;
      const replyTo = envelope.replyTo as string | undefined;
      const params = envelope.payload;
      const resTopic: string = replyTo || `$replies.${correlationId || 'unknown'}`;
      const handlerStart = _enableTimingLogs ? performance.now() : 0;

      // Extract trace context from first-class envelope fields
      const parentTrace: TraceContext | undefined = envelope.traceId
        ? { traceId: envelope.traceId as string, spanId: envelope.spanId as string, parentSpanId: envelope.parentSpanId as string | undefined }
        : undefined;
      const span = startSpan(`rpc.server ${mount}.${op}`, parentTrace);
      span.setAttribute('rpc.service', mount);
      span.setAttribute('rpc.method', op);

      try {
        const result = await handler(params as TReq);
        if (_enableTimingLogs) {
          const handlerMs = performance.now() - handlerStart;
          writeRRTimingDiagnostic(`[RR-HANDLER] ${mount}.${op}: handler=${handlerMs.toFixed(2)}ms`);
        }
        span.setStatus('ok');
        span.end();
        publishReplyPayload(transport, resTopic, {
          ok: true,
          correlationId,
          sourceRoot: transport.root || undefined,
          result,
        });
      } catch (err: unknown) {
        if (_enableTimingLogs) {
          const handlerMs = performance.now() - handlerStart;
          writeRRTimingDiagnostic(`[RR-HANDLER] ${mount}.${op}: handler=${handlerMs.toFixed(2)}ms (ERROR)`);
        }
        const errMessage = err instanceof Error ? err.message : String(err);
        span.setStatus('error', errMessage);
        span.end();
        publishReplyPayload(transport, resTopic, {
          ok: false,
          correlationId,
          sourceRoot: transport.root || undefined,
          error: { message: errMessage },
        });
      }
    });
  }
}

// Convenience alias
export const requestReply = RequestReply.execute;
