/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { ImplementationError } from "#general";
import { Access, AccessLevel, ElementTag, ValueModel } from "#model";
import { Status } from "#types";
import { InvokeError, ReadError, SchemaImplementationError, WriteError } from "../errors.js";
const cache = /* @__PURE__ */ new WeakMap();
function hasRemoteActor(session) {
  return session?.subject !== void 0;
}
function assertRemoteActor(session) {
  if (!hasRemoteActor(session)) {
    throw new ImplementationError("This operation requires an authenticated remote session");
  }
}
function hasLocalActor(session) {
  return session?.subject === void 0;
}
function AccessControl(schema) {
  let enforcer = cache.get(schema);
  if (enforcer === void 0) {
    enforcer = enforcerFor(schema);
  }
  return enforcer;
}
((AccessControl2) => {
  let Authority;
  ((Authority2) => {
    Authority2[Authority2["Granted"] = 1] = "Granted";
    Authority2[Authority2["Unauthorized"] = 2] = "Unauthorized";
    Authority2[Authority2["Restricted"] = 3] = "Restricted";
  })(Authority = AccessControl2.Authority || (AccessControl2.Authority = {}));
})(AccessControl || (AccessControl = {}));
Object.freeze(AccessControl);
Object.freeze(AccessControl.Authority);
function enforcerFor(schema) {
  if (schema.tag === ElementTag.Command) {
    return commandEnforcerFor(schema);
  }
  return dataEnforcerFor(schema);
}
function dataEnforcerFor(schema) {
  const limits = limitsFor(schema);
  let mayRead = (session, location) => {
    if (hasLocalActor(session) || session.command) {
      return true;
    }
    return session.authorityAt(limits.readLevel, location) === 1 /* Granted */;
  };
  let mayWrite = (session, location) => {
    if (hasLocalActor(session) || session.command) {
      return true;
    }
    return session.authorityAt(limits.writeLevel, location) === 1 /* Granted */;
  };
  let authorizeRead = (session, location) => {
    if (hasLocalActor(session) || session.command) {
      return;
    }
    if (session.authorityAt(limits.readLevel, location) === 1 /* Granted */) {
      return;
    }
    throw new ReadError(location, "Permission denied", Status.UnsupportedAccess);
  };
  let authorizeWrite = (session, location) => {
    if (hasLocalActor(session) || session.command) {
      return;
    }
    if (session.authorityAt(limits.writeLevel, location) === 1 /* Granted */) {
      return;
    }
    throw new WriteError(location, "Permission denied", Status.UnsupportedAccess);
  };
  if (limits.timed) {
    const wrappedAuthorizeWrite = authorizeWrite;
    const wrappedMayWrite = mayWrite;
    authorizeWrite = (session, location) => {
      if (hasRemoteActor(session) && !session.timed) {
        throw new WriteError(
          location,
          "Permission denied because interaction is not timed",
          Status.NeedsTimedInteraction
        );
      }
      wrappedAuthorizeWrite?.(session, location);
    };
    mayWrite = (session, location) => {
      if (hasRemoteActor(session) && !session.timed) {
        return false;
      }
      return wrappedMayWrite(session, location);
    };
  }
  if (limits.fabricSensitive) {
    const wrappedAuthorizeRead = authorizeRead;
    const wrappedMayRead = mayRead;
    const wrappedAuthorizeWrite = authorizeWrite;
    const wrappedMayWrite = mayWrite;
    authorizeRead = (session, location) => {
      if (hasLocalActor(session) || session.command) {
        return;
      }
      if (session.fabricFiltered) {
        if (!session.fabric) {
          throw new ReadError(location, "Permission denied: No accessing fabric", Status.UnsupportedAccess);
        }
        if (location?.owningFabric !== void 0 && location.owningFabric !== session.fabric) {
          throw new ReadError(
            location,
            "Permission denied: Owning/accessing fabric mismatch",
            Status.UnsupportedAccess
          );
        }
      }
      wrappedAuthorizeRead(session, location);
    };
    mayRead = (session, location) => {
      if (hasLocalActor(session) || session.command) {
        return true;
      }
      if (!session.fabric) {
        return false;
      }
      if (location?.owningFabric !== void 0 && location.owningFabric !== session.fabric) {
        return false;
      }
      return wrappedMayRead(session, location);
    };
    authorizeWrite = (session, location) => {
      if (hasLocalActor(session) || session.command) {
        return;
      }
      if (!session.fabric) {
        throw new WriteError(location, "Permission denied: No accessing fabric", Status.UnsupportedAccess);
      }
      if (location?.owningFabric !== void 0 && location.owningFabric !== session.fabric) {
        throw new WriteError(location, "Permission denied: Owning/accessing fabric mismatch");
      }
      wrappedAuthorizeWrite(session, location);
    };
    mayWrite = (session, location) => {
      if (hasLocalActor(session) || session.command) {
        return true;
      }
      if (!session.fabric) {
        return false;
      }
      if (location?.owningFabric !== void 0 && location.owningFabric !== session.fabric) {
        return false;
      }
      return wrappedMayWrite(session, location);
    };
  }
  if (!limits.readable) {
    authorizeRead = (session, location) => {
      if (hasLocalActor(session) || session.command) {
        return;
      }
      throw new ReadError(location, "Permission defined: Value is write-only");
    };
    mayRead = (session) => {
      return hasLocalActor(session) || !!session.command;
    };
  }
  if (!limits.writable) {
    authorizeWrite = (session, location) => {
      if (hasLocalActor(session) || session.command) {
        return;
      }
      throw new WriteError(location, "Permission denied: Value is read-only");
    };
    mayWrite = (session) => {
      return hasLocalActor(session) || !!session.command;
    };
  }
  return Object.freeze({
    limits,
    authorizeRead,
    mayRead,
    authorizeWrite,
    mayWrite,
    authorizeInvoke(_session, location) {
      throw new SchemaImplementationError(location, "Permission denied: Invoke request but non-command schema");
    },
    mayInvoke() {
      return false;
    }
  });
}
function commandEnforcerFor(schema) {
  const limits = limitsFor(schema);
  const timed = schema.effectiveAccess.timed;
  const fabric = schema.effectiveAccess.fabric;
  return {
    limits,
    authorizeRead(_session, location) {
      throw new SchemaImplementationError(location, "Permission denied: Read request but command schema");
    },
    mayRead() {
      return false;
    },
    authorizeWrite(_session, location) {
      throw new SchemaImplementationError(location, "Permission denied: Write request but command schema");
    },
    mayWrite() {
      return false;
    },
    authorizeInvoke(session, location) {
      if (hasLocalActor(session)) {
        return;
      }
      if (!session.command) {
        throw new InvokeError(location, "Invoke attempt without command context");
      }
      if (timed && !session.timed) {
        throw new InvokeError(
          location,
          "Invoke attempt without required timed context",
          Status.TimedRequestMismatch
        );
      }
      if (fabric && !session.fabric) {
        throw new WriteError(location, "Permission denied: No accessing fabric", Status.UnsupportedAccess);
      }
      if (session.authorityAt(limits.writeLevel, location) === 1 /* Granted */) {
        return;
      }
      throw new InvokeError(location, "Permission denied", Status.UnsupportedAccess);
    },
    mayInvoke(session, location) {
      if (hasLocalActor(session)) {
        return true;
      }
      if (!session.command) {
        return false;
      }
      if (timed && !session.timed) {
        return false;
      }
      if (fabric && !session.fabric) {
        return false;
      }
      return session.authorityAt(limits.writeLevel, location) === 1 /* Granted */;
    }
  };
}
function limitsFor(schema) {
  const access = schema.effectiveAccess;
  const quality = schema instanceof ValueModel ? schema.effectiveQuality : void 0;
  let fixed = quality?.fixed;
  for (let s = schema.parent; !fixed && s instanceof ValueModel; s = s.parent) {
    if (s.effectiveQuality.fixed) {
      fixed = true;
    }
  }
  const limits = Object.freeze({
    readable: access.readable,
    writable: access.writable && !fixed,
    fabricScoped: access.fabric === Access.Fabric.Scoped || access.fabric === Access.Fabric.Sensitive,
    fabricSensitive: access.fabric === Access.Fabric.Sensitive,
    timed: access.timed === true,
    largeMessage: quality?.largeMessage === true,
    // Official Matter defaults are View for read and Operate for write. However, the schema's effective access
    // should already have these defaults.  Here we just adopt minimum needed rights as a safe fallback access level.
    readLevel: access.readPriv === void 0 ? AccessLevel.View : Access.PrivilegeLevel[access.readPriv],
    writeLevel: access.writePriv === void 0 ? AccessLevel.Operate : Access.PrivilegeLevel[access.writePriv]
  });
  return limits;
}
export {
  AccessControl,
  assertRemoteActor,
  hasLocalActor,
  hasRemoteActor
};
//# sourceMappingURL=AccessControl.js.map
