import {
  OPTIMISTIC_LOCK_CONFLICT_CODE,
  OPTIMISTIC_LOCK_CONFLICT_ERROR,
  OPTIMISTIC_LOCK_ENV_VAR,
  OPTIMISTIC_LOCK_HEADER_NAME
} from "./optimistic-lock-headers.js";
import { getAllOptimisticLockReaders } from "./optimistic-lock-store.js";
import { createLogger } from "../logger/index.js";
const logger = createLogger("shared").child({ component: "optimistic-lock" });
const OPTIMISTIC_LOCK_OFF_TOKENS = /* @__PURE__ */ new Set([
  "off",
  "false",
  "0",
  "no",
  "disabled",
  "none"
]);
function parseOptimisticLockEnv(raw) {
  if (raw == null) return { mode: "all" };
  const trimmed = String(raw).trim();
  if (trimmed === "") return { mode: "all" };
  const tokens = trimmed.split(",").map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0);
  if (tokens.length === 0) return { mode: "all" };
  if (tokens.some((token) => OPTIMISTIC_LOCK_OFF_TOKENS.has(token))) return { mode: "off" };
  if (tokens.includes("all")) return { mode: "all" };
  return { mode: "allowlist", entities: new Set(tokens) };
}
const defaultResolveExpectedUpdatedAt = ({ expectedFromHeader }) => expectedFromHeader;
function createGenericOptimisticLockReader(opts) {
  const idField = opts.idField ?? "id";
  const tenantField = opts.tenantField === null ? null : opts.tenantField ?? "tenantId";
  const orgField = opts.orgField === null ? null : opts.orgField ?? "organizationId";
  const softDeleteField = opts.softDeleteField === null ? null : opts.softDeleteField ?? "deletedAt";
  const updatedAtField = opts.updatedAtField ?? "updatedAt";
  const extraFilter = opts.extraFilter ?? {};
  return async (em, { resourceKind, resourceId, tenantId, organizationId }) => {
    const filter = { [idField]: resourceId };
    if (tenantField) filter[tenantField] = tenantId;
    if (orgField && organizationId) filter[orgField] = organizationId;
    if (softDeleteField) filter[softDeleteField] = null;
    for (const [key, value] of Object.entries(extraFilter)) filter[key] = value;
    try {
      const row = await em.findOne(opts.entity, filter, {
        fields: [updatedAtField]
      });
      if (!row || typeof row !== "object") return null;
      const value = row[updatedAtField];
      if (value instanceof Date) return value.toISOString();
      if (typeof value === "string" && value.length > 0) return value;
      return null;
    } catch (err) {
      logger.error("Reader query failed \u2014 optimistic locking is DISABLED for this entity until fixed; most likely a softDeleteField/tenantField/orgField filters on a column the table does not have", { resourceKind, err });
      return null;
    }
  };
}
function readHeader(headers, name) {
  const direct = headers.get(name);
  if (typeof direct === "string" && direct.trim().length > 0) return direct.trim();
  return null;
}
function normalizeIsoToken(raw) {
  const ms = Date.parse(raw);
  if (!Number.isFinite(ms)) return null;
  return new Date(ms).toISOString();
}
function buildConflictBody(currentIso, expectedIso) {
  return {
    error: OPTIMISTIC_LOCK_CONFLICT_ERROR,
    code: OPTIMISTIC_LOCK_CONFLICT_CODE,
    currentUpdatedAt: currentIso,
    expectedUpdatedAt: expectedIso
  };
}
function createOptimisticLockGuardService(opts) {
  const envValue = opts.envValue !== void 0 ? opts.envValue : process.env[OPTIMISTIC_LOCK_ENV_VAR];
  const config = parseOptimisticLockEnv(envValue);
  const resolveExpected = opts.resolveExpected ?? defaultResolveExpectedUpdatedAt;
  const debugEnabled = process.env.OM_OPTIMISTIC_LOCK_DEBUG === "1";
  function isEntityEnabled(resourceKind) {
    if (config.mode === "off") return false;
    if (config.mode === "all") return true;
    return config.entities.has(resourceKind.toLowerCase());
  }
  async function validateMutation(input) {
    if (config.mode === "off") {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    if (input.operation !== "update" && input.operation !== "delete") {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    if (!isEntityEnabled(input.resourceKind)) {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    const readers = opts.readers ?? getAllOptimisticLockReaders();
    const reader = readers[input.resourceKind];
    if (!reader) {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    const expectedRaw = readHeader(input.requestHeaders, OPTIMISTIC_LOCK_HEADER_NAME);
    const resolvedExpected = await resolveExpected({
      expectedFromHeader: expectedRaw,
      resourceKind: input.resourceKind,
      resourceId: input.resourceId
    });
    if (resolvedExpected == null) {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    const expectedIso = normalizeIsoToken(resolvedExpected);
    if (expectedIso == null) {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    const em = opts.getEm();
    const currentRaw = await reader(em, {
      resourceKind: input.resourceKind,
      resourceId: input.resourceId,
      tenantId: input.tenantId,
      organizationId: input.organizationId ?? null
    });
    if (currentRaw == null) {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    const currentIso = normalizeIsoToken(currentRaw);
    if (currentIso == null) {
      return { ok: true, shouldRunAfterSuccess: false };
    }
    if (currentIso === expectedIso) {
      if (debugEnabled) {
        logger.info("Optimistic lock match", {
          resourceKind: input.resourceKind,
          resourceId: input.resourceId,
          operation: input.operation,
          currentIso,
          expectedIso
        });
      }
      return { ok: true, shouldRunAfterSuccess: false };
    }
    if (debugEnabled) {
      logger.info("Optimistic lock conflict", {
        resourceKind: input.resourceKind,
        resourceId: input.resourceId,
        operation: input.operation,
        tenantId: input.tenantId,
        organizationId: input.organizationId ?? null,
        expectedRaw: resolvedExpected,
        expectedIso,
        currentRaw,
        currentIso
      });
    }
    return {
      ok: false,
      status: 409,
      body: buildConflictBody(currentIso, expectedIso)
    };
  }
  async function afterMutationSuccess(_input) {
  }
  function getConfig() {
    return config;
  }
  return {
    validateMutation,
    afterMutationSuccess,
    getConfig
  };
}
export {
  createGenericOptimisticLockReader,
  createOptimisticLockGuardService,
  normalizeIsoToken,
  parseOptimisticLockEnv
};
//# sourceMappingURL=optimistic-lock.js.map
