import { jsonValueSchema } from "../../../../../shared/src/bigint-json.js";
import { jsonSchema } from "../../../../../shared/src/json-schema.js";
import { readonly, literalUnion } from "../../../../../shared/src/valita.js";
import { astSchema } from "../../../../../zero-protocol/src/ast.js";
import { versionToLexi, versionFromLexi } from "../../../types/lexi-version.js";
import { majorVersionToString, stateVersionFromString } from "../../../types/state-version.js";
import { ttlClockSchema } from "../ttl-clock.js";
import { object, number, string, array, literal, record, union } from "@badrap/valita";
const cvrVersionSchema = object({
  /**
   * The database `stateVersion` with which the rows in the CVR are consistent.
   */
  stateVersion: string(),
  // LexiVersion
  /**
   * `configVersion` is subversion of `stateVersion` that is initially absent for each
   * `stateVersion`, and incremented for configuration changes that affect the contents
   * of the CVR such as:
   *
   * * query set changes
   * * query transformation changes (which may happen for changes
   *   in server-side logic or authorization policies)
   *
   * Such configuration changes are always correlated with a change to one or more
   * `/meta/...` records in the CVR, often (but not always) with corresponding
   * patches in `/patches/meta/...`.
   *
   * When the `stateVersion` moves forward, the `minorVersion` is reset to absent.
   * In this manner it behaves like the analogous concept in semantic versioning.
   */
  configVersion: number().optional()
});
const EMPTY_CVR_VERSION = {
  stateVersion: majorVersionToString(0)
};
function oneAfter(v2) {
  return v2 === null ? { stateVersion: majorVersionToString(0) } : {
    stateVersion: v2.stateVersion,
    configVersion: (v2.configVersion ?? 0) + 1
  };
}
function cmpVersions(a, b) {
  return a === null && b === null ? 0 : a === null ? -1 : b === null ? 1 : a.stateVersion < b.stateVersion ? -1 : a.stateVersion > b.stateVersion ? 1 : (a.configVersion ?? 0) - (b.configVersion ?? 0);
}
function maxVersion(a, b) {
  return !b ? a : cmpVersions(b, a) > 0 ? b : a;
}
function versionToCookie(v2) {
  return versionString(v2);
}
function versionToNullableCookie(v2) {
  return v2 === null ? null : versionToCookie(v2);
}
function cookieToVersion(cookie) {
  if (cookie === null) {
    return null;
  }
  return versionFromString(cookie);
}
object({ id: string() });
const cvrRecordSchema = object({
  /**
   * CVR records store the CVRVersion at which the record was last patched into
   * the CVR, which corresponds with a patch row that is cleaned up when the
   * record is changed (updated, deleted, and re-added in the case of rows).
   *
   * Tombstones are stored for row records but not for config records. This means
   * that "orphaned" delete patches for config records may exist, and therefore
   * scans of config patches must always run until the end of the list. On the
   * contrary, for row patches, the row record tombstones allow cleanup of delete
   * patches.
   */
  patchVersion: cvrVersionSchema
});
object({
  /** The client ID, of which there can be multiple for a client group view. */
  id: string(),
  /** The client's desired query IDs. Patch information is stored in the QueryRecord. */
  desiredQueryIDs: array(string())
});
const baseQueryRecordSchema = object({
  /** The client-specified ID used to identify this query. Typically a hash. */
  id: string(),
  /**
   * The hash of the query after server-side transformations, which include:
   *
   * * Normalization (which may differ from what the client does)
   * * Query "expansion" to include primary keys and query-execution-related columns
   * * Authorization transforms
   *
   * Transformations depend on conditions that are independent of the db state version,
   * such as server-side logic and authorization policies. As such, the version of a CVR
   * version may need to be advanced independent of db state changes. This is done
   * via the `minorVersion` counter of the CVRVersion object, which is used to account
   * for both changes to the query set and changes to query transformations (which are
   * effectively remove-old-query + add-new-query).
   *
   * Note that the transformed AST itself is **not** stored, as the result of the previous
   * transformation is not useful in and of itself. If the current transformation results in
   * a different hash than that of the transformation used for the last version of the CVR,
   * it is simply handled by invalidating the existing rows, re-executed the query with
   * the new transformation, and advancing the CVR's `minorVersion` and this query's
   * `transformationVersion`.
   *
   * Note that the transformationHash is only stored when the query has reached the "gotten"
   * state. If the query is in the "desired" but not yet "gotten" state, the field is absent.
   */
  transformationHash: string().optional(),
  /**
   * The CVR version corresponding to the `transformationHash`. This essentially tracks when
   * this version of the query was effectively added to the CVR (as opposed to the
   * `patchVersion`, which is simply when the client was notified that its query was added
   * to the gotten set). Catchup of clients from old CVR versions require executing all
   * queries with a newer `transformationVersion`.
   */
  transformationVersion: cvrVersionSchema.optional()
});
const internalQueryRecordSchema = baseQueryRecordSchema.extend({
  type: literal("internal"),
  ast: astSchema
});
const clientStateSchema = object({
  /**
   * The time at which the query was last inactivated. If this undefined or
   * missing then the query is active.
   *
   * Desired queries are always active and have an undefined inactivatedAt.
   */
  inactivatedAt: ttlClockSchema.optional(),
  /**
   * TTL, time to live in milliseconds. If the query is not updated within this
   * time. The time to live is the time after it has become inactive. Negative
   * values are treated as `'forever'`.
   *
   * We do clamp this to a maximum of 10 minutes, so that queries do not
   * live for a very long time in the CVR.
   */
  ttl: number(),
  /**
   * The version at which the client state changed (i.e. individual `patchVersion`s).
   */
  version: cvrVersionSchema
});
const externalQueryRecordSchema = baseQueryRecordSchema.extend({
  /**
   * The client state for this query, which includes the inactivatedAt, ttl and
   * version. The client state is stored in a record with the client ID as the
   * key.
   */
  clientState: record(clientStateSchema),
  // For queries, the `patchVersion` indicates when query was added to the got set,
  // and is absent if not yet gotten.
  patchVersion: cvrVersionSchema.optional()
});
const clientQueryRecordSchema = externalQueryRecordSchema.extend({
  type: literal("client"),
  /** The original AST as supplied by the client. */
  ast: astSchema
});
const customQueryRecordSchema = externalQueryRecordSchema.extend({
  type: literal("custom"),
  name: string(),
  args: readonly(array(jsonSchema))
});
union(
  clientQueryRecordSchema,
  customQueryRecordSchema,
  internalQueryRecordSchema
);
const rowIDSchema = object({
  schema: string(),
  table: string(),
  rowKey: record(jsonValueSchema)
});
cvrRecordSchema.extend({
  id: rowIDSchema,
  rowVersion: string(),
  // '_0_version' of the row
  // query hashes => refCount, or `null` for a row that was removed from the
  // view (i.e. tombstone).
  refCounts: record(number()).nullable()
});
const patchSchema = object({
  type: literalUnion("row", "query"),
  op: literalUnion("put", "del")
});
const putRowPatchSchema = patchSchema.extend({
  type: literal("row"),
  op: literal("put"),
  id: rowIDSchema,
  rowVersion: string()
  // '_0_version' of the row
});
const delRowPatchSchema = patchSchema.extend({
  type: literal("row"),
  op: literal("del"),
  id: rowIDSchema
});
union(putRowPatchSchema, delRowPatchSchema);
patchSchema.extend({
  type: literal("query"),
  id: string(),
  clientID: string().optional()
  // defined for "desired", undefined for "got"
});
function versionString(v2) {
  return v2.configVersion ? `${v2.stateVersion}:${versionToLexi(v2.configVersion)}` : v2.stateVersion;
}
function versionFromString(str) {
  const parts = str.split(":");
  const stateVersion = parts[0];
  switch (parts.length) {
    case 1: {
      stateVersionFromString(stateVersion);
      return { stateVersion };
    }
    case 2: {
      const configVersion = versionFromLexi(parts[1]);
      if (configVersion > BigInt(Number.MAX_SAFE_INTEGER)) {
        throw new Error(`minorVersion ${parts[1]} exceeds max safe integer`);
      }
      return { stateVersion, configVersion: Number(configVersion) };
    }
    default:
      throw new TypeError(`Invalid version string ${str}`);
  }
}
function queryRecordToQueryRow(clientGroupID, query) {
  switch (query.type) {
    case "internal":
      return {
        clientGroupID,
        queryHash: query.id,
        clientAST: query.ast,
        queryName: null,
        queryArgs: null,
        patchVersion: null,
        transformationHash: query.transformationHash ?? null,
        transformationVersion: maybeVersionString(query.transformationVersion),
        internal: true,
        deleted: false
        // put vs del "got" query
      };
    case "client":
      return {
        clientGroupID,
        queryHash: query.id,
        clientAST: query.ast,
        queryName: null,
        queryArgs: null,
        patchVersion: maybeVersionString(query.patchVersion),
        transformationHash: query.transformationHash ?? null,
        transformationVersion: maybeVersionString(query.transformationVersion),
        internal: null,
        deleted: false
        // put vs del "got" query
      };
    case "custom":
      return {
        clientGroupID,
        queryHash: query.id,
        clientAST: null,
        queryName: query.name,
        queryArgs: query.args,
        patchVersion: maybeVersionString(query.patchVersion),
        transformationHash: query.transformationHash ?? null,
        transformationVersion: maybeVersionString(query.transformationVersion),
        internal: null,
        deleted: false
        // put vs del "got" query
      };
  }
}
const maybeVersionString = (v2) => v2 ? versionString(v2) : null;
export {
  EMPTY_CVR_VERSION,
  baseQueryRecordSchema,
  clientQueryRecordSchema,
  cmpVersions,
  cookieToVersion,
  customQueryRecordSchema,
  cvrVersionSchema,
  delRowPatchSchema,
  internalQueryRecordSchema,
  maxVersion,
  maybeVersionString,
  oneAfter,
  patchSchema,
  putRowPatchSchema,
  queryRecordToQueryRow,
  rowIDSchema,
  versionFromString,
  versionString,
  versionToCookie,
  versionToNullableCookie
};
//# sourceMappingURL=types.js.map
