import {
  log
} from "./chunk-2ODBA7MQ.js";

// src/temporal-supersession.ts
function normalizeSupersessionKey(raw) {
  return raw.trim().toLowerCase().replace(/[\s\-]+/g, "-").replace(/^-+|-+$/g, "");
}
function computeSupersessionKey(entityRef, attributeName) {
  if (!entityRef || typeof entityRef !== "string") return null;
  if (!attributeName || typeof attributeName !== "string") return null;
  const entity = normalizeSupersessionKey(entityRef);
  const attr = normalizeSupersessionKey(attributeName);
  if (entity.length === 0 || attr.length === 0) return null;
  return `${entity}::${attr}`;
}
function supersessionKeysForFact(spec) {
  if (!spec.entityRef) return [];
  if (!spec.structuredAttributes) return [];
  const keys = [];
  for (const attrName of Object.keys(spec.structuredAttributes)) {
    const key = computeSupersessionKey(spec.entityRef, attrName);
    if (key) keys.push(key);
  }
  return keys;
}
function lookupAttributeByNormalizedKey(attributes, rawKey) {
  const normalizedTarget = normalizeSupersessionKey(rawKey);
  for (const [k, v] of Object.entries(attributes)) {
    if (normalizeSupersessionKey(k) === normalizedTarget) return v;
  }
  return void 0;
}
function shouldSupersedeExisting(args) {
  const { candidate, newEntityRef, newAttributes, newCreatedAt, newMemoryId } = args;
  if (candidate.id === newMemoryId) return null;
  if (candidate.status && candidate.status !== "active") return null;
  if (!candidate.entityRef) return null;
  if (!candidate.structuredAttributes) return null;
  const candidateEntityNorm = normalizeSupersessionKey(candidate.entityRef);
  const newEntityNorm = normalizeSupersessionKey(newEntityRef);
  if (candidateEntityNorm !== newEntityNorm) return null;
  const candidateCreated = Date.parse(candidate.created);
  const newCreated = Date.parse(newCreatedAt);
  if (!Number.isFinite(candidateCreated) || !Number.isFinite(newCreated)) return null;
  if (candidateCreated >= newCreated) return null;
  const matchedKeys = [];
  for (const [attrName, newValue] of Object.entries(newAttributes)) {
    const candidateValue = lookupAttributeByNormalizedKey(
      candidate.structuredAttributes,
      attrName
    );
    if (candidateValue === void 0) continue;
    if (normalizeValue(String(candidateValue)) === normalizeValue(newValue)) continue;
    const key = computeSupersessionKey(newEntityRef, attrName);
    if (key) matchedKeys.push(key);
  }
  return matchedKeys.length > 0 ? { matchedKeys } : null;
}
function normalizeValue(v) {
  return v.trim().toLowerCase();
}
async function applyTemporalSupersession(args) {
  const empty = { supersededIds: [], matchedKeys: [] };
  if (!args.enabled) return empty;
  if (!args.entityRef) return empty;
  if (!args.structuredAttributes) return empty;
  if (Object.keys(args.structuredAttributes).length === 0) return empty;
  const newKeys = supersessionKeysForFact({
    entityRef: args.entityRef,
    structuredAttributes: args.structuredAttributes
  });
  if (newKeys.length === 0) return empty;
  let hotMemories;
  try {
    hotMemories = await args.storage.readAllMemories();
  } catch (err) {
    log.warn(`temporal-supersession: readAllMemories failed: ${err}`);
    return empty;
  }
  const newMemoryFile = hotMemories.find((m) => m.frontmatter.id === args.newMemoryId);
  const persistedCreatedAt = args.useCallerTimestamp ? args.createdAt : newMemoryFile?.frontmatter.created ?? args.createdAt;
  const supersededIds = [];
  const matchedKeys = /* @__PURE__ */ new Set();
  const processedIds = /* @__PURE__ */ new Set();
  let coldMemories;
  try {
    coldMemories = await args.storage.readAllColdMemories();
  } catch (err) {
    log.warn(`temporal-supersession: readAllColdMemories failed: ${err}`);
    coldMemories = [];
  }
  const allCandidates = [...hotMemories, ...coldMemories];
  for (const memory of allCandidates) {
    if (memory.frontmatter.id === args.newMemoryId) continue;
    const dedupeKey = memory.frontmatter.id ?? memory.path;
    if (processedIds.has(dedupeKey)) continue;
    const decision = shouldSupersedeExisting({
      candidate: memory.frontmatter,
      newEntityRef: args.entityRef,
      newAttributes: args.structuredAttributes,
      newCreatedAt: persistedCreatedAt,
      newMemoryId: args.newMemoryId
    });
    if (!decision) {
      processedIds.add(dedupeKey);
      continue;
    }
    try {
      const fresh = await args.storage.readMemoryByPath(memory.path);
      if (!fresh) {
        log.debug(
          `[engram] temporal supersession skipped candidate ${memory.frontmatter.id}: no longer readable at ${memory.path} \u2014 leaving id available for alternate tier`
        );
        continue;
      }
      processedIds.add(dedupeKey);
      const freshStatus = fresh.frontmatter.status ?? "active";
      if (freshStatus !== "active" || fresh.frontmatter.supersededBy) {
        log.debug(
          `[engram] temporal supersession skipped candidate ${memory.frontmatter.id}: already superseded by concurrent writer`
        );
        continue;
      }
      const oldCreatedMs = new Date(fresh.frontmatter.created).getTime();
      const newCreatedMs = new Date(persistedCreatedAt).getTime();
      const argCreatedMs = new Date(args.createdAt).getTime();
      const maxMs = Math.max(
        Number.isFinite(oldCreatedMs) ? oldCreatedMs : 0,
        Number.isFinite(newCreatedMs) ? newCreatedMs : 0,
        Number.isFinite(argCreatedMs) ? argCreatedMs : 0
      );
      const supersededAt = new Date(maxMs).toISOString();
      let invalidAtPatch;
      if (!fresh.frontmatter.invalid_at) {
        if (args.useCallerTimestamp) {
          invalidAtPatch = persistedCreatedAt;
        } else {
          const newValidAt = newMemoryFile?.frontmatter.valid_at?.trim();
          invalidAtPatch = newValidAt && newValidAt.length > 0 ? newValidAt : persistedCreatedAt;
        }
      }
      const wrote = await args.storage.writeMemoryFrontmatter(
        fresh,
        {
          status: "superseded",
          supersededBy: args.newMemoryId,
          supersededAt,
          updated: supersededAt,
          ...invalidAtPatch ? { invalid_at: invalidAtPatch } : {}
        },
        {
          actor: "temporal-supersession",
          reasonCode: "structured-attribute-update",
          relatedMemoryIds: [args.newMemoryId]
        }
      );
      if (wrote) {
        supersededIds.push(memory.frontmatter.id);
        for (const key of decision.matchedKeys) matchedKeys.add(key);
      }
    } catch (err) {
      log.warn(
        `temporal-supersession: failed to mark ${memory.frontmatter.id} superseded: ${err}`
      );
    }
  }
  if (supersededIds.length > 0) {
    log.debug(
      `temporal-supersession: marked ${supersededIds.length} memories superseded by ${args.newMemoryId} (keys=${Array.from(matchedKeys).join(",")})`
    );
  }
  return { supersededIds, matchedKeys: Array.from(matchedKeys) };
}
function shouldFilterSupersededFromRecall(frontmatter, options) {
  if (!options.enabled) return false;
  if (options.includeInRecall) return false;
  return frontmatter.status === "superseded";
}

export {
  normalizeSupersessionKey,
  computeSupersessionKey,
  supersessionKeysForFact,
  lookupAttributeByNormalizedKey,
  shouldSupersedeExisting,
  applyTemporalSupersession,
  shouldFilterSupersededFromRecall
};
//# sourceMappingURL=chunk-ZTSE2ZJ6.js.map