/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Conformance } from "#aspects/Conformance.js";
import { ElementTag } from "#common/ElementTag.js";
import { SchemaImplementationError } from "#common/errors.js";
import { ImplementationError } from "@matter/general";
import { MutableModelIndex } from "./ModelIndex.js";
import { ModelTraversal } from "./ModelTraversal.js";
const DEFAULT_TAGS = /* @__PURE__ */ new Set([ElementTag.Field, ElementTag.Attribute]);
const GLOBAL_IDS = /* @__PURE__ */ new Set([65533, 65532, 65531, 65530, 65529, 65528]);
const cache = /* @__PURE__ */ new WeakMap();
function Scope(subject, options = {}) {
  let owner;
  if (subject.isScope || options.forceOwner) {
    owner = subject;
  } else {
    const scope2 = new ModelTraversal().findScope(subject);
    if (scope2 === void 0) {
      throw new SchemaImplementationError(subject, `No parent scope`);
    }
    owner = scope2;
  }
  const useCache = options.forceCache || Object.isFrozen(owner);
  let deconflictedMemberCache;
  let conformantMemberCache;
  const features = {
    definedFeatures: owner.definedFeatures ?? /* @__PURE__ */ new Set(),
    supportedFeatures: owner.supportedFeatures ?? /* @__PURE__ */ new Set()
  };
  if (useCache && !options.disableCache) {
    const cached = cache.get(owner);
    if (cached) {
      return cached;
    }
    deconflictedMemberCache = /* @__PURE__ */ new Map();
    conformantMemberCache = /* @__PURE__ */ new Map();
  }
  let shadows;
  const canonicalIdentityLevels = {};
  let level = 0;
  new ModelTraversal().visitInheritance(owner, (scope2) => {
    level++;
    for (const model of scope2.children) {
      for (const identity of [`n${model.tag}\u241C${model.name}\u241C${model.discriminator ?? ""}`]) {
        const canonical = canonicalIdentityLevels[identity];
        if (canonical === void 0) {
          canonicalIdentityLevels[identity] = {
            level,
            models: [model]
          };
        } else if (canonical.level === level) {
          canonical.models.push(model);
        } else {
          if (!shadows) {
            shadows = /* @__PURE__ */ new WeakMap();
          }
          shadows.set(model, canonical.models);
        }
      }
    }
  });
  const scope = {
    owner,
    ...features,
    isShadow: shadows ? (model) => shadows.has(model) : () => false,
    extensionOf: shadows ? (model) => shadows.get(model)?.[0] : (model) => model,
    modelFor: shadows ? (model) => shadows.get(model)?.[0] ?? model : (model) => model,
    membersOf,
    hasOperationalSupport
  };
  function membersOf(parent, options2 = {}) {
    const { conformance: conformanceMode } = options2;
    let { tags = DEFAULT_TAGS } = options2;
    if (Array.isArray(tags)) {
      tags = new Set(tags);
    }
    const allMembers = findAllMembers(parent, tags, scope);
    if (parent.tag === ElementTag.Cluster && tags.has(ElementTag.Attribute)) {
      injectGlobalAttributes(
        owner,
        allMembers
      );
    }
    if (!conformanceMode || conformanceMode === "ignore") {
      return allMembers;
    }
    const conformantOnly = conformanceMode === "conformant";
    if (!conformantOnly && conformanceMode !== "deconflicted") {
      throw new ImplementationError(`Invalid member conformance mode ${conformanceMode}`);
    }
    return filterWithConformance(
      parent,
      tags,
      allMembers,
      features,
      conformantOnly,
      conformantOnly ? deconflictedMemberCache : conformantMemberCache
    );
  }
  function hasOperationalSupport(model) {
    model = scope.modelFor(model);
    const operational = model.effectiveIsSupported;
    if (operational !== void 0) {
      return operational;
    }
    const conformance = model.effectiveConformance;
    return conformance?.applicabilityFor(scope) === Conformance.Applicability.Mandatory;
  }
  if (useCache) {
    cache.set(owner, scope);
  }
  return scope;
}
((Scope2) => {
  Scope2.IgnoreConformance = "ignore";
  Scope2.DeconflictedConformance = "deconflicted";
  Scope2.ConformantConformance = "conformant";
})(Scope || (Scope = {}));
function findAllMembers(parent, tags, scope) {
  const members = new MutableModelIndex();
  const defined = {};
  const visited = /* @__PURE__ */ new Set();
  const traversal = new ModelTraversal();
  let level = 0;
  const childSearchVisitor = (model) => {
    const extension = scope.modelFor(model);
    if (extension !== model && !visited.has(extension)) {
      traversal.visitInheritance(extension, childSearchVisitor);
      return false;
    }
    visited.add(model);
    level++;
    for (const child of model.children) {
      if (!tags.has(child.tag)) {
        continue;
      }
      const nameIdentity = `s\u241C${child.tag}\u241C${child.name}\u241C${child.discriminator ?? ""}`;
      const nameLevel = defined[nameIdentity];
      if (nameLevel !== void 0 && nameLevel < level) {
        continue;
      }
      defined[nameIdentity] = level;
      members.push(child);
    }
  };
  traversal.visitInheritance(parent, childSearchVisitor);
  return members;
}
function injectGlobalAttributes(scope, members) {
  const missingGlobalIds = new Set(GLOBAL_IDS);
  for (const m of members) {
    if (m.tag === ElementTag.Attribute && m.id) {
      missingGlobalIds.delete(m.id);
    }
  }
  if (missingGlobalIds.size) {
    const root = new ModelTraversal().findRoot(scope);
    if (root) {
      for (const id of missingGlobalIds) {
        const global = root.children.select(id, [ElementTag.Attribute]);
        if (global) {
          members.push(global);
        }
      }
    }
  }
}
function filterWithConformance(parent, tags, members, features, conformantOnly, cache2) {
  const tagsToCollect = new Set(tags);
  const result = new MutableModelIndex();
  const cached = cache2 ? cache2.get(parent) ?? /* @__PURE__ */ new Map() : void 0;
  if (cache2 && cached) {
    for (const tag of tagsToCollect) {
      const cachedMembers = cached.get(tag);
      if (cachedMembers) {
        result.push(...cachedMembers);
        tagsToCollect.delete(tag);
      }
    }
    if (tagsToCollect.size === 0) {
      return result;
    }
  }
  const selectedMembers = {};
  for (const tag of tagsToCollect) {
    selectedMembers[tag] = {};
  }
  for (const member of members) {
    const { conformance, tag } = member;
    if (!tagsToCollect.has(tag)) {
      continue;
    }
    if (!conformance) {
      throw new ImplementationError(
        `Conformance filtering invoked on ${member} which does not support conformance`
      );
    }
    if (conformantOnly && !conformance.applicabilityFor(features)) {
      continue;
    }
    const other = selectedMembers[tag][member.name];
    if (other !== void 0) {
      if (!conformantOnly && !conformance.applicabilityFor(features)) {
        continue;
      }
      const { conformance: otherConformance } = other;
      if (!otherConformance) {
        throw new ImplementationError(
          `Conformance filtering invoked on ${other} which does not support conformance`
        );
      }
      if (otherConformance.applicabilityFor(features)) {
        throw new SchemaImplementationError(
          parent,
          `There are multiple definitions of "${member.name}" that cannot be differentiated by conformance`
        );
      }
    }
    selectedMembers[tag][member.name] = member;
  }
  for (const tag in selectedMembers) {
    const tagResult = Object.values(selectedMembers[tag]);
    if (tagResult.length) {
      result.push(...tagResult);
      if (cached) {
        cached.set(tag, new Set(tagResult));
      }
    }
  }
  if (cache2 && cached) {
    cache2.set(parent, cached);
  }
  return result;
}
export {
  Scope
};
//# sourceMappingURL=Scope.js.map
