import {
  DEFAULT_HALF_LIFE_MS,
  computeDecayScore
} from "./chunk-ZE473AO6.js";
import {
  MEMORY_FILE_NAME,
  loadMemory,
  loadMemoryAll
} from "./chunk-DFR4DJBM.js";
import {
  LUMENFLOW_MEMORY_PATHS
} from "./chunk-4N74J3UT.js";
import {
  ErrorCodes,
  createError
} from "./chunk-RXRKBBSM.js";

// ../memory/dist/decay/archival.js
import fs from "fs/promises";
import path from "path";
var DEFAULT_DECAY_THRESHOLD = 0.1;
var ARCHIVED_STATUS = "archived";
var PROTECTED_LIFECYCLE = "project";
function isArchived(node) {
  return node.metadata?.status === ARCHIVED_STATUS;
}
function isProtected(node) {
  return node.lifecycle === PROTECTED_LIFECYCLE;
}
function markAsArchived(node, score, threshold, now) {
  const timestamp = new Date(now).toISOString();
  return {
    ...node,
    metadata: {
      ...node.metadata,
      status: ARCHIVED_STATUS,
      archived_at: timestamp,
      decay: {
        ...node.metadata?.decay,
        score,
        reason: `Score ${score.toFixed(4)} below threshold ${threshold}`,
        computed_at: timestamp
      }
    }
  };
}
async function writeMemoryFile(baseDir, nodes) {
  const filePath = path.join(baseDir, MEMORY_FILE_NAME);
  const content = nodes.map((n) => JSON.stringify(n)).join("\n") + (nodes.length > 0 ? "\n" : "");
  await fs.writeFile(filePath, content, { encoding: "utf-8" });
}
async function archiveByDecay(baseDir, options = {}) {
  const { threshold = DEFAULT_DECAY_THRESHOLD, now = Date.now(), halfLifeMs = DEFAULT_HALF_LIFE_MS, dryRun = false } = options;
  const memory = await loadMemory(baseDir, { includeArchived: true });
  const archivedIds = [];
  const retainedIds = [];
  const skippedIds = [];
  const updatedNodes = memory.nodes.map((node) => {
    if (isArchived(node)) {
      skippedIds.push(node.id);
      return node;
    }
    if (isProtected(node)) {
      skippedIds.push(node.id);
      return node;
    }
    const score = computeDecayScore(node, { now, halfLifeMs });
    if (score < threshold) {
      archivedIds.push(node.id);
      return markAsArchived(node, score, threshold, now);
    }
    retainedIds.push(node.id);
    return node;
  });
  if (!dryRun && archivedIds.length > 0) {
    await writeMemoryFile(baseDir, updatedNodes);
  }
  return {
    archivedIds,
    retainedIds,
    skippedIds,
    totalProcessed: memory.nodes.length,
    ...dryRun ? { dryRun: true } : {}
  };
}

// ../memory/dist/mem-cleanup-core.js
import fs2 from "fs/promises";
import path2 from "path";
import { createRequire } from "module";
var require2 = createRequire(import.meta.url);
var ms = require2("ms");
var LIFECYCLE_POLICY = {
  /** Ephemeral nodes are always removed - scratch pad data */
  ephemeral: { alwaysRemove: true, requiresSummarized: false },
  /** Session nodes removed when session is closed */
  session: { alwaysRemove: false, requiresSummarized: true },
  /** WU nodes removed only when summarized_into is set */
  wu: { alwaysRemove: false, requiresSummarized: true },
  /** Project nodes are never removed - architectural knowledge */
  project: { alwaysRemove: false, requiresSummarized: false, protected: true }
};
var SENSITIVE_FLAG = "sensitive";
var ACTIVE_SESSION_STATUS = "active";
function parseTtl(ttlString) {
  if (!ttlString || typeof ttlString !== "string") {
    throw createError(ErrorCodes.INVALID_DURATION, "Invalid TTL format: TTL string is required");
  }
  const trimmed = ttlString.trim();
  if (!trimmed) {
    throw createError(ErrorCodes.INVALID_DURATION, "Invalid TTL format: TTL string is required");
  }
  const result = ms(trimmed);
  if (result == null || result <= 0) {
    throw createError(ErrorCodes.INVALID_DURATION, `Invalid TTL format: "${ttlString}" is not a valid duration`);
  }
  return result;
}
function isNodeExpired(node, ttlMs, now = Date.now()) {
  if (!node.created_at) {
    return false;
  }
  const createdAt = new Date(node.created_at).getTime();
  if (Number.isNaN(createdAt)) {
    return false;
  }
  const age = now - createdAt;
  return age > ttlMs;
}
function isActiveSession(node) {
  if (node.type !== "session") {
    return false;
  }
  if (!node.metadata) {
    return false;
  }
  return node.metadata.status === ACTIVE_SESSION_STATUS;
}
function hasSensitiveFlag(node) {
  if (!node.metadata) {
    return false;
  }
  return Object.hasOwn(node.metadata, SENSITIVE_FLAG) && node.metadata[SENSITIVE_FLAG] === true;
}
function getLifecyclePolicy(lifecycle) {
  if (!Object.hasOwn(LIFECYCLE_POLICY, lifecycle)) {
    return void 0;
  }
  return LIFECYCLE_POLICY[lifecycle];
}
function shouldRemoveNode(node, options = {}) {
  const { sessionId, ttlMs, now = Date.now() } = options;
  if (isActiveSession(node)) {
    return { remove: false, reason: "active-session-protected" };
  }
  if (hasSensitiveFlag(node)) {
    return { remove: false, reason: "sensitive-retained" };
  }
  const policy = getLifecyclePolicy(node.lifecycle);
  if (!policy) {
    return { remove: false, reason: "unknown-lifecycle" };
  }
  if (policy.protected) {
    return { remove: false, reason: "protected-lifecycle" };
  }
  if (ttlMs && isNodeExpired(node, ttlMs, now)) {
    return { remove: true, reason: "ttl-expired" };
  }
  if (policy.alwaysRemove) {
    return { remove: true, reason: "ephemeral-cleanup" };
  }
  if (node.lifecycle === "session" && sessionId && node.session_id === sessionId) {
    return { remove: true, reason: "session-closed" };
  }
  if (policy.requiresSummarized && node.metadata?.summarized_into) {
    return { remove: true, reason: "summarized-archived" };
  }
  return { remove: false, reason: "policy-retained" };
}
function estimateNodeBytes(node) {
  return JSON.stringify(node).length + 1;
}
function getCompactionRatio(removedCount, totalCount) {
  if (totalCount === 0) {
    return 0;
  }
  return removedCount / totalCount;
}
var REASON_TO_BREAKDOWN_KEY = {
  "ephemeral-cleanup": "ephemeral",
  "session-closed": "session",
  "summarized-archived": "wu",
  "sensitive-retained": "sensitive",
  "ttl-expired": "ttlExpired",
  "active-session-protected": "activeSessionProtected"
};
function updateBreakdown(breakdown, decision) {
  const key = REASON_TO_BREAKDOWN_KEY[decision.reason];
  if (key && Object.hasOwn(breakdown, key)) {
    breakdown[key]++;
  }
}
async function writeRetainedNodes(memoryDir, retainedNodes) {
  const filePath = path2.join(memoryDir, MEMORY_FILE_NAME);
  const content = retainedNodes.map((n) => JSON.stringify(n)).join("\n") + (retainedNodes.length > 0 ? "\n" : "");
  await fs2.writeFile(filePath, content, { encoding: "utf-8" });
}
async function cleanupMemory(baseDir, options = {}) {
  const { dryRun = false, sessionId, ttl, ttlMs: providedTtlMs, now = Date.now(), decay = false, decayThreshold = DEFAULT_DECAY_THRESHOLD, halfLifeMs = DEFAULT_HALF_LIFE_MS } = options;
  const memoryDir = path2.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR);
  let ttlMs = providedTtlMs;
  if (ttl && !ttlMs) {
    ttlMs = parseTtl(ttl);
  }
  let decayResult;
  if (decay) {
    decayResult = await archiveByDecay(memoryDir, {
      threshold: decayThreshold,
      now,
      halfLifeMs,
      dryRun
    });
  }
  const memory = await loadMemoryAll(memoryDir);
  const removedIds = [];
  const retainedIds = [];
  const retainedNodes = [];
  let bytesFreed = 0;
  const breakdown = {
    ephemeral: 0,
    session: 0,
    wu: 0,
    sensitive: 0,
    ttlExpired: 0,
    activeSessionProtected: 0,
    decayArchived: decayResult?.archivedIds.length ?? 0
  };
  for (const node of memory.nodes) {
    const decision = shouldRemoveNode(node, { sessionId, ttlMs, now });
    if (decision.remove) {
      removedIds.push(node.id);
      bytesFreed += estimateNodeBytes(node);
    } else {
      retainedIds.push(node.id);
      retainedNodes.push(node);
    }
    updateBreakdown(breakdown, decision);
  }
  const compactionRatio = getCompactionRatio(removedIds.length, memory.nodes.length);
  const baseResult = {
    success: true,
    removedIds,
    retainedIds,
    bytesFreed,
    compactionRatio,
    breakdown
  };
  if (ttlMs) {
    baseResult.ttlMs = ttlMs;
  }
  if (decayResult) {
    baseResult.decayResult = decayResult;
  }
  if (dryRun) {
    return { ...baseResult, dryRun: true };
  }
  if (removedIds.length > 0) {
    await writeRetainedNodes(memoryDir, retainedNodes);
  }
  return baseResult;
}

export {
  archiveByDecay,
  cleanupMemory
};
