import {
  SIGNAL_FILE_NAME,
  SIGNAL_RECEIPTS_FILE_NAME
} from "./chunk-OMJD6A3S.js";
import {
  LUMENFLOW_MEMORY_PATHS
} from "./chunk-4N74J3UT.js";
import {
  MS_PER_DAY
} from "./chunk-V6OJGLBA.js";
import {
  ErrorCodes,
  createError
} from "./chunk-RXRKBBSM.js";

// ../memory/dist/signal-cleanup-core.js
import fs from "fs/promises";
import path from "path";
import { createRequire } from "module";
var require2 = createRequire(import.meta.url);
var ms = require2("ms");
var DEFAULT_SIGNAL_CLEANUP_CONFIG = {
  ttl: 7 * MS_PER_DAY,
  unreadTtl: 30 * MS_PER_DAY,
  maxEntries: 500
};
function parseSignalTtl(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 isSignalExpired(signal, ttlMs, now) {
  if (!signal.created_at) {
    return false;
  }
  const createdAt = new Date(signal.created_at).getTime();
  if (Number.isNaN(createdAt)) {
    return false;
  }
  const age = now - createdAt;
  return age > ttlMs;
}
function shouldRemoveSignal(signal, config, context) {
  const { now, activeWuIds } = context;
  if (signal.wu_id && activeWuIds.has(signal.wu_id)) {
    return { remove: false, reason: "active-wu-protected" };
  }
  if (signal.read) {
    if (isSignalExpired(signal, config.ttl, now)) {
      return { remove: true, reason: "ttl-expired" };
    }
  } else {
    if (isSignalExpired(signal, config.unreadTtl, now)) {
      return { remove: true, reason: "unread-ttl-expired" };
    }
  }
  return { remove: false, reason: "within-ttl" };
}
function estimateSignalBytes(signal) {
  return JSON.stringify(signal).length + 1;
}
function getCompactionRatio(removedCount, totalCount) {
  if (totalCount === 0) {
    return 0;
  }
  return removedCount / totalCount;
}
function getSignalsPath(baseDir) {
  return path.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR, SIGNAL_FILE_NAME);
}
function getReceiptsPath(baseDir) {
  return path.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR, SIGNAL_RECEIPTS_FILE_NAME);
}
async function loadAllReceipts(baseDir) {
  const receiptsPath = getReceiptsPath(baseDir);
  try {
    const content = await fs.readFile(receiptsPath, { encoding: "utf-8" });
    const lines = content.split("\n").filter((line) => line.trim());
    return lines.map((line) => JSON.parse(line));
  } catch (err) {
    const error = err;
    if (error.code === "ENOENT") {
      return [];
    }
    throw error;
  }
}
async function writeReceipts(baseDir, receipts) {
  const receiptsPath = getReceiptsPath(baseDir);
  const content = receipts.map((r) => JSON.stringify(r)).join("\n") + (receipts.length > 0 ? "\n" : "");
  await fs.writeFile(receiptsPath, content, { encoding: "utf-8" });
}
async function loadAllSignals(baseDir) {
  const signalsPath = getSignalsPath(baseDir);
  try {
    const content = await fs.readFile(signalsPath, { encoding: "utf-8" });
    const lines = content.split("\n").filter((line) => line.trim());
    return lines.map((line) => JSON.parse(line));
  } catch (err) {
    const error = err;
    if (error.code === "ENOENT") {
      return [];
    }
    throw error;
  }
}
async function writeSignals(baseDir, signals) {
  const signalsPath = getSignalsPath(baseDir);
  const content = signals.map((s) => JSON.stringify(s)).join("\n") + (signals.length > 0 ? "\n" : "");
  await fs.writeFile(signalsPath, content, { encoding: "utf-8" });
}
async function defaultGetActiveWuIds() {
  return /* @__PURE__ */ new Set();
}
function buildCleanupConfig(options) {
  const { ttl, ttlMs: providedTtlMs, unreadTtl, unreadTtlMs: providedUnreadTtlMs, maxEntries } = options;
  let ttlMs = providedTtlMs ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.ttl;
  if (ttl && !providedTtlMs) {
    ttlMs = parseSignalTtl(ttl);
  }
  let unreadTtlMs = providedUnreadTtlMs ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.unreadTtl;
  if (unreadTtl && !providedUnreadTtlMs) {
    unreadTtlMs = parseSignalTtl(unreadTtl);
  }
  return {
    ttl: ttlMs,
    unreadTtl: unreadTtlMs,
    maxEntries: maxEntries ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.maxEntries
  };
}
function processSignalsForTtl(signals, config, context) {
  const state = {
    removedIds: [],
    retainedIds: [],
    retainedSignals: [],
    bytesFreed: 0,
    breakdown: {
      ttlExpired: 0,
      unreadTtlExpired: 0,
      countLimitExceeded: 0,
      activeWuProtected: 0
    }
  };
  for (const signal of signals) {
    const decision = shouldRemoveSignal(signal, config, context);
    processSignalDecision(signal, decision, state);
  }
  return state;
}
function processSignalDecision(signal, decision, state) {
  if (decision.remove) {
    state.removedIds.push(signal.id);
    state.bytesFreed += estimateSignalBytes(signal);
    updateBreakdownForRemoval(decision.reason, state.breakdown);
  } else {
    state.retainedIds.push(signal.id);
    state.retainedSignals.push(signal);
    if (decision.reason === "active-wu-protected") {
      state.breakdown.activeWuProtected++;
    }
  }
}
function updateBreakdownForRemoval(reason, breakdown) {
  if (reason === "ttl-expired") {
    breakdown.ttlExpired++;
  } else if (reason === "unread-ttl-expired") {
    breakdown.unreadTtlExpired++;
  }
}
function applyCountPruning(state, maxEntries) {
  if (state.retainedSignals.length <= maxEntries) {
    return;
  }
  state.retainedSignals.sort((a, b) => {
    const aTime = new Date(a.created_at).getTime();
    const bTime = new Date(b.created_at).getTime();
    return aTime - bTime;
  });
  const toRemove = state.retainedSignals.length - maxEntries;
  for (let i = 0; i < toRemove; i++) {
    const signal = state.retainedSignals[i];
    if (!signal)
      continue;
    const idIndex = state.retainedIds.indexOf(signal.id);
    if (idIndex !== -1) {
      state.retainedIds.splice(idIndex, 1);
    }
    state.removedIds.push(signal.id);
    state.bytesFreed += estimateSignalBytes(signal);
    state.breakdown.countLimitExceeded++;
  }
  state.retainedSignals.splice(0, toRemove);
}
async function cleanupSignals(baseDir, options = {}) {
  const { dryRun = false, now = Date.now(), getActiveWuIds = defaultGetActiveWuIds } = options;
  const config = buildCleanupConfig(options);
  const signals = await loadAllSignals(baseDir);
  const activeWuIds = await getActiveWuIds();
  const receipts = await loadAllReceipts(baseDir);
  const receiptSignalIds = new Set(receipts.map((r) => r.signal_id));
  for (const signal of signals) {
    if (!signal.read && receiptSignalIds.has(signal.id)) {
      signal.read = true;
    }
  }
  const state = processSignalsForTtl(signals, config, { now, activeWuIds });
  applyCountPruning(state, config.maxEntries);
  const compactionRatio = getCompactionRatio(state.removedIds.length, signals.length);
  const baseResult = {
    success: true,
    removedIds: state.removedIds,
    retainedIds: state.retainedIds,
    bytesFreed: state.bytesFreed,
    compactionRatio,
    breakdown: state.breakdown
  };
  if (dryRun) {
    return { ...baseResult, dryRun: true };
  }
  if (state.removedIds.length > 0) {
    await writeSignals(baseDir, state.retainedSignals);
    const retainedIdSet = new Set(state.retainedIds);
    const retainedReceipts = receipts.filter((r) => retainedIdSet.has(r.signal_id));
    await writeReceipts(baseDir, retainedReceipts);
  }
  return baseResult;
}

export {
  cleanupSignals
};
