// Narrow session/runtime facade re-exported for memory transcript helpers.
import path from "node:path";
import { isValidAgentId, normalizeAgentId } from "@openclaw/normalization-core/agent-id";

export {
  canonicalizeMainSessionAlias,
  getRuntimeConfig,
  HEARTBEAT_PROMPT,
  HEARTBEAT_TOKEN,
  SILENT_REPLY_TOKEN,
  hasInterSessionUserProvenance,
  isCompactionCheckpointTranscriptFileName,
  isCronRunSessionKey,
  isExecCompletionEvent,
  isHeartbeatUserMessage,
  isSessionArchiveArtifactName,
  isSilentReplyPayloadText,
  isUsageCountedSessionTranscriptFileName,
  loadTranscriptEventsSync,
  listSessionEntries,
  listSessionTranscriptInstances,
  materializeSessionArchiveForRead,
  onSessionTranscriptUpdate,
  parseSqliteSessionFileMarker,
  parseUsageCountedSessionIdFromFileName,
  readTranscriptContentRevisionSync,
  readTranscriptStatsSync,
  resolveStorePath,
  resolveTranscriptSessionKeyBySessionId,
  resolveSessionAgentId,
  resolveSessionTranscriptsDirForAgent,
  stripInboundMetadata,
  stripInternalRuntimeContext,
  type SessionEntry,
  type SessionTranscriptInstance,
} from "./openclaw-runtime.js";

/** Extracts the agent id from a canonical `agents/<id>/sessions` directory path. */
export function extractAgentIdFromSessionsDir(sessionsDir: string): string | null {
  const parts = path.normalize(path.resolve(sessionsDir)).split(path.sep).filter(Boolean);
  const sessionsSegment = parts.at(-1);
  const agentId = parts.at(-2);
  const agentsSegment = parts.at(-3);
  const isWindows = process.platform === "win32";
  // Windows preserves path casing while matching canonical segments without it.
  // Reject malformed ids before normalization to prevent cross-agent aliasing.
  if (
    !sessionsSegment ||
    !agentId ||
    !agentsSegment ||
    (isWindows ? sessionsSegment.toLowerCase() : sessionsSegment) !== "sessions" ||
    (isWindows ? agentsSegment.toLowerCase() : agentsSegment) !== "agents" ||
    (isWindows && (agentId !== agentId.trim() || !isValidAgentId(agentId)))
  ) {
    return null;
  }
  return isWindows ? normalizeAgentId(agentId) : agentId;
}

/** Finds the nearest canonical sessions owner without escaping its directory. */
export function extractAgentIdFromSessionPath(absPath: string): string | null {
  let currentDir = path.dirname(path.resolve(absPath));
  while (true) {
    const currentSegment = path.basename(currentDir);
    const isSessionsDir =
      (process.platform === "win32" ? currentSegment.toLowerCase() : currentSegment) === "sessions";
    if (isSessionsDir) {
      const agentId = extractAgentIdFromSessionsDir(currentDir);
      // Nested transcript folders may also be named `sessions`; only a
      // canonical agents/<id>/sessions ancestor establishes ownership.
      if (agentId) {
        return agentId;
      }
    }
    const parentDir = path.dirname(currentDir);
    if (parentDir === currentDir) {
      return null;
    }
    currentDir = parentDir;
  }
}

/** Session-key prefix marking transcripts generated by memory dreaming runs. */
export const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-";

/** True when a session-store key belongs to a dreaming narrative run. */
export function isDreamingNarrativeSessionStoreKey(sessionKey: string): boolean {
  const trimmed = sessionKey.trim();
  if (!trimmed) {
    return false;
  }
  const firstSeparator = trimmed.indexOf(":");
  if (firstSeparator < 0) {
    return trimmed.startsWith(DREAMING_NARRATIVE_RUN_PREFIX);
  }
  const secondSeparator = trimmed.indexOf(":", firstSeparator + 1);
  const sessionSegment = secondSeparator < 0 ? trimmed : trimmed.slice(secondSeparator + 1);
  return sessionSegment.startsWith(DREAMING_NARRATIVE_RUN_PREFIX);
}
