import {
  CONFIDENCE,
  PROMOTION,
  queueMemoryEmbeddingSync
} from "./chunk-JRIM7VBY.js";
import {
  and,
  eq,
  feedbackEvents,
  gte,
  inArray,
  like,
  memories,
  sql
} from "./chunk-QDA5M3AJ.js";

// src/models/dedupe.ts
function normalizeDedupeText(text) {
  return text.toLowerCase().replace(/\s+/g, " ").replace(/[\s.;:,!?`]+$/g, "").trim();
}
function memoryDedupeKey(input) {
  return [
    "memory",
    input.type,
    input.scope,
    input.repo ?? "",
    input.path_scope ?? "",
    normalizeDedupeText(input.text)
  ].join("");
}
function historySnippetDedupeKey(input) {
  return [
    "history",
    input.repo ?? "",
    input.session_id ?? "",
    input.kind,
    normalizeDedupeText(input.text)
  ].join("");
}
function activityEventDedupeKey(input) {
  if (!input.session_id) return null;
  return [
    "activity",
    input.session_id,
    input.repo ?? "",
    input.path ?? "",
    input.source,
    input.event_type,
    stableDedupeJson(stripVolatileFields(input.request ?? {})),
    stableDedupeJson(stripVolatileFields(input.result ?? {}))
  ].join("");
}
function hookCallDedupeKey(input) {
  if (!input.session_id) return null;
  return [
    "hook",
    input.session_id,
    input.agent,
    input.event,
    input.ok ? "ok" : "error",
    stableDedupeJson(stripVolatileFields(input.payload ?? {}))
  ].join("");
}
function stripVolatileFields(value) {
  if (Array.isArray(value)) {
    return value.map(stripVolatileFields);
  }
  if (!value || typeof value !== "object") {
    return value;
  }
  const out = {};
  for (const [key, entry] of Object.entries(value)) {
    if (/_at$/u.test(key) || key === "timestamp") continue;
    if (entry === void 0) continue;
    out[key] = stripVolatileFields(entry);
  }
  return out;
}
function stableDedupeJson(value) {
  if (Array.isArray(value)) {
    return `[${value.map(stableDedupeJson).join(",")}]`;
  }
  if (!value || typeof value !== "object") {
    return JSON.stringify(value);
  }
  const record = value;
  return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableDedupeJson(record[key])}`).join(",")}}`;
}

// src/models/memory.ts
import { randomUUID } from "crypto";
function statusFromConfidence(confidence) {
  if (confidence < CONFIDENCE.TRANSIENT_MAX) return "transient";
  if (confidence < CONFIDENCE.CANDIDATE_MAX) return "candidate";
  return "active";
}
function createMemory(db, input) {
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const id = randomUUID();
  const confidence = input.confidence ?? 0.35;
  const status = statusFromConfidence(confidence);
  const dedupeKey = input.dedupe === false ? null : memoryDedupeKey({
    type: input.type,
    scope: input.scope,
    repo: input.repo ?? null,
    path_scope: input.path_scope ?? null,
    text: input.text
  });
  if (dedupeKey) {
    const existing = db.select().from(memories).where(and(eq(memories.dedupe_key, dedupeKey), sql`${memories.status} != 'rejected'`)).get();
    if (existing) return existing.id;
  }
  db.insert(memories).values({
    id,
    type: input.type,
    text: input.text,
    scope: input.scope,
    path_scope: input.path_scope ?? null,
    repo: input.repo ?? null,
    status,
    confidence,
    source: input.source,
    evidence: input.evidence ?? [],
    capture_context: input.capture_context ? input.capture_context : null,
    supersedes: input.supersedes ?? null,
    dedupe_key: dedupeKey,
    created_at: now,
    updated_at: now,
    last_validated_at: null,
    last_injected_at: null,
    injection_count: 0,
    override_count: 0,
    repetition_count: 0
  }).run();
  queueMemoryEmbeddingSync(db, id);
  return id;
}
function getMemory(db, id) {
  const row = db.select().from(memories).where(eq(memories.id, id)).get();
  if (!row) return void 0;
  return rowToMemory(row);
}
function queryMemories(db, query) {
  const conditions = [];
  if (query.repo) conditions.push(eq(memories.repo, query.repo));
  if (query.status) conditions.push(eq(memories.status, query.status));
  if (query.type) conditions.push(eq(memories.type, query.type));
  if (query.scope) conditions.push(eq(memories.scope, query.scope));
  if (query.min_confidence != null)
    conditions.push(gte(memories.confidence, query.min_confidence));
  if (query.path) conditions.push(like(memories.path_scope, `%${query.path}%`));
  if (query.auto_inject != null) conditions.push(eq(memories.auto_inject, query.auto_inject));
  let statement = db.select().from(memories).$dynamic();
  if (conditions.length > 0) {
    statement = statement.where(and(...conditions));
  }
  if (query.offset != null) {
    statement = statement.offset(query.offset);
  }
  if (query.limit != null) {
    statement = statement.limit(query.limit);
  }
  const rows = statement.all();
  return rows.map(rowToMemory);
}
function listMemories(db, repo, options = {}) {
  return queryMemories(db, {
    repo,
    limit: options.limit,
    offset: options.offset
  });
}
function listRepos(db) {
  return [...new Set(
    db.select({ repo: memories.repo }).from(memories).all().map((row) => row.repo).filter((repo) => Boolean(repo))
  )].sort();
}
function promoteMemory(db, id, reason, evidence) {
  const mem = getMemory(db, id);
  if (!mem) return false;
  if (mem.status === "rejected") return false;
  let newConfidence;
  if (reason === "explicit_confirm") {
    newConfidence = Math.max(mem.confidence, PROMOTION.EXPLICIT_CONFIRM);
  } else if (reason === "repeat_correction") {
    newConfidence = Math.min(1, mem.confidence + PROMOTION.REPEAT_CORRECTION);
  } else if (reason === "review_feedback") {
    newConfidence = Math.min(1, mem.confidence + PROMOTION.REVIEW_FEEDBACK);
  } else {
    newConfidence = Math.min(1, mem.confidence + PROMOTION.PASSIVE_GAIN);
  }
  const newStatus = statusFromConfidence(newConfidence);
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const newEvidence = evidence ? [...mem.evidence, evidence] : mem.evidence;
  db.update(memories).set({
    confidence: newConfidence,
    status: newStatus,
    evidence: newEvidence,
    updated_at: now,
    last_validated_at: now
  }).where(eq(memories.id, id)).run();
  queueMemoryEmbeddingSync(db, id);
  return true;
}
function demoteMemory(db, id, reason) {
  const mem = getMemory(db, id);
  if (!mem) return false;
  const newConfidence = Math.max(0, mem.confidence - 0.3);
  const newStatus = statusFromConfidence(newConfidence);
  const now = (/* @__PURE__ */ new Date()).toISOString();
  db.update(memories).set({
    confidence: newConfidence,
    status: newStatus === "transient" ? "candidate" : newStatus,
    // don't lose it completely
    updated_at: now
  }).where(eq(memories.id, id)).run();
  queueMemoryEmbeddingSync(db, id);
  return true;
}
function demoteGlobalMemory(db, id, opts = {}) {
  const mem = getMemory(db, id);
  if (!mem) return { ok: false, reason: "not_found" };
  if (mem.scope !== "global") return { ok: false, reason: "not_global" };
  const targetRepo = opts.repo?.trim() || null;
  const now = (/* @__PURE__ */ new Date()).toISOString();
  if (targetRepo) {
    const newDedupe = memoryDedupeKey({
      type: mem.type,
      scope: "repo",
      repo: targetRepo,
      path_scope: mem.path_scope,
      text: mem.text
    });
    db.update(memories).set({
      scope: "repo",
      repo: targetRepo,
      dedupe_key: newDedupe,
      updated_at: now
    }).where(eq(memories.id, id)).run();
  } else {
    db.update(memories).set({
      status: "rejected",
      confidence: 0,
      dedupe_key: null,
      updated_at: now
    }).where(eq(memories.id, id)).run();
  }
  queueMemoryEmbeddingSync(db, id);
  const updated = getMemory(db, id);
  return { ok: true, outcome: targetRepo ? "rescoped" : "rejected", memory: updated };
}
function rejectMemory(db, id) {
  const mem = getMemory(db, id);
  if (!mem) return false;
  db.update(memories).set({
    status: "rejected",
    confidence: 0,
    dedupe_key: null,
    updated_at: (/* @__PURE__ */ new Date()).toISOString()
  }).where(eq(memories.id, id)).run();
  queueMemoryEmbeddingSync(db, id);
  return true;
}
function confirmMemory(db, id) {
  return promoteMemory(db, id, "explicit_confirm", {
    type: "session_correction",
    session: "cli",
    timestamp: (/* @__PURE__ */ new Date()).toISOString(),
    context: "user explicitly confirmed"
  });
}
function appendEvidence(db, id, evidence) {
  const mem = getMemory(db, id);
  if (!mem) return false;
  if (hasEquivalentEvidence(mem.evidence, evidence)) return true;
  const now = (/* @__PURE__ */ new Date()).toISOString();
  db.update(memories).set({
    evidence: [...mem.evidence, evidence],
    updated_at: now,
    last_validated_at: now
  }).where(eq(memories.id, id)).run();
  return true;
}
function updateMemoryCaptureContext(db, id, captureContext) {
  const mem = getMemory(db, id);
  if (!mem) return false;
  db.update(memories).set({
    capture_context: captureContext,
    updated_at: (/* @__PURE__ */ new Date()).toISOString()
  }).where(eq(memories.id, id)).run();
  return true;
}
function incrementMemoryRepetition(db, id) {
  const mem = getMemory(db, id);
  if (!mem) return false;
  db.update(memories).set({
    repetition_count: sql`repetition_count + 1`,
    updated_at: (/* @__PURE__ */ new Date()).toISOString()
  }).where(eq(memories.id, id)).run();
  return true;
}
function countDistinctCorrectionSessions(mem) {
  const sessions = /* @__PURE__ */ new Set();
  for (const entry of mem.evidence) {
    if (entry.type === "session_correction") {
      sessions.add(entry.session);
    } else if (entry.type === "repeated_correction") {
      for (const session of entry.sessions) {
        sessions.add(session);
      }
    }
  }
  return sessions.size;
}
function recordFeedback(db, memoryId, sessionId, injected, outcome) {
  const id = randomUUID();
  const now = (/* @__PURE__ */ new Date()).toISOString();
  db.insert(feedbackEvents).values({
    id,
    memory_id: memoryId,
    session_id: sessionId,
    injected,
    outcome,
    timestamp: now
  }).run();
  const mem = getMemory(db, memoryId);
  if (!mem) return id;
  const delta = outcome === "followed" ? 0.05 : outcome === "overridden" ? -0.15 : outcome === "ignored" ? -0.02 : -0.25;
  const nextConfidence = Math.max(0, Math.min(1, mem.confidence + delta));
  const nextStatus = statusFromConfidence(nextConfidence);
  if (injected) {
    db.update(memories).set({
      confidence: nextConfidence,
      status: nextStatus === "transient" ? "candidate" : nextStatus,
      updated_at: now,
      last_injected_at: now,
      injection_count: sql`injection_count + 1`,
      ...outcome === "overridden" || outcome === "contradicted" ? { override_count: sql`override_count + 1` } : {}
    }).where(eq(memories.id, memoryId)).run();
  } else {
    db.update(memories).set({
      confidence: nextConfidence,
      status: nextStatus === "transient" ? "candidate" : nextStatus,
      updated_at: now
    }).where(eq(memories.id, memoryId)).run();
  }
  return id;
}
function getMemoryFeedback(db, memoryId) {
  return db.select().from(feedbackEvents).where(eq(feedbackEvents.memory_id, memoryId)).all();
}
function getMemoryFeedbackSummaries(db, memoryIds) {
  const empty = () => ({
    followed: 0,
    overridden: 0,
    contradicted: 0,
    ignored: 0,
    resolved: 0
  });
  const result = /* @__PURE__ */ new Map();
  if (memoryIds.length === 0) return result;
  for (const id of memoryIds) result.set(id, empty());
  const rows = db.select({
    memory_id: feedbackEvents.memory_id,
    outcome: feedbackEvents.outcome,
    count: sql`count(*)`.as("count")
  }).from(feedbackEvents).where(inArray(feedbackEvents.memory_id, [...memoryIds])).groupBy(feedbackEvents.memory_id, feedbackEvents.outcome).all();
  for (const row of rows) {
    const entry = result.get(row.memory_id) ?? empty();
    if (row.outcome === "followed") entry.followed += row.count;
    else if (row.outcome === "overridden") entry.overridden += row.count;
    else if (row.outcome === "contradicted") entry.contradicted += row.count;
    else if (row.outcome === "ignored") entry.ignored += row.count;
    if (row.outcome !== "ignored") entry.resolved += row.count;
    result.set(row.memory_id, entry);
  }
  return result;
}
var FEEDBACK_MATURITY = 5;
function feedbackWeightedScore(confidence, summary) {
  const total = summary.resolved;
  const maturity = Math.min(total, FEEDBACK_MATURITY) / FEEDBACK_MATURITY;
  if (maturity === 0) return confidence;
  const positive = summary.followed;
  const negative = summary.overridden + 2 * summary.contradicted;
  const numerator = Math.max(0, positive - negative + 1);
  const denominator = Math.max(1, total + 2);
  const empirical = Math.min(1, numerator / denominator);
  return (1 - maturity) * confidence + maturity * empirical;
}
function rowToMemory(row) {
  const evidence = typeof row.evidence === "string" ? JSON.parse(row.evidence) : Array.isArray(row.evidence) ? row.evidence : [];
  const captureContext = typeof row.capture_context === "string" ? JSON.parse(row.capture_context) : row.capture_context ?? null;
  return {
    id: row.id,
    type: row.type,
    text: row.text,
    scope: row.scope,
    path_scope: row.path_scope,
    repo: row.repo,
    status: row.status,
    confidence: row.confidence,
    source: row.source,
    evidence,
    capture_context: captureContext,
    supersedes: row.supersedes,
    created_at: row.created_at,
    updated_at: row.updated_at,
    last_validated_at: row.last_validated_at,
    last_injected_at: row.last_injected_at,
    injection_count: row.injection_count,
    override_count: row.override_count,
    repetition_count: row.repetition_count,
    auto_inject: row.auto_inject
  };
}
function hasEquivalentEvidence(existing, next) {
  return existing.some((entry) => {
    if (entry.type !== next.type) return false;
    if (entry.type === "session_correction" && next.type === "session_correction") {
      return entry.session === next.session;
    }
    if (entry.type === "review_feedback" && next.type === "review_feedback") {
      return entry.reviewer === next.reviewer && entry.context === next.context;
    }
    if (entry.type === "repo_scan" && next.type === "repo_scan") {
      return entry.file === next.file;
    }
    if (entry.type === "repeated_correction" && next.type === "repeated_correction") {
      return entry.sessions.join("|") === next.sessions.join("|");
    }
    return false;
  });
}

export {
  normalizeDedupeText,
  memoryDedupeKey,
  historySnippetDedupeKey,
  activityEventDedupeKey,
  hookCallDedupeKey,
  statusFromConfidence,
  createMemory,
  getMemory,
  queryMemories,
  listMemories,
  listRepos,
  promoteMemory,
  demoteMemory,
  demoteGlobalMemory,
  rejectMemory,
  confirmMemory,
  appendEvidence,
  updateMemoryCaptureContext,
  incrementMemoryRepetition,
  countDistinctCorrectionSessions,
  recordFeedback,
  getMemoryFeedback,
  getMemoryFeedbackSummaries,
  feedbackWeightedScore
};
//# sourceMappingURL=chunk-UBPGOOAN.js.map