import {
  BaseRepository
} from "./chunk-J6KZS54B.js";

// runtime/base-classes/junction-integration-repository.ts
import { and, eq } from "drizzle-orm";
var JunctionIntegrationRepository = class extends BaseRepository {
  /**
   * Upsert ONE junction row by its composite identity, in a single transaction:
   *   1. resolve the REQUIRED left FK (provider-scoped) — STRICT: missing → throws;
   *   2. resolve the REQUIRED right FK (provider-scoped) — STRICT: missing → throws;
   *   3. insert-or-update on the `(left, right[, role])` conflict target.
   *
   * Idempotent. Returns the composite externalId as the projection `id`.
   *
   * @param write     parent external ids (`<left>ExternalId`/`<right>ExternalId`)
   *                  + optional `role` + `userId`
   * @param provider  adapter/provider label used to scope the parent lookups
   * @param tx        optional outer transaction; when omitted we open our own
   */
  async integrationUpsertOne(write, provider, tx) {
    const cfg = this.integrationConfig;
    const w = write;
    const leftWriteKey = `${cfg.left.column.replace(/Id$/, "")}ExternalId`;
    const rightWriteKey = `${cfg.right.column.replace(/Id$/, "")}ExternalId`;
    const run = async (db) => {
      const leftId = await this.resolveStrict(
        db,
        cfg.left.refTable,
        w[leftWriteKey],
        provider,
        cfg.left.column
      );
      const rightId = await this.resolveStrict(
        db,
        cfg.right.refTable,
        w[rightWriteKey],
        provider,
        cfg.right.column
      );
      const now = /* @__PURE__ */ new Date();
      const role = cfg.roleColumn ? w["role"] : void 0;
      const values = {
        [cfg.left.column]: leftId,
        [cfg.right.column]: rightId,
        ...cfg.roleColumn ? { [cfg.roleColumn]: role } : {},
        ...this.behaviors.timestamps ? { updatedAt: now } : {}
      };
      const target = cfg.roleColumn ? [this.table[cfg.left.column], this.table[cfg.right.column], this.table[cfg.roleColumn]] : [this.table[cfg.left.column], this.table[cfg.right.column]];
      const rows = await db.insert(this.table).values(values).onConflictDoUpdate({
        target,
        set: { ...this.behaviors.timestamps ? { updatedAt: now } : {} }
      }).returning();
      const saved = rows[0];
      return this.toProjection(saved, w, provider);
    };
    return tx ? run(tx) : this.db.transaction((t) => run(t));
  }
  /**
   * Canonical-projected lookup by the COMPOSITE externalId, differ-ready. Parses
   * the composite, resolves BOTH parents NON-throwing (→ null), then selects by
   * the identity tuple. Returns `null` on malformed composite / unresolved
   * parent / no row (a missing "before" side is a create from the differ's view).
   */
  async findByExternalIdProjected(externalId, provider) {
    const cfg = this.integrationConfig;
    const parsed = parseCompositeExternalId(externalId, cfg.roleColumn !== null);
    if (!parsed) return null;
    const leftId = await this.resolveLoose(this.db, cfg.left.refTable, parsed.left, provider);
    if (leftId === null) return null;
    const rightId = await this.resolveLoose(this.db, cfg.right.refTable, parsed.right, provider);
    if (rightId === null) return null;
    const rows = await this.db.select().from(this.table).where(this.identityWhere(leftId, rightId, parsed.role)).limit(1);
    const row = rows[0];
    if (!row) return null;
    const w = {
      [`${cfg.left.column.replace(/Id$/, "")}ExternalId`]: parsed.left,
      [`${cfg.right.column.replace(/Id$/, "")}ExternalId`]: parsed.right,
      ...cfg.roleColumn ? { role: parsed.role } : {},
      userId: ""
    };
    return this.toProjection(row, w, provider);
  }
  /**
   * Hard-delete the junction by composite externalId. Junctions have no
   * `deleted_at` and no external-linkage columns to clear, so a integration "delete"
   * removes the row. Resolves both parents NON-throwing, then deletes by the
   * identity tuple. Returns the composite id, or `null` when nothing matched.
   */
  async softDeleteByExternalId(externalId, provider, tx) {
    const cfg = this.integrationConfig;
    const parsed = parseCompositeExternalId(externalId, cfg.roleColumn !== null);
    if (!parsed) return null;
    const db = this.runner(tx);
    const leftId = await this.resolveLoose(db, cfg.left.refTable, parsed.left, provider);
    if (leftId === null) return null;
    const rightId = await this.resolveLoose(db, cfg.right.refTable, parsed.right, provider);
    if (rightId === null) return null;
    const rows = await db.delete(this.table).where(this.identityWhere(leftId, rightId, parsed.role)).returning({ id: this.table[cfg.left.column] });
    return rows[0] ? { id: externalId } : null;
  }
  /**
   * Project a raw junction row to the differ shape: the COMPOSITE externalId
   * `id` (junctions have no surrogate id) plus the local FK columns, role (when
   * role-bearing) and timestamps — matching the emitted
   * `<Junction>IntegrationProjection` interface (id + leftId + rightId + role? +
   * createdAt + updatedAt). Junction projections are purely structural, so this
   * is fully generic over `integrationConfig` — no per-junction override is emitted.
   */
  toProjection(row, write, _provider) {
    const cfg = this.integrationConfig;
    const r = row;
    const leftExt = write[`${cfg.left.column.replace(/Id$/, "")}ExternalId`];
    const rightExt = write[`${cfg.right.column.replace(/Id$/, "")}ExternalId`];
    const role = cfg.roleColumn ? r[cfg.roleColumn] : void 0;
    const out = {
      id: buildCompositeExternalId(leftExt, rightExt, role),
      [cfg.left.column]: r[cfg.left.column],
      [cfg.right.column]: r[cfg.right.column]
    };
    if (cfg.roleColumn) out[cfg.roleColumn] = r[cfg.roleColumn];
    out["createdAt"] = r["createdAt"];
    out["updatedAt"] = r["updatedAt"];
    return out;
  }
  /** Build the identity WHERE clause `(left, right[, role])`. */
  identityWhere(leftId, rightId, role) {
    const cfg = this.integrationConfig;
    const conds = [
      eq(this.table[cfg.left.column], leftId),
      eq(this.table[cfg.right.column], rightId)
    ];
    if (cfg.roleColumn && role !== void 0) {
      conds.push(eq(this.table[cfg.roleColumn], role));
    }
    return and(...conds);
  }
  /** Resolve a parent id (provider-scoped), throwing when unresolved. */
  async resolveStrict(db, refTable, parentExternalId, provider, column) {
    const id = await this.resolveLoose(db, refTable, parentExternalId, provider);
    if (!id) {
      throw new Error(
        `${this.constructor.name}.integrationUpsertOne: unresolved parent '${parentExternalId}' (provider '${provider}') for '${column}' \u2014 parent not integrated yet`
      );
    }
    return id;
  }
  /** Resolve a parent id (provider-scoped), returning null when unresolved. */
  async resolveLoose(db, refTable, parentExternalId, provider) {
    if (!parentExternalId) return null;
    const rows = await db.select({ id: refTable["id"] }).from(refTable).where(
      and(
        eq(refTable["provider"], provider),
        eq(refTable["externalId"], parentExternalId)
      )
    ).limit(1);
    return rows[0]?.id ?? null;
  }
};
function buildCompositeExternalId(leftExternalId, rightExternalId, role) {
  return role !== void 0 ? `${leftExternalId}::${rightExternalId}::${role}` : `${leftExternalId}::${rightExternalId}`;
}
function parseCompositeExternalId(externalId, withRole) {
  const parts = externalId.split("::");
  const expected = withRole ? 3 : 2;
  if (parts.length !== expected || parts.some((p) => p.length === 0)) return null;
  return {
    left: parts[0],
    right: parts[1],
    role: withRole ? parts[2] : void 0
  };
}

export {
  JunctionIntegrationRepository,
  buildCompositeExternalId,
  parseCompositeExternalId
};
//# sourceMappingURL=chunk-2FTZLDBP.js.map