import {
  readAllEdges
} from "./chunk-RK2Y4XOM.js";
import {
  readEdgeConfidence
} from "./chunk-2LSZVONP.js";

// src/graph-snapshot.ts
import * as path from "path";
var GRAPH_SNAPSHOT_DEFAULT_LIMIT = 500;
var GRAPH_SNAPSHOT_MAX_LIMIT = 5e3;
function normalizeGraphSnapshotLimit(raw) {
  if (raw === void 0 || raw === null) return GRAPH_SNAPSHOT_DEFAULT_LIMIT;
  if (typeof raw !== "number" || !Number.isFinite(raw) || !Number.isInteger(raw)) {
    throw new Error("graphSnapshot: limit must be a positive integer");
  }
  if (raw <= 0) {
    throw new Error("graphSnapshot: limit must be a positive integer");
  }
  if (raw > GRAPH_SNAPSHOT_MAX_LIMIT) return GRAPH_SNAPSHOT_MAX_LIMIT;
  return raw;
}
function parseGraphSnapshotSince(raw) {
  if (raw === void 0 || raw === null || raw === "") return void 0;
  const ms = Date.parse(raw);
  if (!Number.isFinite(ms)) {
    throw new Error("graphSnapshot: since must be a parseable ISO timestamp");
  }
  return ms;
}
async function buildGraphSnapshot(opts) {
  const limit = normalizeGraphSnapshotLimit(opts.request.limit);
  const sinceMs = parseGraphSnapshotSince(opts.request.since);
  const focusNodeId = opts.request.focusNodeId?.trim();
  const categoryFilter = normalizeCategoryFilter(opts.request.categories);
  const allEdges = await readAllEdges(opts.memoryDir, opts.graphConfig);
  const timeFiltered = sinceMs === void 0 ? allEdges : allEdges.filter((edge) => {
    const ts = Date.parse(edge.ts);
    return Number.isFinite(ts) && ts >= sinceMs;
  });
  const focusFiltered = focusNodeId ? timeFiltered.filter((edge) => edge.from === focusNodeId || edge.to === focusNodeId) : timeFiltered;
  const sortedEdges = [...focusFiltered].sort((a, b) => {
    const aMs = Date.parse(a.ts);
    const bMs = Date.parse(b.ts);
    if (Number.isFinite(aMs) && Number.isFinite(bMs) && aMs !== bMs) {
      return bMs - aMs;
    }
    if (a.ts !== b.ts) return a.ts < b.ts ? 1 : -1;
    const aKey = `${a.from}|${a.to}|${a.type}`;
    const bKey = `${b.from}|${b.to}|${b.type}`;
    if (aKey === bKey) return 0;
    return aKey < bKey ? -1 : 1;
  });
  const metadata = /* @__PURE__ */ new Map();
  const trimmedEdges = [];
  const ensureMetadata = async (relPath) => {
    if (metadata.has(relPath)) return metadata.get(relPath) ?? null;
    let resolved = null;
    try {
      resolved = await opts.loadNode(relPath);
    } catch {
      resolved = null;
    }
    metadata.set(relPath, resolved);
    return resolved;
  };
  for (const edge of sortedEdges) {
    if (trimmedEdges.length >= limit) break;
    if (categoryFilter !== null) {
      const fromMeta = await ensureMetadata(edge.from);
      const toMeta = await ensureMetadata(edge.to);
      if (!fromMeta || !toMeta) continue;
      if (!categoryFilter.has(fromMeta.category) || !categoryFilter.has(toMeta.category)) {
        continue;
      }
    } else {
      await ensureMetadata(edge.from);
      await ensureMetadata(edge.to);
    }
    trimmedEdges.push(edge);
  }
  const resolvedMetadata = /* @__PURE__ */ new Map();
  for (const [key, value] of metadata) {
    if (value) resolvedMetadata.set(key, value);
  }
  const nodes = buildNodeIndex(trimmedEdges, resolvedMetadata);
  const edges = trimmedEdges.map((edge) => ({
    source: edge.from,
    target: edge.to,
    kind: edge.type,
    confidence: readEdgeConfidence(edge)
  }));
  const generatedAt = (opts.now ? opts.now() : /* @__PURE__ */ new Date()).toISOString();
  return { nodes, edges, generatedAt };
}
function normalizeCategoryFilter(raw) {
  if (raw === void 0 || raw === null) return null;
  if (!Array.isArray(raw)) {
    throw new Error("graphSnapshot: categories must be an array of strings");
  }
  const cleaned = /* @__PURE__ */ new Set();
  for (const value of raw) {
    if (typeof value !== "string") {
      throw new Error("graphSnapshot: categories must be an array of strings");
    }
    const trimmed = value.trim();
    if (trimmed.length > 0) cleaned.add(trimmed);
  }
  if (cleaned.size === 0) {
    throw new Error("graphSnapshot: categories must contain at least one non-empty value");
  }
  return cleaned;
}
function buildNodeIndex(edges, metadata) {
  const aggregates = /* @__PURE__ */ new Map();
  function bump(id, edge) {
    const current = aggregates.get(id) ?? {
      score: 0,
      lastUpdatedMs: null,
      lastUpdatedIso: null
    };
    current.score += readEdgeConfidence(edge);
    const ms = Date.parse(edge.ts);
    if (Number.isFinite(ms) && (current.lastUpdatedMs === null || ms > current.lastUpdatedMs)) {
      current.lastUpdatedMs = ms;
      current.lastUpdatedIso = edge.ts;
    }
    aggregates.set(id, current);
  }
  for (const edge of edges) {
    bump(edge.from, edge);
    bump(edge.to, edge);
  }
  const ids = Array.from(aggregates.keys()).sort((a, b) => {
    const aScore = aggregates.get(a)?.score ?? 0;
    const bScore = aggregates.get(b)?.score ?? 0;
    if (aScore !== bScore) return bScore - aScore;
    if (a === b) return 0;
    return a < b ? -1 : 1;
  });
  return ids.map((id) => {
    const meta = metadata.get(id);
    const aggregate = aggregates.get(id);
    return {
      id,
      label: meta?.label ?? defaultLabelFromPath(id),
      kind: meta?.category ?? "unknown",
      score: Number((aggregate?.score ?? 0).toFixed(4)),
      lastUpdated: aggregate?.lastUpdatedIso ?? null
    };
  });
}
function defaultLabelFromPath(relPath) {
  const base = path.basename(relPath, path.extname(relPath));
  return base.length > 0 ? base : relPath;
}

export {
  GRAPH_SNAPSHOT_DEFAULT_LIMIT,
  GRAPH_SNAPSHOT_MAX_LIMIT,
  normalizeGraphSnapshotLimit,
  parseGraphSnapshotSince,
  buildGraphSnapshot
};
//# sourceMappingURL=chunk-PCUKNJAZ.js.map