import {
  createVersion
} from "./chunk-FAAFWE4G.js";
import {
  assertIsDirectoryNotSymlink,
  assertRealpathInsideRoot,
  fromPosixRelPath,
  isPathInsideRoot,
  sha256String
} from "./chunk-457A4P3L.js";
import {
  parseExportBundle
} from "./chunk-OA3L7BFR.js";
import {
  decryptCapsuleFileInMemory,
  isEncryptedCapsuleFile
} from "./chunk-KNKUID7G.js";

// src/transfer/capsule-import.ts
import { lstat, mkdir, readFile, realpath, stat, writeFile } from "fs/promises";
import path from "path";
import { createHash, randomUUID } from "crypto";
import { gunzipSync } from "zlib";
async function importCapsule(opts) {
  const archiveAbs = path.resolve(opts.archivePath);
  const rootAbs = path.resolve(opts.root);
  await assertIsDirectoryNotSymlink(rootAbs, "importCapsule", "root");
  const mode = opts.mode ?? "skip";
  if (mode !== "skip" && mode !== "overwrite" && mode !== "fork") {
    throw new Error(
      `importCapsule: unknown mode ${JSON.stringify(mode)}; expected "skip", "overwrite", or "fork"`
    );
  }
  const encrypted = await isEncryptedCapsuleFile(archiveAbs);
  let raw;
  if (encrypted) {
    if (!opts.memoryDir) {
      throw new Error(
        `importCapsule: archive is encrypted but 'memoryDir' was not provided. Pass the memory directory so the secure-store key can be retrieved, or run \`remnic secure-store unlock\` before importing.`
      );
    }
    raw = await decryptCapsuleFileInMemory(archiveAbs, opts.memoryDir);
  } else {
    raw = await readFile(archiveAbs);
  }
  const json = gunzipSync(raw).toString("utf-8");
  let parsedJson;
  try {
    parsedJson = JSON.parse(json);
  } catch (cause) {
    throw new Error(
      `importCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,
      { cause }
    );
  }
  const parsed = parseExportBundle(parsedJson);
  if (parsed.capsuleVersion !== 2) {
    throw new Error(
      "importCapsule: archive is V1; only V2 capsule archives are supported"
    );
  }
  const bundle = parsed.bundle;
  const manifest = bundle.manifest;
  const capsule = manifest.capsule;
  const manifestIndex = /* @__PURE__ */ new Map();
  for (const f of manifest.files) {
    manifestIndex.set(f.path, f);
  }
  if (manifestIndex.size !== manifest.files.length) {
    throw new Error(
      "importCapsule: manifest contains duplicate file paths"
    );
  }
  const recordPaths = /* @__PURE__ */ new Set();
  for (const rec of bundle.records) {
    if (recordPaths.has(rec.path)) {
      throw new Error(
        `importCapsule: bundle contains duplicate record path: ${rec.path}`
      );
    }
    recordPaths.add(rec.path);
  }
  const rootReal = await realpath(rootAbs).catch(() => rootAbs);
  const seenTargetPaths = /* @__PURE__ */ new Map();
  for (const rec of bundle.records) {
    const entry = manifestIndex.get(rec.path);
    if (!entry) {
      throw new Error(
        `importCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`
      );
    }
    const { sha256, bytes } = sha256String(rec.content);
    if (sha256 !== entry.sha256 || bytes !== entry.bytes) {
      throw new Error(
        `importCapsule: archive checksum mismatch for ${rec.path}: expected sha256=${entry.sha256} bytes=${entry.bytes}, got sha256=${sha256} bytes=${bytes}`
      );
    }
    if (rec.path.includes("\\")) {
      throw new Error(
        `importCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`
      );
    }
    const posixNormalized = path.posix.normalize(rec.path);
    if (rec.path.startsWith("/") || rec.path.split("/").some((seg) => seg === "..") || posixNormalized.startsWith("..") || posixNormalized.startsWith("/")) {
      throw new Error(
        `importCapsule: record path escapes target root: ${rec.path}`
      );
    }
    const targetRel = computeTargetPath(rec.path, mode, capsule.id);
    const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));
    if (!isPathInsideRoot(rootReal, targetAbs)) {
      throw new Error(
        `importCapsule: record path escapes target root: ${rec.path}`
      );
    }
    await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, "importCapsule");
    const targetLstat = await lstat(targetAbs).catch(() => null);
    if (targetLstat !== null && targetLstat.isSymbolicLink()) {
      throw new Error(
        `importCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`
      );
    }
    const dedupKey = targetAbs.toLowerCase();
    const firstSourcePath = seenTargetPaths.get(dedupKey);
    if (firstSourcePath !== void 0) {
      throw new Error(
        `importCapsule: manifest contains two entries that resolve to the same target path: "${firstSourcePath}" and "${rec.path}" both map to "${targetRel}"`
      );
    }
    seenTargetPaths.set(dedupKey, rec.path);
  }
  for (const f of manifest.files) {
    if (!recordPaths.has(f.path)) {
      throw new Error(
        `importCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`
      );
    }
  }
  const imported = [];
  const skipped = [];
  const sortedRecords = [...bundle.records].sort(
    (a, b) => a.path.localeCompare(b.path)
  );
  for (const rec of sortedRecords) {
    const targetRel = computeTargetPath(rec.path, mode, capsule.id);
    const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));
    const exists = await fileExistsAt(targetAbs);
    if ((mode === "skip" || mode === "fork") && exists) {
      skipped.push({ path: rec.path, reason: "exists" });
      continue;
    }
    let snapshotted = false;
    if (mode === "overwrite" && exists) {
      if (opts.versioning && opts.versioning.enabled) {
        const prior = await readFile(targetAbs, "utf-8").catch(() => "");
        await createVersion(
          targetAbs,
          prior,
          "manual",
          opts.versioning,
          opts.log,
          `capsule-import: ${capsule.id}`,
          rootReal
        );
        snapshotted = true;
      }
    }
    let contentToWrite = rec.content;
    let rewroteId = false;
    if (mode === "fork") {
      const forked = rewriteFrontmatterIdForFork(rec.content, capsule.id, opts.now);
      contentToWrite = forked.content;
      rewroteId = forked.rewrote;
    }
    await mkdir(path.dirname(targetAbs), { recursive: true });
    await writeFile(targetAbs, contentToWrite, "utf-8");
    imported.push({
      sourcePath: rec.path,
      targetPath: targetRel,
      snapshotted,
      rewroteId
    });
  }
  skipped.sort((a, b) => a.path.localeCompare(b.path));
  return { imported, skipped, manifest };
}
function computeTargetPath(sourcePosix, mode, capsuleId) {
  if (mode === "fork") return `forks/${capsuleId}/${sourcePosix}`;
  return sourcePosix;
}
async function fileExistsAt(absPath) {
  const st = await stat(absPath).catch(() => null);
  return st !== null && st.isFile();
}
function rewriteFrontmatterIdForFork(content, capsuleId, now) {
  const crlfMatch = /^---(\r\n|\r|\n)/.exec(content);
  const eol = crlfMatch ? crlfMatch[1] : "\n";
  const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/.exec(content);
  if (!fmMatch) return { content, rewrote: false };
  const fmBody = fmMatch[1];
  const fmTrailer = fmMatch[2];
  const fmStart = fmMatch.index;
  const fmEnd = fmStart + fmMatch[0].length;
  const idLineRe = /^id:[ \t]*([^\r\n]*)$/m;
  const idMatch = idLineRe.exec(fmBody);
  if (!idMatch) return { content, rewrote: false };
  const newId = mintForkId(capsuleId, idMatch[1]?.trim() ?? "", now);
  const replacedBody = fmBody.replace(idLineRe, `id: ${newId}`);
  const prefix = content.slice(0, fmStart);
  const tail = content.slice(fmEnd);
  const rebuilt = `${prefix}---${eol}${replacedBody}${eol}---${fmTrailer}${tail}`;
  return { content: rebuilt, rewrote: true };
}
function mintForkId(capsuleId, originalId, now) {
  const base = originalId.length > 0 ? originalId : "fork";
  if (now === void 0) {
    const suffix2 = randomUUID().slice(0, 8);
    return `${base}-fork-${capsuleId}-${suffix2}`;
  }
  const payload = JSON.stringify({ c: capsuleId, i: originalId, n: now });
  const suffix = createHash("sha256").update(payload, "utf-8").digest("hex").slice(0, 8);
  return `${base}-fork-${capsuleId}-${suffix}`;
}

export {
  importCapsule
};
//# sourceMappingURL=chunk-3KIS4VGT.js.map