// src/summary-snapshot.ts
import {
  mkdir,
  open,
  readFile,
  stat,
  unlink,
  utimes,
  writeFile
} from "fs/promises";
import path from "path";
import { z } from "zod";
var summarySnapshotSchemaVersion = 1;
var SummarySnapshotItemSchema = z.object({
  hour: z.string(),
  sessionKey: z.string(),
  bullets: z.array(z.string()),
  turnCount: z.number().int().nonnegative(),
  generatedAt: z.string()
});
var SummarySnapshotSchema = z.object({
  schemaVersion: z.number().default(summarySnapshotSchemaVersion),
  sessionKey: z.string(),
  generatedAt: z.string().datetime({ offset: true }),
  summaries: z.array(SummarySnapshotItemSchema)
});
var summarySnapshotUpserts = /* @__PURE__ */ new Map();
var summarySnapshotLockTimeoutMs = 5e3;
var summarySnapshotLockStaleMs = 3e4;
var summarySnapshotLockHeartbeatMs = Math.max(
  1e3,
  Math.floor(summarySnapshotLockStaleMs / 3)
);
function summarySnapshotPath(memoryDir, sessionKey) {
  return path.join(memoryDir, "state", "summaries", `${sessionKey}.json`);
}
function summarySnapshotLockPath(memoryDir, sessionKey) {
  return path.join(memoryDir, "state", "summaries", `${sessionKey}.lock`);
}
async function readSummarySnapshot(memoryDir, sessionKey) {
  try {
    const filePath = summarySnapshotPath(memoryDir, sessionKey);
    const raw = await readFile(filePath, "utf-8");
    const data = SummarySnapshotSchema.parse(JSON.parse(raw));
    if (data.sessionKey !== sessionKey) return null;
    return data.summaries;
  } catch {
    return null;
  }
}
async function writeSummarySnapshot(memoryDir, sessionKey, summaries) {
  const filePath = summarySnapshotPath(memoryDir, sessionKey);
  await mkdir(path.dirname(filePath), { recursive: true });
  const payload = {
    schemaVersion: summarySnapshotSchemaVersion,
    sessionKey,
    generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
    summaries: summaries.map((summary) => ({
      hour: summary.hour,
      sessionKey: summary.sessionKey,
      bullets: summary.bullets,
      turnCount: summary.turnCount,
      generatedAt: summary.generatedAt
    })).sort((a, b) => new Date(b.hour).getTime() - new Date(a.hour).getTime())
  };
  await writeFile(filePath, JSON.stringify(payload, null, 2), "utf-8");
}
async function withSummarySnapshotLock(memoryDir, sessionKey, work) {
  const previous = summarySnapshotUpserts.get(sessionKey) ?? Promise.resolve();
  let release;
  const current = new Promise((resolve) => {
    release = resolve;
  });
  const chained = previous.then(() => current);
  summarySnapshotUpserts.set(sessionKey, chained);
  await previous;
  try {
    return await withExclusiveSummarySnapshotFileLock(
      summarySnapshotLockPath(memoryDir, sessionKey),
      work
    );
  } finally {
    release();
    if (summarySnapshotUpserts.get(sessionKey) === chained) {
      summarySnapshotUpserts.delete(sessionKey);
    }
  }
}
async function upsertSummarySnapshot(memoryDir, summary) {
  await withSummarySnapshotLock(memoryDir, summary.sessionKey, async () => {
    const existing = await readSummarySnapshot(memoryDir, summary.sessionKey);
    const byHour = /* @__PURE__ */ new Map();
    for (const item of existing ?? []) {
      byHour.set(item.hour, {
        ...item,
        generatedAt: item.generatedAt || (/* @__PURE__ */ new Date()).toISOString(),
        sessionKey: summary.sessionKey
      });
    }
    byHour.set(summary.hour, summary);
    const next = Array.from(byHour.values()).sort(
      (a, b) => new Date(b.hour).getTime() - new Date(a.hour).getTime()
    );
    await writeSummarySnapshot(memoryDir, summary.sessionKey, next);
  });
}
async function withExclusiveSummarySnapshotFileLock(lockPath, callback) {
  await mkdir(path.dirname(lockPath), { recursive: true });
  const startedAt = Date.now();
  while (true) {
    try {
      const handle = await open(lockPath, "wx");
      let heartbeat = null;
      if (summarySnapshotLockHeartbeatMs > 0) {
        heartbeat = setInterval(() => {
          void utimes(lockPath, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date()).catch(() => void 0);
        }, summarySnapshotLockHeartbeatMs);
        heartbeat.unref?.();
      }
      try {
        return await callback();
      } finally {
        if (heartbeat) clearInterval(heartbeat);
        await handle.close().catch(() => void 0);
        await unlink(lockPath).catch(() => void 0);
      }
    } catch (error) {
      if (!isAlreadyExistsError(error)) throw error;
      try {
        const lockStat = await stat(lockPath);
        if (Date.now() - lockStat.mtimeMs > summarySnapshotLockStaleMs) {
          await unlink(lockPath).catch(() => void 0);
          continue;
        }
      } catch {
        continue;
      }
      if (Date.now() - startedAt > summarySnapshotLockTimeoutMs) {
        throw new Error("timed out acquiring summary snapshot lock");
      }
      await sleep(10);
    }
  }
}
function isAlreadyExistsError(error) {
  return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export {
  summarySnapshotPath,
  readSummarySnapshot,
  writeSummarySnapshot,
  upsertSummarySnapshot
};
//# sourceMappingURL=chunk-ETOW6ACV.js.map