import { EntityMetadata } from '../typings.js';
import { Utils } from '../utils/Utils.js';
import { MetadataError } from '../errors.js';
import { EntityHelper } from '../entity/EntityHelper.js';
import { EntitySchema } from './EntitySchema.js';
function getGlobalStorage(namespace) {
  const key = `mikro-orm-${namespace}`;
  globalThis[key] = globalThis[key] || {};
  return globalThis[key];
}
/** Registry that stores and provides access to entity metadata by class, name, or id. */
export class MetadataStorage {
  static PATH_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.PATH_SYMBOL');
  static #metadata = getGlobalStorage('metadata');
  #metadataMap = new Map();
  #idMap;
  #classNameMap;
  #uniqueNameMap;
  constructor(metadata = {}) {
    this.#idMap = {};
    this.#uniqueNameMap = {};
    this.#classNameMap = Utils.copy(metadata, false);
    for (const meta of Object.values(this.#classNameMap)) {
      this.#idMap[meta._id] = meta;
      this.#uniqueNameMap[meta.uniqueName] = meta;
      this.#metadataMap.set(meta.class, meta);
    }
  }
  static getMetadata(entity, path) {
    const key = entity && path ? entity + '-' + Utils.hash(path) : null;
    if (key && !MetadataStorage.#metadata[key]) {
      MetadataStorage.#metadata[key] = new EntityMetadata({ className: entity, path });
    }
    if (key) {
      return MetadataStorage.#metadata[key];
    }
    return MetadataStorage.#metadata;
  }
  /** Checks whether an entity with the given class name exists in the global metadata. */
  static isKnownEntity(name) {
    return !!Object.values(this.#metadata).find(meta => meta.className === name);
  }
  /** Clears all entries from the global metadata registry. */
  static clear() {
    Object.keys(this.#metadata).forEach(k => delete this.#metadata[k]);
  }
  /** Returns the map of all registered entity metadata. */
  getAll() {
    return this.#metadataMap;
  }
  /** Returns metadata for the given entity, optionally initializing it if not found. */
  get(entityName, init = false) {
    const exists = this.find(entityName);
    if (exists) {
      return exists;
    }
    const className = Utils.className(entityName);
    if (!init) {
      throw MetadataError.missingMetadata(className);
    }
    const meta = new EntityMetadata({ class: entityName, name: className });
    this.set(entityName, meta);
    return meta;
  }
  /** Finds metadata for the given entity, returning undefined if not registered. */
  find(entityName) {
    if (!entityName) {
      return;
    }
    const meta = this.#metadataMap.get(entityName);
    if (meta) {
      return meta;
    }
    if (EntitySchema.is(entityName)) {
      return this.#metadataMap.get(entityName.meta.class) ?? entityName.meta;
    }
    return this.#classNameMap[Utils.className(entityName)];
  }
  /** Checks whether metadata exists for the given entity. */
  has(entityName) {
    return this.#metadataMap.has(entityName);
  }
  /** Registers metadata for the given entity. */
  set(entityName, meta) {
    this.#metadataMap.set(entityName, meta);
    this.#idMap[meta._id] = meta;
    this.#uniqueNameMap[meta.uniqueName] = meta;
    this.#classNameMap[Utils.className(entityName)] = meta;
    return meta;
  }
  /** Removes metadata for the given entity from all internal maps. */
  reset(entityName) {
    const meta = this.find(entityName);
    if (meta) {
      this.#metadataMap.delete(meta.class);
      delete this.#idMap[meta._id];
      delete this.#uniqueNameMap[meta.uniqueName];
      delete this.#classNameMap[meta.className];
    }
  }
  /** Decorates all entity prototypes with helper methods (e.g. init, toJSON). */
  decorate(em) {
    [...this.#metadataMap.values()].filter(meta => meta.prototype).forEach(meta => EntityHelper.decorate(meta, em));
  }
  *[Symbol.iterator]() {
    for (const meta of this.#metadataMap.values()) {
      yield meta;
    }
  }
  /** Returns metadata by its internal numeric id. */
  getById(id) {
    return this.#idMap[id];
  }
  /** Returns metadata by class name, optionally throwing if not found. */
  getByClassName(className, validate = true) {
    return this.validate(this.#classNameMap[className], className, validate);
  }
  /** Returns metadata by unique name, optionally throwing if not found. */
  getByUniqueName(uniqueName, validate = true) {
    return this.validate(this.#uniqueNameMap[uniqueName], uniqueName, validate);
  }
  validate(meta, id, validate) {
    if (!meta && validate) {
      throw MetadataError.missingMetadata(id);
    }
    return meta;
  }
}
