import {
  PEER_ID_PATTERN,
  appendInteractionLog,
  listPeers,
  readPeerInteractionLog,
  readPeerProfile,
  writePeerProfile
} from "./chunk-4DWOBS2A.js";

// src/peers/profile-reasoner.ts
function buildPeerProfileReasonerPrompt(input) {
  const existingFields = input.existingProfile ? Object.keys(input.existingProfile.fields) : [];
  const logBlock = input.log.map((e) => {
    const session = e.sessionId ? ` session=${e.sessionId}` : "";
    return `- [${e.timestamp}] (${e.kind})${session} ${e.summary}`;
  }).join("\n");
  return [
    `You are an async peer-profile reasoner. Your job is to read recent interaction-log entries for one peer and propose 0..${input.maxFields} profile-field updates.`,
    "",
    `Peer:`,
    `  id: ${input.peer.id}`,
    `  kind: ${input.peer.kind}`,
    `  displayName: ${input.peer.displayName}`,
    "",
    `Existing profile field keys (preserve names when proposing updates that refine an existing field): ${existingFields.length > 0 ? existingFields.join(", ") : "(none yet)"}`,
    "",
    `Recent interaction log (oldest first):`,
    logBlock.length > 0 ? logBlock : "(no entries)",
    "",
    `Output a single JSON object: {"proposals": [{"field": "<stable_key>", "value": "<markdown>", "signal": "<short_label>", "note": "<optional>", "sourceSessionId": "<optional>"}]}.`,
    "",
    `Rules:`,
    `1. Only propose fields supported by evidence in the log. Do not invent.`,
    `2. Keys are short snake_case (e.g. "communication_style", "tool_patterns").`,
    `3. value is markdown. signal is a short label like "explicit_preference" or "topic_recurrence".`,
    `4. Omit fields you can't justify. Empty proposals array is valid.`,
    `5. Output JSON ONLY \u2014 no prose before or after.`
  ].join("\n");
}
function parsePeerProfileReasonerResponse(raw) {
  if (typeof raw !== "string" || raw.trim() === "") return [];
  const trimmed = raw.trim();
  const fenced = /^```(?:json)?\s*([\s\S]*?)```\s*$/u.exec(trimmed);
  const payload = fenced ? fenced[1].trim() : trimmed;
  let parsed;
  try {
    parsed = JSON.parse(payload);
  } catch {
    return [];
  }
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
    return [];
  }
  const obj = parsed;
  if (!Array.isArray(obj.proposals)) return [];
  const out = [];
  const RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
  for (const item of obj.proposals) {
    if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
    const r = item;
    if (typeof r.field !== "string" || r.field.trim() === "") continue;
    if (RESERVED_KEYS.has(r.field)) continue;
    if (typeof r.value !== "string" || r.value.trim() === "") continue;
    if (typeof r.signal !== "string" || r.signal.trim() === "") continue;
    const proposal = {
      field: r.field,
      value: r.value,
      signal: r.signal,
      ...typeof r.note === "string" && r.note.length > 0 ? { note: r.note } : {},
      ...typeof r.sourceSessionId === "string" && r.sourceSessionId.length > 0 ? { sourceSessionId: r.sourceSessionId } : {}
    };
    out.push(proposal);
  }
  return out;
}
var SYSTEM_MESSAGE = 'You are a peer-profile reasoner. Output ONLY a JSON object of the form {"proposals":[{"field":"...","value":"...","signal":"...","note":"...","sourceSessionId":"..."}]}. No prose, no fenced code block, no commentary.';
var RUN_MARKER_KIND = "peer_profile_reasoner_run";
function lastRunTimestamp(log) {
  let latest;
  for (const entry of log) {
    if (entry.kind !== RUN_MARKER_KIND) continue;
    if (latest === void 0 || entry.timestamp > latest) {
      latest = entry.timestamp;
    }
  }
  return latest;
}
function noopLogger() {
  return { debug: () => {
  }, info: () => {
  }, warn: () => {
  } };
}
async function runPeerProfileReasoner(options) {
  const log = {
    debug: options.log?.debug ?? noopLogger().debug,
    info: options.log?.info ?? noopLogger().info,
    warn: options.log?.warn ?? noopLogger().warn
  };
  const result = {
    peersConsidered: 0,
    peersProcessed: 0,
    fieldsApplied: 0,
    perPeer: []
  };
  if (options.enabled !== true) {
    log.debug("[peer-profile-reasoner] disabled \u2014 no-op");
    return result;
  }
  if (!options.llm) {
    log.warn("[peer-profile-reasoner] no LLM client supplied \u2014 skipping run");
    return result;
  }
  const minInteractions = Number.isFinite(options.minInteractions) ? Math.max(0, Math.floor(options.minInteractions)) : 0;
  const maxFields = Number.isFinite(options.maxFieldsPerRun) ? Math.max(0, Math.floor(options.maxFieldsPerRun)) : 0;
  if (maxFields === 0) {
    log.debug("[peer-profile-reasoner] maxFieldsPerRun=0 \u2014 no-op");
    return result;
  }
  const maxLogPerPeer = Number.isFinite(options.maxLogEntriesPerPeer ?? NaN) ? Math.max(1, Math.floor(options.maxLogEntriesPerPeer)) : 50;
  const now = options.now ?? /* @__PURE__ */ new Date();
  const nowIso = now.toISOString();
  let peers;
  try {
    if (options.peerIds && options.peerIds.length > 0) {
      const all = await listPeers(options.memoryDir);
      const wanted = new Set(
        options.peerIds.filter(
          (id) => typeof id === "string" && PEER_ID_PATTERN.test(id)
        )
      );
      peers = all.filter((p) => wanted.has(p.id));
    } else {
      peers = await listPeers(options.memoryDir);
    }
  } catch (err) {
    log.warn(
      `[peer-profile-reasoner] listPeers failed: ${err instanceof Error ? err.message : String(err)}`
    );
    return result;
  }
  result.peersConsidered = peers.length;
  let fieldsAppliedTotal = 0;
  for (const peer of peers) {
    if (options.signal?.aborted) {
      result.perPeer.push({
        peerId: peer.id,
        status: "skipped_aborted",
        fieldsApplied: 0,
        droppedDueToCap: 0,
        fields: []
      });
      continue;
    }
    try {
      const fullLog = await readPeerInteractionLog(
        options.memoryDir,
        peer.id
      );
      if (fullLog.length === 0) {
        result.perPeer.push({
          peerId: peer.id,
          status: "skipped_no_log",
          fieldsApplied: 0,
          droppedDueToCap: 0,
          fields: []
        });
        continue;
      }
      const lastRun = lastRunTimestamp(fullLog);
      const sinceLastRunFull = lastRun ? fullLog.filter(
        (e) => e.timestamp > lastRun && e.kind !== RUN_MARKER_KIND
      ) : fullLog.filter((e) => e.kind !== RUN_MARKER_KIND);
      if (sinceLastRunFull.length < minInteractions) {
        result.perPeer.push({
          peerId: peer.id,
          status: "skipped_below_min_interactions",
          fieldsApplied: 0,
          droppedDueToCap: 0,
          fields: []
        });
        continue;
      }
      const sinceLastRun = sinceLastRunFull.length > maxLogPerPeer ? sinceLastRunFull.slice(sinceLastRunFull.length - maxLogPerPeer) : sinceLastRunFull;
      const existingProfile = await readPeerProfile(options.memoryDir, peer.id);
      const remainingBudget = maxFields - fieldsAppliedTotal;
      if (remainingBudget <= 0) {
        result.perPeer.push({
          peerId: peer.id,
          status: "skipped_cap_reached",
          fieldsApplied: 0,
          droppedDueToCap: 0,
          fields: []
        });
        continue;
      }
      const prompt = buildPeerProfileReasonerPrompt({
        peer,
        existingProfile,
        log: sinceLastRun,
        maxFields: remainingBudget
      });
      const messages = [
        { role: "system", content: SYSTEM_MESSAGE },
        { role: "user", content: prompt }
      ];
      let response;
      try {
        response = await options.llm.chatCompletion(messages, {
          temperature: 0.2,
          maxTokens: 1500
        });
      } catch (err) {
        log.warn(
          `[peer-profile-reasoner] LLM call failed for "${peer.id}": ${err instanceof Error ? err.message : String(err)}`
        );
        result.perPeer.push({
          peerId: peer.id,
          status: "skipped_llm_unavailable",
          fieldsApplied: 0,
          droppedDueToCap: 0,
          fields: []
        });
        continue;
      }
      if (!response || typeof response.content !== "string") {
        result.perPeer.push({
          peerId: peer.id,
          status: "skipped_llm_unavailable",
          fieldsApplied: 0,
          droppedDueToCap: 0,
          fields: []
        });
        continue;
      }
      const proposals = parsePeerProfileReasonerResponse(response.content);
      if (proposals.length === 0) {
        result.perPeer.push({
          peerId: peer.id,
          status: "processed",
          fieldsApplied: 0,
          droppedDueToCap: 0,
          fields: []
        });
        continue;
      }
      const sessionIdsInWindow = new Set(
        sinceLastRun.map((e) => e.sessionId).filter((s) => typeof s === "string" && s.length > 0)
      );
      const baseFields = existingProfile ? { ...existingProfile.fields } : {};
      const baseProvenance = {};
      if (existingProfile) {
        for (const [k, list] of Object.entries(existingProfile.provenance)) {
          baseProvenance[k] = [...list];
        }
      }
      const appliedFieldsForPeer = [];
      let droppedDueToCap = 0;
      let invalidProposalSeen = false;
      for (const proposal of proposals) {
        const candidateBudget = maxFields - fieldsAppliedTotal - appliedFieldsForPeer.length;
        if (candidateBudget <= 0) {
          droppedDueToCap += 1;
          continue;
        }
        if (proposal.field === "__proto__" || proposal.field === "constructor" || proposal.field === "prototype") {
          invalidProposalSeen = true;
          continue;
        }
        if (!/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(proposal.field)) {
          invalidProposalSeen = true;
          continue;
        }
        baseFields[proposal.field] = proposal.value;
        const sourceSessionId = proposal.sourceSessionId && sessionIdsInWindow.has(proposal.sourceSessionId) ? proposal.sourceSessionId : void 0;
        const provEntry = {
          observedAt: nowIso,
          signal: proposal.signal,
          ...sourceSessionId ? { sourceSessionId } : {},
          ...proposal.note && proposal.note.length > 0 ? { note: proposal.note } : {}
        };
        const list = baseProvenance[proposal.field] ?? [];
        list.push(provEntry);
        baseProvenance[proposal.field] = list;
        appliedFieldsForPeer.push(proposal.field);
      }
      if (appliedFieldsForPeer.length === 0) {
        result.perPeer.push({
          peerId: peer.id,
          status: invalidProposalSeen ? "skipped_invalid_proposal" : droppedDueToCap > 0 ? "skipped_cap_reached" : "processed",
          fieldsApplied: 0,
          droppedDueToCap,
          fields: []
        });
        continue;
      }
      const merged = {
        peerId: peer.id,
        updatedAt: nowIso,
        fields: baseFields,
        provenance: baseProvenance
      };
      await writePeerProfile(options.memoryDir, merged);
      fieldsAppliedTotal += appliedFieldsForPeer.length;
      const wantsMarker = options.appendRunMarkerToLog ?? true;
      if (wantsMarker) {
        try {
          await appendInteractionLog(options.memoryDir, peer.id, {
            timestamp: nowIso,
            kind: RUN_MARKER_KIND,
            summary: `applied ${appliedFieldsForPeer.length} field(s) via ${options.model ?? "unknown-model"}`
          });
        } catch (err) {
          log.warn(
            `[peer-profile-reasoner] run-marker append failed for "${peer.id}": ${err instanceof Error ? err.message : String(err)}`
          );
        }
      }
      result.perPeer.push({
        peerId: peer.id,
        status: "processed",
        fieldsApplied: appliedFieldsForPeer.length,
        droppedDueToCap,
        fields: appliedFieldsForPeer
      });
      result.peersProcessed += 1;
      result.fieldsApplied = fieldsAppliedTotal;
    } catch (err) {
      log.warn(
        `[peer-profile-reasoner] error processing peer "${peer.id}": ${err instanceof Error ? err.message : String(err)}`
      );
      result.perPeer.push({
        peerId: peer.id,
        status: "error",
        fieldsApplied: 0,
        droppedDueToCap: 0,
        fields: [],
        error: err instanceof Error ? err.message : String(err)
      });
    }
  }
  return result;
}

export {
  buildPeerProfileReasonerPrompt,
  parsePeerProfileReasonerResponse,
  runPeerProfileReasoner
};
//# sourceMappingURL=chunk-SKE7JYKA.js.map