import {
  listFilesRecursive,
  sha256File,
  toPosixRelPath,
  writeJsonFile
} from "./chunk-457A4P3L.js";
import {
  CAPSULE_ID_PATTERN,
  CapsuleBlockSchema,
  ExportBundleV2Schema,
  ExportManifestV2Schema
} from "./chunk-OA3L7BFR.js";
import {
  encryptCapsuleFile
} from "./chunk-KNKUID7G.js";

// src/transfer/capsule-export.ts
import { mkdir, readFile, stat, writeFile } from "fs/promises";
import path from "path";
import { gzipSync } from "zlib";

// src/transfer/constants.ts
var EXPORT_FORMAT = "openclaw-engram-export";
var EXPORT_SCHEMA_VERSION = 1;
var CAPSULE_SCHEMA_VERSION = 2;

// src/transfer/capsule-export.ts
var DEFAULT_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
  "node_modules",
  ".git",
  // Never export the secure-store directory: it contains the encryption
  // header (KDF params + verifier) which is security-sensitive and
  // machine-specific. The passphrase is not stored here, but including
  // the header in a capsule would let an attacker brute-force the
  // passphrase offline if the capsule is intercepted.
  ".secure-store",
  // Exclude .capsules to avoid recursive self-inclusion.
  ".capsules"
]);
var TRANSCRIPTS_DIR = "transcripts";
var PEERS_DIR = "peers";
async function exportCapsule(opts) {
  validateName(opts.name);
  const sinceMs = parseSince(opts.since);
  const includeKinds = normalizeIncludeKinds(opts.includeKinds);
  const peerFilter = normalizePeerIds(opts.peerIds);
  const transcriptsOverride = opts.includeTranscripts === true;
  const rootAbs = path.resolve(opts.root);
  await assertIsDirectory(rootAbs);
  const outDirAbs = path.resolve(opts.outDir ?? path.join(rootAbs, ".capsules"));
  if (outDirAbs === rootAbs) {
    throw new Error(
      "exportCapsule: 'outDir' must not equal 'root'. Choose a separate directory (default: <root>/.capsules) so the export does not overwrite or shadow the source tree."
    );
  }
  await mkdir(outDirAbs, { recursive: true });
  const outDirRelPosix = computeOutDirRel(rootAbs, outDirAbs);
  const filesAbs = await listFilesRecursive(rootAbs);
  const records = [];
  const manifestFiles = [];
  for (const abs of filesAbs) {
    const relPosix = toPosixRelPath(abs, rootAbs);
    if (!shouldInclude(relPosix, includeKinds, peerFilter, outDirRelPosix, transcriptsOverride)) continue;
    if (sinceMs !== null) {
      const st = await stat(abs);
      if (st.mtimeMs < sinceMs) continue;
    }
    const content = await readFile(abs, "utf-8");
    records.push({ path: relPosix, content });
    const { sha256, bytes } = await sha256File(abs);
    manifestFiles.push({ path: relPosix, sha256, bytes });
  }
  records.sort((a, b) => a.path.localeCompare(b.path));
  manifestFiles.sort((a, b) => a.path.localeCompare(b.path));
  const capsule = buildCapsuleBlock(opts.name, opts.capsule);
  const includesTranscripts = transcriptsOverride || (includeKinds ?? /* @__PURE__ */ new Set()).has(TRANSCRIPTS_DIR);
  const createdAtMs = opts.now ?? Date.now();
  const manifest = ExportManifestV2Schema.parse({
    format: EXPORT_FORMAT,
    schemaVersion: CAPSULE_SCHEMA_VERSION,
    createdAt: new Date(createdAtMs).toISOString(),
    pluginVersion: opts.pluginVersion ?? "0.0.0",
    includesTranscripts,
    files: manifestFiles,
    capsule
  });
  const bundle = ExportBundleV2Schema.parse({
    manifest,
    records
  });
  const archivePath = path.join(outDirAbs, `${opts.name}.capsule.json.gz`);
  const manifestPath = path.join(outDirAbs, `${opts.name}.manifest.json`);
  await writeJsonFile(manifestPath, manifest);
  const json = JSON.stringify(bundle);
  const gz = gzipSync(Buffer.from(json, "utf-8"));
  await writeFile(archivePath, gz);
  if (opts.encrypt === true) {
    if (!opts.memoryDir) {
      throw new Error(
        "exportCapsule: 'memoryDir' is required when 'encrypt' is true so the secure-store key can be retrieved."
      );
    }
    const { encPath } = await encryptCapsuleFile({
      sourceGzPath: archivePath,
      memoryDir: opts.memoryDir
    });
    const { unlink } = await import("fs/promises");
    await unlink(archivePath);
    return { archivePath: encPath, manifestPath, manifest, encryptedArchivePath: encPath };
  }
  return { archivePath, manifestPath, manifest, encryptedArchivePath: null };
}
function validateName(name) {
  if (typeof name !== "string" || !CAPSULE_ID_PATTERN.test(name)) {
    throw new Error(
      `exportCapsule: invalid capsule name. Expected /${CAPSULE_ID_PATTERN.source}/`
    );
  }
  if (name.length > 64) {
    throw new Error(
      "exportCapsule: invalid capsule name. Must be 64 characters or fewer."
    );
  }
}
var ISO_8601_RE = /^\d{4}-\d{2}-\d{2}(?:[Tt]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:[Zz]|[+-]\d{2}:?\d{2}))?$/;
function parseSince(since) {
  if (since === void 0) return null;
  if (typeof since !== "string" || since.trim() === "") {
    throw new Error("exportCapsule: 'since' must be a non-empty ISO-8601 string");
  }
  if (!ISO_8601_RE.test(since)) {
    throw new Error(
      `exportCapsule: 'since' is not a valid ISO-8601 timestamp: ${since}`
    );
  }
  const ms = Date.parse(since);
  if (!Number.isFinite(ms)) {
    throw new Error(
      `exportCapsule: 'since' is not a valid ISO-8601 timestamp: ${since}`
    );
  }
  assertCalendarRoundTrip(since, ms);
  return ms;
}
function isValidCapsuleSince(since) {
  try {
    parseSince(since);
    return true;
  } catch {
    return false;
  }
}
function assertCalendarRoundTrip(since, ms) {
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(since);
  if (!m) return;
  const wantY = Number(m[1]);
  const wantMo = Number(m[2]);
  const wantD = Number(m[3]);
  const offsetMatch = /([+-])(\d{2}):?(\d{2})$/.exec(since);
  let displayMs = ms;
  if (offsetMatch) {
    const sign = offsetMatch[1] === "-" ? -1 : 1;
    const offsetMin = sign * (Number(offsetMatch[2]) * 60 + Number(offsetMatch[3]));
    displayMs = ms + offsetMin * 6e4;
  }
  const dd = new Date(displayMs);
  const gotY = dd.getUTCFullYear();
  const gotMo = dd.getUTCMonth() + 1;
  const gotD = dd.getUTCDate();
  if (gotY !== wantY || gotMo !== wantMo || gotD !== wantD) {
    throw new Error(
      `exportCapsule: 'since' is not a valid ISO-8601 timestamp: ${since}`
    );
  }
}
function normalizeIncludeKinds(kinds) {
  if (kinds === void 0) return null;
  const set = /* @__PURE__ */ new Set();
  for (const raw of kinds) {
    if (typeof raw !== "string" || raw.trim() === "") {
      throw new Error("exportCapsule: 'includeKinds' entries must be non-empty strings");
    }
    if (raw.includes("/") || raw.includes("\\")) {
      throw new Error(
        `exportCapsule: 'includeKinds' entries must be top-level segment names, got: ${raw}`
      );
    }
    set.add(raw);
  }
  return set;
}
function normalizePeerIds(peerIds) {
  if (peerIds === void 0) return null;
  const set = /* @__PURE__ */ new Set();
  for (const raw of peerIds) {
    if (typeof raw !== "string" || raw.trim() === "") {
      throw new Error("exportCapsule: 'peerIds' entries must be non-empty strings");
    }
    if (raw.includes("/") || raw.includes("\\") || raw === "." || raw === "..") {
      throw new Error(
        `exportCapsule: 'peerIds' entries must be plain segment names, got: ${raw}`
      );
    }
    set.add(raw);
  }
  return set;
}
function computeOutDirRel(rootAbs, outDirAbs) {
  const rel = path.relative(rootAbs, outDirAbs);
  if (rel === "") return null;
  if (rel === ".." || rel.startsWith(`..${path.sep}`)) return null;
  if (path.isAbsolute(rel)) return null;
  return rel.split(path.sep).join("/");
}
async function assertIsDirectory(absPath) {
  const st = await stat(absPath).catch(() => null);
  if (!st || !st.isDirectory()) {
    throw new Error(
      `exportCapsule: 'root' must be an existing directory: ${absPath}`
    );
  }
}
function shouldInclude(relPosix, includeKinds, peerFilter, outDirRelPosix, transcriptsOverride = false) {
  const parts = relPosix.split("/");
  if (parts.some((p) => DEFAULT_EXCLUDE_DIRS.has(p))) return false;
  if (outDirRelPosix !== null) {
    if (relPosix === outDirRelPosix) return false;
    if (relPosix.startsWith(`${outDirRelPosix}/`)) return false;
  }
  const top = parts[0];
  if (top === TRANSCRIPTS_DIR && !transcriptsOverride && (includeKinds === null || !includeKinds.has(TRANSCRIPTS_DIR))) {
    return false;
  }
  if (includeKinds !== null) {
    if (parts.length < 2) return false;
    if (!includeKinds.has(top)) return false;
  }
  if (top === PEERS_DIR && peerFilter !== null) {
    if (peerFilter.size === 0) return false;
    if (parts.length < 2) return false;
    if (!peerFilter.has(parts[1])) return false;
  }
  return true;
}
function buildCapsuleBlock(name, override) {
  const parent = override?.parent ?? null;
  const parentCapsule = override && Object.prototype.hasOwnProperty.call(override, "parentCapsule") ? override.parentCapsule ?? null : parent?.capsuleId ?? null;
  const merged = {
    id: name,
    version: override?.version ?? "0.1.0",
    schemaVersion: override?.schemaVersion ?? "taxonomy-v1",
    parentCapsule,
    parent,
    description: override?.description ?? "",
    retrievalPolicy: override?.retrievalPolicy ?? {
      tierWeights: {},
      directAnswerEnabled: false
    },
    includes: override?.includes ?? {
      taxonomy: false,
      identityAnchors: false,
      peerProfiles: false,
      procedural: false
    }
  };
  return CapsuleBlockSchema.parse(merged);
}

export {
  EXPORT_FORMAT,
  EXPORT_SCHEMA_VERSION,
  exportCapsule,
  isValidCapsuleSince
};
//# sourceMappingURL=chunk-IYY4MCPG.js.map