class InsertStatement {
  order;
  #keys;
  #data;
  constructor(keys, data, order) {
    this.order = order;
    this.#keys = keys;
    this.#data = data;
  }
  getHash() {
    return JSON.stringify(this.#data);
  }
  getData() {
    const data = {};
    this.#keys.forEach((key, idx) => (data[key] = this.#data[idx]));
    return data;
  }
}
class DeleteStatement {
  #keys;
  #cond;
  constructor(keys, cond) {
    this.#keys = keys;
    this.#cond = cond;
  }
  getHash() {
    return JSON.stringify(this.#cond);
  }
  getCondition() {
    const cond = {};
    this.#keys.forEach((key, idx) => (cond[key] = this.#cond[idx]));
    return cond;
  }
}
export class PivotCollectionPersister {
  #inserts = new Map();
  #upserts = new Map();
  #deletes = new Map();
  #batchSize;
  #order = 0;
  #meta;
  #driver;
  #ctx;
  #schema;
  #loggerContext;
  constructor(meta, driver, ctx, schema, loggerContext) {
    this.#meta = meta;
    this.#driver = driver;
    this.#ctx = ctx;
    this.#schema = schema;
    this.#loggerContext = loggerContext;
    this.#batchSize = this.#driver.config.get('batchSize');
  }
  enqueueUpdate(prop, insertDiff, deleteDiff, pks, isInitialized = true) {
    if (insertDiff.length) {
      if (isInitialized) {
        this.enqueueInsert(prop, insertDiff, pks);
      } else {
        this.enqueueUpsert(prop, insertDiff, pks);
      }
    }
    if (deleteDiff === true || (Array.isArray(deleteDiff) && deleteDiff.length)) {
      this.enqueueDelete(prop, deleteDiff, pks);
    }
  }
  enqueueInsert(prop, insertDiff, pks) {
    for (const fks of insertDiff) {
      const statement = this.createInsertStatement(prop, fks, pks);
      const hash = statement.getHash();
      if (prop.owner || !this.#inserts.has(hash)) {
        this.#inserts.set(hash, statement);
      }
    }
  }
  enqueueUpsert(prop, insertDiff, pks) {
    for (const fks of insertDiff) {
      const statement = this.createInsertStatement(prop, fks, pks);
      const hash = statement.getHash();
      if (prop.owner || !this.#upserts.has(hash)) {
        this.#upserts.set(hash, statement);
      }
    }
  }
  createInsertStatement(prop, fks, pks) {
    const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
    return new InsertStatement(keys, data, this.#order++);
  }
  enqueueDelete(prop, deleteDiff, pks) {
    if (deleteDiff === true) {
      const { data, keys } = this.buildPivotKeysAndData(prop, [], pks, true);
      const statement = new DeleteStatement(keys, data);
      this.#deletes.set(statement.getHash(), statement);
      return;
    }
    for (const fks of deleteDiff) {
      const { data, keys } = this.buildPivotKeysAndData(prop, fks, pks);
      const statement = new DeleteStatement(keys, data);
      this.#deletes.set(statement.getHash(), statement);
    }
  }
  /**
   * Build the keys and data arrays for pivot table operations.
   * Handles polymorphic M:N by prepending the discriminator column/value.
   */
  buildPivotKeysAndData(prop, fks, pks, deleteAll = false) {
    let data;
    let keys;
    if (deleteAll) {
      data = pks;
      keys = prop.joinColumns;
    } else {
      data = prop.owner ? [...fks, ...pks] : [...pks, ...fks];
      keys = prop.owner
        ? [...prop.inverseJoinColumns, ...prop.joinColumns]
        : [...prop.joinColumns, ...prop.inverseJoinColumns];
    }
    if (prop.polymorphic && prop.discriminatorColumn && prop.discriminatorValue) {
      data = [prop.discriminatorValue, ...data];
      keys = [prop.discriminatorColumn, ...keys];
    }
    return { data, keys };
  }
  collectStatements(statements) {
    const items = [];
    for (const statement of statements.values()) {
      items[statement.order] = statement.getData();
    }
    return items.filter(Boolean);
  }
  async execute() {
    if (this.#deletes.size > 0) {
      const deletes = [...this.#deletes.values()];
      for (let i = 0; i < deletes.length; i += this.#batchSize) {
        const chunk = deletes.slice(i, i + this.#batchSize);
        const cond = { $or: [] };
        for (const item of chunk) {
          cond.$or.push(item.getCondition());
        }
        await this.#driver.nativeDelete(this.#meta.class, cond, {
          ctx: this.#ctx,
          schema: this.#schema,
          loggerContext: this.#loggerContext,
        });
      }
    }
    if (this.#inserts.size > 0) {
      const filtered = this.collectStatements(this.#inserts);
      for (let i = 0; i < filtered.length; i += this.#batchSize) {
        const chunk = filtered.slice(i, i + this.#batchSize);
        await this.#driver.nativeInsertMany(this.#meta.class, chunk, {
          ctx: this.#ctx,
          schema: this.#schema,
          convertCustomTypes: false,
          processCollections: false,
          loggerContext: this.#loggerContext,
        });
      }
    }
    if (this.#upserts.size > 0) {
      const filtered = this.collectStatements(this.#upserts);
      for (let i = 0; i < filtered.length; i += this.#batchSize) {
        const chunk = filtered.slice(i, i + this.#batchSize);
        await this.#driver.nativeUpdateMany(this.#meta.class, [], chunk, {
          ctx: this.#ctx,
          schema: this.#schema,
          convertCustomTypes: false,
          processCollections: false,
          upsert: true,
          onConflictAction: 'ignore',
          loggerContext: this.#loggerContext,
        });
      }
    }
  }
}
