import { parse as parse$1, stringify, jsonObjectSchema } from "../../../../../shared/src/bigint-json.js";
import { literalUnion, parse } from "../../../../../shared/src/valita.js";
import { normalizedKeyOrder } from "../../../types/row-key.js";
import { object, string, record } from "@badrap/valita";
const SET_OP = "s";
const DEL_OP = "d";
const TRUNCATE_OP = "t";
const RESET_OP = "r";
const CREATE_CHANGELOG_SCHEMA = (
  // stateVersion : a.k.a. row version
  // pos          : order in which to process the change (within the version)
  // table        : The table associated with the change
  // rowKey       : JSON row key for a row change. For table-wide changes RESET
  //                and TRUNCATE, there is no associated row; instead, `pos` is
  //                set to -1 and the rowKey is set to the stateVersion,
  //                guaranteeing when attempting to process the transaction,
  //                the pipeline is reset (and the change log traversal
  //                aborted).
  // op           : 's' for set (insert/update)
  //              : 'd' for delete
  //              : 'r' for table reset (schema change)
  //              : 't' for table truncation (which also resets the pipeline)
  // backfillingColumnVersions
  //              : A JSON mapping from column name to stateVersion tracked
  //                for replicated writes of columns that are being backfilled.
  //                This is used to prevent backfill data, which is at a
  //                fixed snapshot/version outside of the replication stream,
  //                from overwriting newer column values.
  //
  // Naming note: To maintain compatibility between a new replication-manager
  // and old view-syncers, the previous _zero.changeLog table is preserved
  // and its replacement given a new name "changeLog2".
  `
  CREATE TABLE "_zero.changeLog2" (
    "stateVersion"              TEXT NOT NULL,
    "pos"                       INT  NOT NULL,
    "table"                     TEXT NOT NULL,
    "rowKey"                    TEXT NOT NULL,
    "op"                        TEXT NOT NULL,
    "backfillingColumnVersions" TEXT DEFAULT '{}',
    PRIMARY KEY("stateVersion", "pos"),
    UNIQUE("table", "rowKey")
  );
  `
);
const changeLogEntrySchema = object({
  stateVersion: string(),
  table: string(),
  rowKey: string(),
  op: literalUnion(SET_OP, DEL_OP, TRUNCATE_OP, RESET_OP)
}).map((val) => ({
  ...val,
  // Note: sets the rowKey to `null` for table-wide ops / resets
  rowKey: val.op === "t" || val.op === "r" ? null : parse(parse$1(val.rowKey), jsonObjectSchema)
}));
const rawChangeLogEntrySchema = object({
  stateVersion: string(),
  table: string(),
  rowKey: string(),
  op: literalUnion(SET_OP, DEL_OP, TRUNCATE_OP, RESET_OP),
  backfillingColumnVersions: string().map((val) => record(string()).parse(JSON.parse(val)))
});
class ChangeLog {
  #logRowOpStmt;
  #logRowOpWithBackfillStmt;
  #logTableWideOpStmt;
  #getRowOpStmt;
  constructor(db) {
    this.#logRowOpStmt = db.prepare(
      /*sql*/
      `
      INSERT OR REPLACE INTO "_zero.changeLog2" 
        (stateVersion, pos, "table", rowKey, op)
        VALUES (@version, @pos, @table, JSON(@rowKey), @op)
    `
    );
    this.#logRowOpWithBackfillStmt = db.prepare(
      /*sql*/
      `
      INSERT INTO "_zero.changeLog2" 
        (stateVersion, pos, "table", rowKey, op, backfillingColumnVersions)
        VALUES (@version, @pos, @table, JSON(@rowKey), @op, 
                JSON(@backfillingColumnVersions))
        ON CONFLICT ("table", rowKey) DO UPDATE 
                   SET stateVersion = excluded.stateVersion,
                                pos = excluded.pos,
                                 op = excluded.op,
          backfillingColumnVersions = json_patch(
          backfillingColumnVersions, excluded.backfillingColumnVersions)
    `
    );
    this.#logTableWideOpStmt = db.prepare(
      /*sql*/
      `
      INSERT OR REPLACE INTO "_zero.changeLog2" 
        (stateVersion, pos, "table", rowKey, op) 
        VALUES (@version, -1, @table, @version, @op)
    `
    );
    this.#getRowOpStmt = db.prepare(
      /*sql*/
      `
      SELECT * FROM "_zero.changeLog2" WHERE "table" = ? AND "rowKey" = JSON(?)
    `
    );
  }
  /**
   *
   * @param backfilled The backfilling columns for which values were set. Note
   *   that an empty list and the `undefined` value mean different things;
   *   * An empty list indicates that a backfill is in progress but no
   *     backfilling values were set. In this case, existing
   *     backfillingColumnVersions are preserved.
   *   * `undefined` indicates that there are no columns being backfilled.
   *     In this case, any vestigial `backfillingColumnVersions` value
   *     is cleared.
   */
  logSetOp(version, pos, table, row, backfilled) {
    return this.#logRowOp(version, pos, table, row, SET_OP, backfilled);
  }
  logDeleteOp(version, pos, table, row) {
    return this.#logRowOp(version, pos, table, row, DEL_OP, void 0);
  }
  getLatestRowOp(table, row) {
    const rowKey = stringify(normalizedKeyOrder(row));
    const result = this.#getRowOpStmt.get(table, rowKey);
    return result === void 0 ? void 0 : parse(result, rawChangeLogEntrySchema, "passthrough");
  }
  #logRowOp(version, pos, table, row, op, backfilled) {
    const rowKey = stringify(normalizedKeyOrder(row));
    if (backfilled === void 0) {
      this.#logRowOpStmt.run({ version, pos, table, rowKey, op });
    } else {
      const versions = {};
      for (const col of backfilled) {
        versions[col] = version;
      }
      this.#logRowOpWithBackfillStmt.run({
        version,
        pos,
        table,
        rowKey,
        op,
        backfillingColumnVersions: JSON.stringify(versions)
      });
    }
    return rowKey;
  }
  logTruncateOp(version, table) {
    this.#logTableWideOpStmt.run({ version, table, op: TRUNCATE_OP });
  }
  logResetOp(version, table) {
    this.#logTableWideOpStmt.run({ version, table, op: RESET_OP });
  }
}
export {
  CREATE_CHANGELOG_SCHEMA,
  ChangeLog,
  DEL_OP,
  RESET_OP,
  SET_OP,
  TRUNCATE_OP,
  changeLogEntrySchema
};
//# sourceMappingURL=change-log.js.map
