import {
  getVersion
} from "./chunk-FAAFWE4G.js";
import "./chunk-PZ5AY32C.js";

// src/consolidation-undo.ts
import path from "path";
import { mkdir, writeFile, access, realpath, lstat } from "fs/promises";
import { constants as fsConstants } from "fs";
var DERIVED_FROM_ENTRY_RE = /^(.+):(\d+)$/;
function parseEntry(entry) {
  if (typeof entry !== "string") return null;
  const match = entry.match(DERIVED_FROM_ENTRY_RE);
  if (!match) return null;
  return { pagePath: match[1], versionId: match[2] };
}
function isInsideDirectory(candidate, root) {
  const normRoot = path.resolve(root);
  const normCandidate = path.resolve(candidate);
  const rel = path.relative(normRoot, normCandidate);
  if (rel.length === 0) return true;
  if (rel.startsWith("..")) return false;
  if (path.isAbsolute(rel)) return false;
  return true;
}
async function isInsideDirectoryRealpath(candidate, root) {
  if (!isInsideDirectory(candidate, root)) return false;
  const rawSegments = candidate.replace(/\\/g, "/").split("/");
  if (rawSegments.some((s) => s === "..")) return false;
  let resolvedRoot;
  try {
    resolvedRoot = await realpath(path.resolve(root));
  } catch {
    return false;
  }
  const normCandidate = path.resolve(candidate);
  const normRoot = path.resolve(root);
  const relFromRoot = path.relative(normRoot, normCandidate);
  const segments = relFromRoot.length > 0 ? relFromRoot.split(path.sep) : [];
  for (let i = 0; i <= segments.length; i++) {
    const probe = i === 0 ? normRoot : path.join(normRoot, ...segments.slice(0, i));
    try {
      const st = await lstat(probe);
      if (st.isSymbolicLink() && probe !== normRoot) {
        let target;
        try {
          target = await realpath(probe);
        } catch {
          return false;
        }
        const rel = path.relative(resolvedRoot, target);
        if (rel.length === 0) continue;
        if (rel.startsWith("..") || path.isAbsolute(rel)) return false;
      }
    } catch {
    }
  }
  const parts = normCandidate.split(path.sep);
  for (let i = parts.length; i > 0; i--) {
    const probe = parts.slice(0, i).join(path.sep) || path.sep;
    try {
      const resolved = await realpath(probe);
      const trailing = parts.slice(i).join(path.sep);
      const final = trailing.length > 0 ? path.join(resolved, trailing) : resolved;
      const rel = path.relative(resolvedRoot, final);
      if (rel.length === 0) return true;
      if (rel.startsWith("..")) return false;
      if (path.isAbsolute(rel)) return false;
      return true;
    } catch {
      continue;
    }
  }
  return false;
}
var NON_ACTIVE_PREFIXES = ["archive/", "state/"];
function normalizeRelativePath(p) {
  const parts = p.replace(/\\/g, "/").split("/");
  const resolved = [];
  for (const seg of parts) {
    if (seg === "" || seg === ".") continue;
    if (seg === "..") {
      if (resolved.length > 0) resolved.pop();
    } else {
      resolved.push(seg);
    }
  }
  return resolved.join("/");
}
function isActiveMemoryRelativePath(pagePath, sidecarDir) {
  const normalized = normalizeRelativePath(pagePath);
  const prefixes = [...NON_ACTIVE_PREFIXES];
  if (sidecarDir) {
    const normSidecar = normalizeRelativePath(sidecarDir);
    prefixes.push(normSidecar + "/");
  }
  for (const prefix of prefixes) {
    if (normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)) {
      return false;
    }
  }
  return true;
}
async function isRegularFile(p) {
  try {
    const st = await lstat(p);
    return st.isFile();
  } catch {
    return false;
  }
}
async function fileExists(p) {
  try {
    await access(p, fsConstants.F_OK);
    return true;
  } catch {
    return false;
  }
}
async function runConsolidationUndo(options) {
  const { storage, memoryDir, targetPath, versioning } = options;
  const dryRun = options.dryRun === true;
  const result = {
    targetPath,
    targetArchived: false,
    restores: [],
    dryRun
  };
  if (!await isInsideDirectoryRealpath(targetPath, memoryDir)) {
    result.error = `target path ${targetPath} is outside memory directory ${memoryDir}`;
    return result;
  }
  const targetRel = path.relative(memoryDir, targetPath);
  if (!isActiveMemoryRelativePath(targetRel, versioning.sidecarDir)) {
    result.error = `target path "${targetRel}" is inside a non-active directory \u2014 refusing to operate`;
    return result;
  }
  const target = await storage.readMemoryByPath(targetPath);
  if (!target) {
    result.error = `could not load target memory at ${targetPath}`;
    return result;
  }
  const derivedFrom = target.frontmatter.derived_from;
  if (!Array.isArray(derivedFrom) || derivedFrom.length === 0) {
    result.error = "target memory has no derived_from entries \u2014 nothing to undo";
    return result;
  }
  const plans = [];
  for (const rawEntry of derivedFrom) {
    const entry = typeof rawEntry === "string" ? rawEntry : String(rawEntry);
    const parsed = parseEntry(rawEntry);
    if (!parsed) {
      plans.push({
        kind: "skip",
        restore: {
          entry,
          sourcePath: "",
          outcome: "skipped_malformed_entry",
          detail: `expected "<path>:<version>" shape`
        }
      });
      continue;
    }
    if (path.isAbsolute(parsed.pagePath)) {
      plans.push({
        kind: "skip",
        restore: {
          entry,
          sourcePath: parsed.pagePath,
          outcome: "skipped_malformed_entry",
          detail: `derived_from path must be relative, got absolute: "${parsed.pagePath}"`
        }
      });
      continue;
    }
    const sourcePath = path.join(memoryDir, parsed.pagePath);
    if (!await isInsideDirectoryRealpath(sourcePath, memoryDir)) {
      plans.push({
        kind: "skip",
        restore: {
          entry,
          sourcePath,
          outcome: "skipped_outside_memory_dir",
          detail: `resolved path escapes memory directory ${memoryDir}`
        }
      });
      continue;
    }
    let resolvedRelative = parsed.pagePath;
    try {
      const realBase = await realpath(memoryDir);
      try {
        const realSource = await realpath(sourcePath);
        const rel = path.relative(realBase, realSource);
        if (!rel.startsWith("..") && !path.isAbsolute(rel)) {
          resolvedRelative = rel.replace(/\\/g, "/");
        }
      } catch {
        const parentDir = path.dirname(sourcePath);
        try {
          const realParent = await realpath(parentDir);
          const parentRel = path.relative(realBase, realParent);
          if (!parentRel.startsWith("..") && !path.isAbsolute(parentRel)) {
            const leafName = path.basename(sourcePath);
            resolvedRelative = path.join(parentRel, leafName).replace(/\\/g, "/");
          }
        } catch {
        }
      }
    } catch {
    }
    if (!isActiveMemoryRelativePath(parsed.pagePath, versioning.sidecarDir) || !isActiveMemoryRelativePath(resolvedRelative, versioning.sidecarDir)) {
      plans.push({
        kind: "skip",
        restore: {
          entry,
          sourcePath,
          outcome: "skipped_non_active_path",
          detail: `source path "${parsed.pagePath}" is inside a non-active directory (archive/state/versions)`
        }
      });
      continue;
    }
    if (path.resolve(sourcePath) === path.resolve(targetPath)) {
      plans.push({
        kind: "skip",
        restore: {
          entry,
          sourcePath,
          outcome: "skipped_self_referential",
          detail: `derived_from entry "${entry}" resolves to the same file as the target \u2014 refusing to count as recovered`
        }
      });
      continue;
    }
    if (await isRegularFile(sourcePath)) {
      plans.push({ kind: "recovered_existing", entry, sourcePath });
      continue;
    }
    if (await fileExists(sourcePath)) {
      plans.push({
        kind: "skip",
        restore: {
          entry,
          sourcePath,
          outcome: "skipped_non_regular_file",
          detail: "source path is occupied by a non-regular-file; refusing to proceed"
        }
      });
      continue;
    }
    let snapshotContent;
    try {
      snapshotContent = await getVersion(
        sourcePath,
        parsed.versionId,
        versioning,
        memoryDir
      );
    } catch {
      plans.push({
        kind: "skip",
        restore: {
          entry,
          sourcePath,
          outcome: "skipped_snapshot_missing",
          detail: `no snapshot for version ${parsed.versionId}`
        }
      });
      continue;
    }
    plans.push({ kind: "write", entry, sourcePath, content: snapshotContent });
  }
  const skipped = plans.filter((p) => p.kind === "skip");
  if (skipped.length > 0) {
    for (const p of plans) {
      if (p.kind === "skip") {
        result.restores.push(p.restore);
      } else if (p.kind === "write") {
        result.restores.push({
          entry: p.entry,
          sourcePath: p.sourcePath,
          outcome: dryRun ? "skipped_dry_run" : "skipped_blocked_by_other_failures",
          detail: dryRun ? "would restore from snapshot (blocked by other failures)" : "snapshot available but undo aborted due to other failures"
        });
      } else {
        result.restores.push({
          entry: p.entry,
          sourcePath: p.sourcePath,
          outcome: "skipped_file_exists",
          detail: "source file already exists; no restore needed"
        });
      }
    }
    const recovered = result.restores.filter(
      (r) => r.outcome === "restored" || r.outcome === "skipped_file_exists"
    ).length;
    if (recovered === 0) {
      result.error = "no sources could be recovered (all snapshots missing or paths unsafe); target not archived to preserve data";
    } else {
      result.error = `${skipped.length} of ${plans.length} sources could not be recovered; target not archived (undo is all-or-nothing)`;
    }
    return result;
  }
  const seenSourcePaths = /* @__PURE__ */ new Set();
  const dedupedPlans = [];
  for (const p of plans) {
    if (p.kind === "write" || p.kind === "recovered_existing") {
      if (seenSourcePaths.has(p.sourcePath)) {
        dedupedPlans.push({
          kind: "skip",
          restore: {
            entry: p.kind === "write" ? p.entry : p.entry,
            sourcePath: p.sourcePath,
            outcome: "skipped_file_exists",
            detail: "duplicate derived_from entry \u2014 source already processed"
          }
        });
        continue;
      }
      seenSourcePaths.add(p.sourcePath);
    }
    dedupedPlans.push(p);
  }
  if (dryRun) {
    for (const p of dedupedPlans) {
      if (p.kind === "write") {
        result.restores.push({
          entry: p.entry,
          sourcePath: p.sourcePath,
          outcome: "skipped_dry_run",
          detail: "would restore from snapshot"
        });
      } else if (p.kind === "recovered_existing") {
        result.restores.push({
          entry: p.entry,
          sourcePath: p.sourcePath,
          outcome: "skipped_file_exists",
          detail: "source file already exists; no restore needed"
        });
      } else if (p.kind === "skip" && p.restore) {
        result.restores.push(p.restore);
      }
    }
    return result;
  }
  let writeFailed = false;
  for (const p of dedupedPlans) {
    if (p.kind === "skip") {
      if (p.restore) result.restores.push(p.restore);
      continue;
    }
    if (p.kind === "recovered_existing") {
      result.restores.push({
        entry: p.entry,
        sourcePath: p.sourcePath,
        outcome: "skipped_file_exists",
        detail: "source file already exists; no restore needed"
      });
      continue;
    }
    if (p.kind === "write") {
      if (writeFailed) {
        result.restores.push({
          entry: p.entry,
          sourcePath: p.sourcePath,
          outcome: "skipped_blocked_by_other_failures",
          detail: "a prior source write failed; skipping remaining writes to honor all-or-nothing contract"
        });
        continue;
      }
      try {
        await mkdir(path.dirname(p.sourcePath), { recursive: true });
        await writeFile(p.sourcePath, p.content, { encoding: "utf-8", flag: "wx" });
        result.restores.push({
          entry: p.entry,
          sourcePath: p.sourcePath,
          outcome: "restored"
        });
      } catch (err) {
        writeFailed = true;
        result.restores.push({
          entry: p.entry,
          sourcePath: p.sourcePath,
          outcome: "skipped_write_failed",
          detail: `write failed: ${err instanceof Error ? err.message : String(err)}`
        });
      }
    }
  }
  if (writeFailed) {
    result.error = "one or more source writes failed mid-restore; target not archived to preserve data";
    return result;
  }
  const archivedAt = await storage.archiveMemory(target, {
    actor: "consolidate-undo",
    reasonCode: "consolidation-undo"
  });
  result.targetArchived = archivedAt !== null;
  if (!result.targetArchived) {
    result.error = "sources restored successfully but archiving the consolidated target failed; inspect storage for manual cleanup";
  }
  return result;
}
function formatConsolidationUndoResult(result) {
  const lines = [];
  lines.push(`consolidate undo ${result.dryRun ? "(dry run) " : ""}\u2192 ${result.targetPath}`);
  for (const r of result.restores) {
    lines.push(`  - ${r.entry} \u2192 ${r.outcome}${r.detail ? ` (${r.detail})` : ""}`);
  }
  if (result.error) {
    lines.push(`  ERROR: ${result.error}`);
    return lines.join("\n");
  }
  lines.push(
    result.dryRun ? "  (dry run \u2014 no files were modified, target not archived)" : `  target archived: ${result.targetArchived ? "yes" : "no"}`
  );
  return lines.join("\n");
}
export {
  formatConsolidationUndoResult,
  isActiveMemoryRelativePath,
  isInsideDirectory,
  isInsideDirectoryRealpath,
  runConsolidationUndo
};
//# sourceMappingURL=consolidation-undo.js.map