/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { InternalError, camelize } from "@matter/general";
import { ElementTag } from "../common/ElementTag.js";
import { Metatype } from "../common/Metatype.js";
import { ClusterModel } from "../models/ClusterModel.js";
import { CommandModel } from "../models/CommandModel.js";
import { DatatypeModel } from "../models/DatatypeModel.js";
import { EventModel } from "../models/EventModel.js";
import { FieldModel } from "../models/FieldModel.js";
import { MatterModel } from "../models/MatterModel.js";
import { Model } from "../models/Model.js";
import { ValueModel } from "../models/ValueModel.js";
import { Scope } from "./Scope.js";
const cache = /* @__PURE__ */ new WeakMap();
function GeneratorScope(model) {
  if (!(model instanceof Model)) {
    return model;
  }
  let rootModel = model;
  while (rootModel.parent && rootModel.parent.tag !== ElementTag.Matter) {
    rootModel = rootModel.parent;
  }
  const owner = Scope(rootModel, { forceOwner: true, forceCache: true });
  let result = cache.get(owner);
  if (result === void 0) {
    cache.set(owner, result = allocateScope(owner));
  }
  return result;
}
function allocateScope(scope) {
  const { locations, names } = assignNames(scope);
  const { owner } = scope;
  return {
    owner,
    scope,
    nameFor(model, tlv, specific = false) {
      const { definition: definer } = this.locationOf(model, specific);
      const name = names.get(definer);
      if (name === void 0) {
        throw new InternalError(`No name assigned to ${model} in scope ${owner}`);
      }
      if (tlv) {
        return `Tlv${name}`;
      }
      return name;
    },
    locationOf(model, specific = false) {
      if (!specific) {
        model = scope.modelFor(model);
      }
      const location = locations.get(model);
      if (location === void 0) {
        throw new InternalError(`No location identified for ${model} from scope ${owner}`);
      }
      return location;
    },
    canonicalModelFor(model) {
      return scope.extensionOf(model) ?? model;
    },
    definingModelFor(model) {
      let result;
      model.forEachAncestor((model2) => {
        if (model2 instanceof ValueModel) {
          result = this.canonicalModelFor(model2).definingModel;
          if (result) {
            result = this.canonicalModelFor(result);
            return false;
          }
        }
      });
      return result;
    },
    *namedModels() {
      for (const [model, location] of locations) {
        const name = names.get(model);
        if (name !== void 0) {
          yield [model, name, location];
        }
      }
    }
  };
}
var Priority = /* @__PURE__ */ ((Priority2) => {
  Priority2[Priority2["LocalNamespace"] = 0] = "LocalNamespace";
  Priority2[Priority2["Enum"] = 1] = "Enum";
  Priority2[Priority2["Struct"] = 2] = "Struct";
  Priority2[Priority2["Bitmap"] = 3] = "Bitmap";
  Priority2[Priority2["OtherNamespace"] = 4] = "OtherNamespace";
  Priority2[Priority2["Global"] = 5] = "Global";
  Priority2[Priority2["Attribute"] = 6] = "Attribute";
  Priority2[Priority2["Command"] = 7] = "Command";
  Priority2[Priority2["Event"] = 8] = "Event";
  return Priority2;
})(Priority || {});
class Locations extends Map {
}
function assignNames(scope) {
  const locations = identifyNamedModels(scope);
  const priorities = assignPriorities(locations);
  const names = assignNamesByPriority(priorities);
  return { names, locations };
}
function identifyNamedModels(scope) {
  const locations = new Locations();
  const { owner } = scope;
  locations.set(scope.owner, {
    definition: owner,
    scope: owner,
    isLocal: true,
    isGlobal: true
  });
  const base = owner.base;
  if (base instanceof ClusterModel || base instanceof ValueModel && !base.metatype) {
    locations.set(base, {
      definition: base,
      scope: base,
      isLocal: false,
      isGlobal: true
    });
  }
  owner.visit(define);
  if (owner instanceof ClusterModel) {
    const aces = owner.allAces;
    for (const ace of aces) {
      if (ace.parent === owner) {
        continue;
      }
      ace.visit(define);
    }
  }
  return locations;
  function define(model, specific = false) {
    if (!(model instanceof ValueModel)) {
      return;
    }
    const metatype = model.effectiveMetatype;
    switch (metatype) {
      case Metatype.array:
        const entry = model.listEntry;
        if (entry) {
          define(entry);
        }
        break;
      case Metatype.object:
      case Metatype.enum:
      case Metatype.bitmap:
        break;
      default:
        return;
    }
    let definer = model.definingModel;
    if (!definer) {
      return;
    }
    if (!specific) {
      const extension = scope.extensionOf(definer);
      if (extension === model) {
        definer = model;
      } else if (extension) {
        define(extension);
        return;
      }
    }
    if (locations.has(definer)) {
      return;
    }
    let modelScope = definer.owner(ClusterModel) ?? definer.owner(MatterModel);
    if (modelScope === void 0) {
      throw new InternalError(`Model ${definer} does not appear to be part of a proper hierarchy`);
    }
    const location = {
      definition: definer,
      scope: modelScope,
      isLocal: modelScope === owner,
      isGlobal: modelScope.tag === ElementTag.Matter
    };
    locations.set(definer, location);
    if (definer instanceof ValueModel && !specific && referencesShadows(definer)) {
      location.isLocal = true;
      modelScope = owner;
      definer.members.forEach((m) => define(m));
    }
    if (modelScope instanceof ClusterModel && modelScope !== owner) {
      locations.set(modelScope, {
        definition: modelScope,
        scope: modelScope,
        isLocal: false,
        isGlobal: true
      });
    }
    if (metatype === Metatype.enum) {
      const { base: base2 } = definer;
      if (base2 && base2.metatype === void 0) {
        define(base2, true);
      }
    }
  }
  function referencesShadows(model) {
    const visited = /* @__PURE__ */ new Set();
    return model.visit((descendent) => {
      if (visited.has(descendent)) {
        return;
      }
      visited.add(descendent);
      if (scope.isShadow(descendent)) {
        return false;
      }
      const base2 = descendent.base;
      if (!base2) {
        return;
      }
      if (referencesShadows(base2)) {
        return false;
      }
    }) === false;
  }
}
function assignPriorities(locations) {
  const priorities = Array();
  function collect(priority, model) {
    let slot = priorities[priority];
    if (slot === void 0) {
      slot = priorities[priority] = /* @__PURE__ */ new Set();
    }
    slot.add(model);
  }
  for (const [model, { definition: definer, isLocal, isGlobal }] of locations) {
    if (definer instanceof DatatypeModel || definer instanceof FieldModel) {
      const definingNamespace = definer.owner(ClusterModel) ?? definer.owner(MatterModel);
      if (definingNamespace === void 0) {
        throw new InternalError(`Model ${definer} does not appear to be part of a proper hierarchy`);
      }
      if (isLocal) {
        switch (model.effectiveMetatype) {
          case Metatype.object:
            collect(2 /* Struct */, definer);
            break;
          case Metatype.enum:
            collect(1 /* Enum */, definer);
            break;
          case Metatype.bitmap:
            collect(3 /* Bitmap */, definer);
            break;
        }
      } else {
        if (isGlobal) {
          collect(5 /* Global */, definer);
        } else {
          collect(4 /* OtherNamespace */, definingNamespace);
        }
      }
    } else {
      switch (model.tag) {
        case ElementTag.Attribute:
          collect(6 /* Attribute */, definer);
          break;
        case ElementTag.Event:
          collect(8 /* Event */, definer);
          break;
        case ElementTag.Command:
          collect(7 /* Command */, definer);
          break;
        case ElementTag.Cluster:
          collect(isLocal ? 0 /* LocalNamespace */ : 4 /* OtherNamespace */, definer);
      }
    }
  }
  return priorities;
}
function assignNamesByPriority(priorities) {
  const names = /* @__PURE__ */ new Map();
  const assigned = /* @__PURE__ */ new Set();
  function assign(model) {
    let name = camelize(model.name, true);
    if (model instanceof DatatypeModel) {
      name = name.replace(/(?:Enum|Struct|Bitmap)$/, "");
    } else if (model instanceof CommandModel) {
      name = name.replace(/(?:Request|Response)$/, "");
      if (model.isRequest) {
        name += "Request";
      } else {
        name += "Response";
      }
    } else if (model instanceof EventModel) {
      if (!name.endsWith("Event")) {
        name += "Event";
      }
    } else if (model instanceof ClusterModel) {
      name = name.replace(/Cluster$/, "");
    }
    if (name === "Type") {
      name = `${model.parent?.name}Type`;
    }
    if (assigned.has(name)) {
      switch (model.tag) {
        case ElementTag.Attribute:
          name += "Attribute";
          break;
        case ElementTag.Field:
          if (model.parent && model.name !== "Type") {
            name = `${model.parent.name}${name}`;
          }
          break;
        case ElementTag.Cluster:
          name += "Namespace";
          break;
        case ElementTag.Datatype:
          switch (model.effectiveMetatype) {
            case Metatype.object:
              name += "Struct";
              break;
            case Metatype.enum:
              name += "Enum";
              break;
            case Metatype.bitmap:
              name += "Bitmap";
              break;
          }
      }
    }
    if (assigned.has(name)) {
      let next = 2;
      while (true) {
        const candidate = `${name}${next}`;
        if (assigned.has(candidate)) {
          next++;
        } else {
          name = candidate;
          break;
        }
      }
    }
    names.set(model, name);
    assigned.add(name);
  }
  for (const models of priorities) {
    if (!models) {
      continue;
    }
    for (const model of models) {
      assign(model);
    }
  }
  return names;
}
export {
  GeneratorScope
};
//# sourceMappingURL=GeneratorScope.js.map
