import {
  DERIVED_FROM_MEMORY_ID_RE,
  isConsolidationOperator
} from "./chunk-G7D6GZ5J.js";
import {
  sidecarKey
} from "./chunk-FAAFWE4G.js";

// src/consolidation-provenance-check.ts
import path from "path";
import { readdir, readFile, stat } from "fs/promises";
var DERIVED_VIA_RAW_RE = /^[\t ]*derived_via:[\t ]*(.*)$/mu;
var DERIVED_FROM_RAW_RE = /^[\t ]*derived_from:[\t ]*(.*)$/mu;
function tokenizeRawBlockList(fmSlice, key) {
  const lines = fmSlice.split("\n");
  const keyRe = new RegExp(`^[\\t ]*${key}:[\\t ]*(.*)$`, "u");
  let startIdx = -1;
  for (let i = 0; i < lines.length; i++) {
    const m = lines[i].match(keyRe);
    if (m) {
      if (m[1].trim().length === 0) {
        startIdx = i + 1;
      }
      break;
    }
  }
  if (startIdx < 0) return null;
  const items = [];
  for (let i = startIdx; i < lines.length; i++) {
    const line = lines[i];
    if (!/^\s+-/.test(line)) break;
    const m = line.match(/^\s+-\s*(.*)$/u);
    if (!m) break;
    let tok = m[1].trim();
    if (tok.startsWith('"') && tok.endsWith('"') && tok.length >= 2 || tok.startsWith("'") && tok.endsWith("'") && tok.length >= 2) {
      tok = tok.slice(1, -1);
    }
    items.push(tok);
  }
  return items.length > 0 ? items : null;
}
function tokenizeRawFlowList(raw) {
  const trimmed = raw.trim();
  if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
  const inner = trimmed.slice(1, -1);
  const parts = [];
  let current = "";
  let inSingle = false;
  let inDouble = false;
  for (let i = 0; i < inner.length; i++) {
    const ch = inner[i];
    if (inDouble) {
      if (ch === "\\" && i + 1 < inner.length) {
        current += inner[++i];
        continue;
      }
      if (ch === '"') {
        inDouble = false;
        continue;
      }
      current += ch;
    } else if (inSingle) {
      if (ch === "'" && inner[i + 1] === "'") {
        current += "'";
        i++;
        continue;
      }
      if (ch === "'") {
        inSingle = false;
        continue;
      }
      current += ch;
    } else if (ch === '"') {
      inDouble = true;
    } else if (ch === "'") {
      inSingle = true;
    } else if (ch === ",") {
      parts.push(current.trim());
      current = "";
    } else {
      current += ch;
    }
  }
  if (current.trim().length > 0 || parts.length > 0) {
    parts.push(current.trim());
  }
  return parts;
}
var DERIVED_FROM_ENTRY_RE = /^(.+):(\d+)$/;
function resolveSnapshotPath(memoryDir, sidecarDir, entry) {
  const match = entry.match(DERIVED_FROM_ENTRY_RE);
  if (!match) {
    return { ok: false, reason: `malformed entry (expected "<path>:<version>")` };
  }
  const pagePath = match[1];
  const versionId = match[2];
  const ext = path.extname(pagePath) || ".md";
  const key = sidecarKey(pagePath);
  const snapshotPath = path.join(memoryDir, sidecarDir, key, `${versionId}${ext}`);
  return { ok: true, snapshotPath };
}
async function runConsolidationProvenanceCheck(options) {
  const { storage, memoryDir } = options;
  const sidecarDir = options.sidecarDir ?? ".versions";
  const report = {
    scanned: 0,
    withProvenance: 0,
    issues: []
  };
  let memories;
  try {
    memories = await storage.readAllMemories();
  } catch {
    return {
      scanned: 0,
      withProvenance: 0,
      issues: [
        {
          memoryPath: memoryDir,
          memoryId: "(unreadable)",
          kind: "derived_from_malformed_entry",
          detail: "Could not enumerate memory directory to scan provenance."
        }
      ]
    };
  }
  for (const memory of memories) {
    report.scanned += 1;
    const fm = memory.frontmatter;
    const derivedFrom = fm.derived_from;
    const derivedVia = fm.derived_via;
    let rawDerivedVia;
    let rawDerivedFrom;
    let rawDerivedViaKeyPresent = false;
    let rawDerivedFromKeyPresent = false;
    let duplicateViaKeys = false;
    let duplicateFromKeys = false;
    let viaMatchCount = 0;
    let fromMatchCount = 0;
    let fmSlice = "";
    try {
      const raw = await readFile(memory.path, "utf-8");
      const frontmatterEnd = raw.indexOf("\n---", raw.indexOf("---") + 3);
      fmSlice = frontmatterEnd > 0 ? raw.slice(0, frontmatterEnd) : raw;
      const viaMatches = [...fmSlice.matchAll(new RegExp(DERIVED_VIA_RAW_RE.source, DERIVED_VIA_RAW_RE.flags + "g"))];
      viaMatchCount = viaMatches.length;
      duplicateViaKeys = viaMatches.length > 1;
      if (viaMatches.length > 0) {
        rawDerivedViaKeyPresent = true;
        const lastVia = viaMatches[viaMatches.length - 1];
        let val = lastVia[1].trim();
        if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
          val = val.slice(1, -1);
        }
        rawDerivedVia = val;
      }
      const fromMatches = [...fmSlice.matchAll(new RegExp(DERIVED_FROM_RAW_RE.source, DERIVED_FROM_RAW_RE.flags + "g"))];
      fromMatchCount = fromMatches.length;
      duplicateFromKeys = fromMatches.length > 1;
      if (fromMatches.length > 0) {
        rawDerivedFromKeyPresent = true;
        const lastFrom = fromMatches[fromMatches.length - 1];
        rawDerivedFrom = lastFrom[1].trim();
      }
    } catch {
    }
    const hasFrom = Array.isArray(derivedFrom) && derivedFrom.length > 0;
    const hasVia = derivedVia !== void 0 && derivedVia !== null;
    const hasRawVia = rawDerivedVia !== void 0 && rawDerivedVia.length > 0;
    const hasRawMalformedFrom = rawDerivedFromKeyPresent && !hasFrom;
    const hasBlankRawVia = rawDerivedViaKeyPresent && (rawDerivedVia === void 0 || rawDerivedVia.length === 0) && !hasVia;
    if (!hasFrom && !hasVia && !hasRawVia && !hasRawMalformedFrom && !hasBlankRawVia) continue;
    report.withProvenance += 1;
    if (duplicateViaKeys) {
      report.issues.push({
        memoryPath: memory.path,
        memoryId: fm.id,
        kind: "derived_via_unknown_operator",
        detail: `raw YAML contains ${viaMatchCount} "derived_via" keys; parseFrontmatter uses the last occurrence`
      });
    }
    if (duplicateFromKeys) {
      report.issues.push({
        memoryPath: memory.path,
        memoryId: fm.id,
        kind: "derived_from_malformed_entry",
        detail: `raw YAML contains ${fromMatchCount} "derived_from" keys; parseFrontmatter uses the last occurrence`
      });
    }
    if (hasRawMalformedFrom) {
      const display = rawDerivedFrom ?? "(blank)";
      report.issues.push({
        memoryPath: memory.path,
        memoryId: fm.id,
        kind: "derived_from_malformed_entry",
        detail: `raw YAML "derived_from: ${display}" could not be parsed as a list`
      });
    }
    if (hasFrom && rawDerivedFromKeyPresent) {
      let rawList = null;
      if (rawDerivedFrom && rawDerivedFrom.length > 0) {
        rawList = tokenizeRawFlowList(rawDerivedFrom);
      }
      if (rawList === null) {
        rawList = tokenizeRawBlockList(fmSlice, "derived_from");
      }
      if (rawList !== null && rawList.length > derivedFrom.length) {
        for (const tok of rawList) {
          if (tok.length === 0) {
            report.issues.push({
              memoryPath: memory.path,
              memoryId: fm.id,
              kind: "derived_from_malformed_entry",
              detail: `raw YAML derived_from contains an empty entry (mixed list)`
            });
            continue;
          }
          if (!derivedFrom.includes(tok)) {
            if (!/^(.+):(\d+)$/u.test(tok) && !DERIVED_FROM_MEMORY_ID_RE.test(tok)) {
              report.issues.push({
                memoryPath: memory.path,
                memoryId: fm.id,
                kind: "derived_from_malformed_entry",
                detail: `raw YAML derived_from contains a malformed entry: ${JSON.stringify(tok)}`
              });
            }
          }
        }
      }
    }
    if (hasBlankRawVia) {
      report.issues.push({
        memoryPath: memory.path,
        memoryId: fm.id,
        kind: "derived_via_unknown_operator",
        detail: "raw YAML has `derived_via:` key with empty value"
      });
    }
    if (hasFrom) {
      for (const entry of derivedFrom) {
        if (derivedVia === "pattern-reinforcement" && DERIVED_FROM_MEMORY_ID_RE.test(entry)) {
          continue;
        }
        const resolved = resolveSnapshotPath(memoryDir, sidecarDir, entry);
        if (!resolved.ok) {
          report.issues.push({
            memoryPath: memory.path,
            memoryId: fm.id,
            kind: "derived_from_malformed_entry",
            detail: `${JSON.stringify(entry)}: ${resolved.reason}`
          });
          continue;
        }
        let snapshotOk = false;
        try {
          const st = await stat(resolved.snapshotPath);
          snapshotOk = st.isFile();
        } catch {
          snapshotOk = false;
        }
        if (!snapshotOk) {
          report.issues.push({
            memoryPath: memory.path,
            memoryId: fm.id,
            kind: "derived_from_missing_snapshot",
            detail: `${entry} \u2192 ${resolved.snapshotPath} (not a regular file)`
          });
        }
      }
    }
    if (hasRawVia && !isConsolidationOperator(rawDerivedVia)) {
      report.issues.push({
        memoryPath: memory.path,
        memoryId: fm.id,
        kind: "derived_via_unknown_operator",
        detail: `unknown operator: ${JSON.stringify(rawDerivedVia)}`
      });
    }
  }
  try {
    const seenPaths = new Set(memories.map((m) => m.path));
    const scanRoots = ["facts", "corrections", "procedures", "reasoning-traces"];
    for (const rootName of scanRoots) {
      const rootPath = path.join(memoryDir, rootName);
      for await (const file of walkMarkdownFiles(rootPath)) {
        if (seenPaths.has(file)) continue;
        try {
          const raw = await readFile(file, "utf-8");
          if (DERIVED_FROM_RAW_RE.test(raw) || DERIVED_VIA_RAW_RE.test(raw)) {
            report.withProvenance += 1;
            report.issues.push({
              memoryPath: file,
              memoryId: "(parse failed)",
              kind: "derived_from_malformed_entry",
              detail: "frontmatter could not be parsed by storage reader; provenance fields visible in raw YAML"
            });
          }
        } catch {
        }
      }
    }
  } catch {
  }
  return report;
}
async function* walkMarkdownFiles(root) {
  let entries;
  try {
    entries = await readdir(root, { withFileTypes: true });
  } catch {
    return;
  }
  for (const entry of entries) {
    const full = path.join(root, entry.name);
    if (entry.isDirectory()) {
      yield* walkMarkdownFiles(full);
    } else if (entry.isFile() && entry.name.endsWith(".md")) {
      yield full;
    }
  }
}

export {
  runConsolidationProvenanceCheck
};
//# sourceMappingURL=chunk-5HRY2WRF.js.map