/** @internal Stores managed entity instances keyed by their primary key hash, ensuring each row is loaded once. */
export class IdentityMap {
  #defaultSchema;
  #registry = new Map();
  /** Tracks alternate key hashes for each entity so we can clean them up on delete */
  #alternateKeys = new WeakMap();
  constructor(defaultSchema) {
    this.#defaultSchema = defaultSchema;
  }
  /** Stores an entity in the identity map under its primary key hash. */
  store(item) {
    this.getStore(item.__meta.root).set(this.getPkHash(item), item);
  }
  /**
   * Stores an entity under an alternate key (non-PK property).
   * This allows looking up entities by unique properties that are not the primary key.
   */
  storeByKey(item, key, value, schema) {
    const hash = this.getKeyHash(key, value, schema);
    this.getStore(item.__meta.root).set(hash, item);
    // Track this alternate key so we can clean it up when the entity is deleted
    let keys = this.#alternateKeys.get(item);
    if (!keys) {
      keys = new Set();
      this.#alternateKeys.set(item, keys);
    }
    keys.add(hash);
  }
  /** Removes an entity and its alternate key entries from the identity map. */
  delete(item) {
    const meta = item.__meta.root;
    const store = this.getStore(meta);
    store.delete(this.getPkHash(item));
    // Also delete any alternate key entries for this entity
    const altKeys = this.#alternateKeys.get(item);
    if (altKeys) {
      for (const hash of altKeys) {
        store.delete(hash);
      }
      this.#alternateKeys.delete(item);
    }
  }
  /** Retrieves an entity by its hash key from the identity map. */
  getByHash(meta, hash) {
    const store = this.getStore(meta);
    return store.has(hash) ? store.get(hash) : undefined;
  }
  /** Returns (or creates) the per-entity-class store within the identity map. */
  getStore(meta) {
    const store = this.#registry.get(meta.class);
    if (store) {
      return store;
    }
    const newStore = new Map();
    this.#registry.set(meta.class, newStore);
    return newStore;
  }
  clear() {
    this.#registry.clear();
  }
  /** Returns all entities currently in the identity map. */
  values() {
    const ret = [];
    for (const store of this.#registry.values()) {
      ret.push(...store.values());
    }
    return ret;
  }
  *[Symbol.iterator]() {
    for (const store of this.#registry.values()) {
      for (const item of store.values()) {
        yield item;
      }
    }
  }
  /** Returns all hash keys currently in the identity map. */
  keys() {
    const ret = [];
    for (const [cls, store] of this.#registry) {
      ret.push(...[...store.keys()].map(hash => `${cls.name}-${hash}`));
    }
    return ret;
  }
  /**
   * For back compatibility only.
   */
  get(hash) {
    const [name, id] = hash.split('-', 2);
    const cls = [...this.#registry.keys()].find(k => k.name === name);
    if (!cls) {
      return undefined;
    }
    const store = this.#registry.get(cls);
    return store.has(id) ? store.get(id) : undefined;
  }
  getPkHash(item) {
    const wrapped = item.__helper;
    const meta = wrapped.__meta;
    const hash = wrapped.getSerializedPrimaryKey();
    const schema = wrapped.__schema ?? meta.root.schema ?? this.#defaultSchema;
    if (schema) {
      return schema + ':' + hash;
    }
    return hash;
  }
  /**
   * Creates a hash for an alternate key lookup.
   * Format: `[key]value` or `schema:[key]value`
   */
  getKeyHash(key, value, schema) {
    const hash = `[${key}]${value}`;
    if (schema) {
      return schema + ':' + hash;
    }
    return hash;
  }
}
