/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { AsyncObservable, Diagnostic, InternalError, Transaction } from "#general";
import { AccessControl, FabricAccessControl, SecureSession } from "#protocol";
import { FabricIndex } from "#types";
import { Contextual } from "../Contextual.js";
const exchangeCompleteEvents = /* @__PURE__ */ new WeakMap();
function RemoteActorContext(options) {
  let nodeProtocol;
  let accessLevelCache;
  const { exchange, message } = options;
  const session = exchange.session;
  SecureSession.assert(session);
  const fabric = session.fabric;
  const subject = session.subjectFor(message);
  const accessControl = fabric?.accessControl ?? new FabricAccessControl();
  const via = Diagnostic.via(
    `online#${message?.packetHeader?.messageId?.toString(16) ?? "?"}@${subject.id.toString(16)}`
  );
  return {
    /**
     * Operate on behalf of a remote actor.
     *
     * If the actor changes state, this may return a promise even if {@link actor} does not return a promise.
     */
    act(actor) {
      const context = this.open();
      let result;
      try {
        result = actor(context);
      } catch (e) {
        return context.reject(e);
      }
      return context.resolve(result);
    },
    /**
     * Create an online context.
     *
     * This context operates with a {@link Transaction} created via {@link Transaction.open} and the same rules
     * apply for lifecycle management using {@link Transaction.Finalization}.
     */
    open() {
      let close;
      let tx;
      try {
        close = initialize();
        tx = Transaction.open(via);
        tx.onClose(close);
      } catch (e) {
        close?.();
        throw e;
      }
      return createContext(tx, {
        resolve: tx.resolve.bind(tx),
        reject: tx.reject.bind(tx)
      });
    },
    /**
     * Begin an operation with a read-only context.
     *
     * A read-only context offers simpler lifecycle semantics than a r/w OnlineContext but you must still close the
     * context after use to properly deregister activity.
     */
    beginReadOnly() {
      const close = initialize();
      return createContext(Transaction.open(via, "snapshot"), {
        [Symbol.dispose]: close
      });
    },
    [Symbol.toStringTag]: "OnlineContext"
  };
  function initialize() {
    const activity = options.activity?.frame(via);
    const close = () => {
      if (message) {
        Contextual.setContextOf(message, void 0);
      }
      if (activity) {
        activity[Symbol.dispose]();
      }
    };
    return close;
  }
  function createContext(transaction, methods) {
    if (session) {
      SecureSession.assert(session);
    }
    let interactionComplete;
    if (exchange !== void 0) {
      interactionComplete = exchangeCompleteEvents.get(exchange);
      if (interactionComplete === void 0) {
        interactionComplete = new AsyncObservable();
        exchangeCompleteEvents.set(exchange, interactionComplete);
      }
      const notifyInteractionComplete = () => {
        exchange.closing.off(notifyInteractionComplete);
        exchangeCompleteEvents.delete(exchange);
        if (context.interactionComplete?.isObserved) {
          context.interactionComplete.emit(context);
        }
      };
      exchange.closing.on(notifyInteractionComplete);
    }
    const context = {
      ...options,
      session,
      exchange,
      subject,
      largeMessage: exchange?.channel.supportsLargeMessages,
      offline: false,
      fabric: fabric?.fabricIndex ?? FabricIndex.NO_FABRIC,
      transaction,
      interactionComplete,
      ...methods,
      // TODO - Matter 1.4 - add support for ARLs
      authorityAt(desiredAccessLevel, location) {
        if (location === void 0) {
          throw new InternalError("AccessControl.Location is required");
        }
        const cachedAccessLevels = accessLevelCache?.get(location);
        if (cachedAccessLevels !== void 0) {
          return cachedAccessLevels.includes(desiredAccessLevel) ? AccessControl.Authority.Granted : AccessControl.Authority.Unauthorized;
        }
        if (options.node === void 0) {
          throw new InternalError("OnlineContext initialized without node");
        }
        const accessLevels = accessControl.accessLevelsFor(context, location, aclEndpointContextFor(location));
        if (accessLevelCache === void 0) {
          accessLevelCache = /* @__PURE__ */ new Map();
        }
        accessLevelCache.set(location, accessLevels);
        return accessLevels.includes(desiredAccessLevel) ? AccessControl.Authority.Granted : AccessControl.Authority.Unauthorized;
      },
      get [Contextual.context]() {
        return this;
      }
    };
    if (message) {
      Contextual.setContextOf(message, context);
    }
    return context;
  }
  function aclEndpointContextFor({ endpoint: number }) {
    if (number === void 0) {
      throw new InternalError("Online location missing required endpoint number");
    }
    if (options.node === void 0) {
      throw new InternalError("Online context has no node defined");
    }
    if (nodeProtocol === void 0) {
      nodeProtocol = options.node.protocol;
    }
    const endpoint = nodeProtocol[number];
    if (endpoint !== void 0) {
      return endpoint;
    }
    return {
      id: number,
      deviceTypes: []
    };
  }
}
export {
  RemoteActorContext
};
//# sourceMappingURL=RemoteActorContext.js.map
