import { randomUUID } from "node:crypto";

import {
  CODE_AGENT_SESSION_LIFECYCLE_STATUSES,
  CODE_AGENT_SESSION_STATUS_ALIASES,
  codeAgentSessionLifecycleSummary,
  decorateCodeAgentSession
} from "./code-agent-session-lifecycle.ts";

export const DEFAULT_CODE_AGENT_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
export const DEFAULT_CODE_AGENT_MAX_SESSIONS = 200;
export const DEFAULT_CODE_AGENT_MAX_CONVERSATION_FACTS = 12;
export const DEFAULT_CODE_AGENT_MAX_FACT_TOOL_CALLS = 8;
export const DEFAULT_CODE_AGENT_MAX_FACT_SKILLS = 24;
export const CODE_AGENT_SESSION_STATUSES = Object.freeze([
  "creating",
  "ready",
  "busy",
  "idle",
  "timeout",
  "error",
  "canceled",
  "interrupted",
  "expired",
  "failed"
]);

export function createCodeAgentSessionRegistry(options = {}) {
  const idleTimeoutMs = positiveInteger(options.idleTimeoutMs, DEFAULT_CODE_AGENT_SESSION_IDLE_TIMEOUT_MS);
  const maxSessions = positiveInteger(options.maxSessions, DEFAULT_CODE_AGENT_MAX_SESSIONS);
  const maxConversationFacts = positiveInteger(options.maxConversationFacts, DEFAULT_CODE_AGENT_MAX_CONVERSATION_FACTS);
  const maxFactToolCalls = positiveInteger(options.maxFactToolCalls, DEFAULT_CODE_AGENT_MAX_FACT_TOOL_CALLS);
  const maxFactSkills = positiveInteger(options.maxFactSkills, DEFAULT_CODE_AGENT_MAX_FACT_SKILLS);
  const idFactory = typeof options.idFactory === "function" ? options.idFactory : () => `ses_${randomUUID()}`;
  const defaultNow = options.now;
  const sessions = new Map();
  const conversations = new Map();
  const conversationFacts = new Map();

  function acquire(params = {}) {
    const timestamp = timestampFor(params.now ?? defaultNow);
    const timestampMs = Date.parse(timestamp);
    const conversationId = requiredId(params.conversationId, "cnv");
    const requestedSessionId = optionalId(params.sessionId);
    const mappedSessionId = conversations.get(conversationId) ?? null;

    if (mappedSessionId && requestedSessionId && mappedSessionId !== requestedSessionId) {
      const mappedSession = sessions.get(mappedSessionId) ?? null;
      return blockedAcquire({
        code: "session_reuse_conflict",
        summary: `conversationId ${conversationId} is already bound to sessionId ${mappedSessionId}; requested ${requestedSessionId}.`,
        session: mappedSession,
        timestamp,
        traceId: params.traceId
      });
    }

    const effectiveSessionId = mappedSessionId || requestedSessionId || requiredId(idFactory(), "ses");
    let session = sessions.get(effectiveSessionId) ?? null;
    const reused = Boolean(session);

    if (session && sessionExpired(session, timestampMs)) {
      session.status = "expired";
      session.updatedAt = timestamp;
      session.lastTraceId = optionalId(params.traceId);
      return blockedAcquire({
        code: "session_expired",
        summary: `Code Agent runner session ${effectiveSessionId} expired after ${session.idleTimeoutMs}ms idle timeout.`,
        session,
        timestamp,
        traceId: params.traceId
      });
    }

    if (session?.status === "busy") {
      session.updatedAt = timestamp;
      session.lastTraceId = optionalId(params.traceId) ?? session.lastTraceId;
      return blockedAcquire({
        code: "session_busy",
        summary: `Code Agent runner session ${effectiveSessionId} is already busy with traceId ${session.currentTraceId ?? "unknown"}.`,
        session,
        timestamp,
        traceId: params.traceId
      });
    }

    if (session && ["expired"].includes(session.status)) {
      session.updatedAt = timestamp;
      session.lastTraceId = optionalId(params.traceId) ?? session.lastTraceId;
      return blockedAcquire({
        code: `session_${session.status}`,
        summary: `Code Agent runner session ${effectiveSessionId} is ${session.status}; create a new session before retrying.`,
        session,
        timestamp,
        traceId: params.traceId
      });
    }

    if (!session) {
      session = {
        sessionId: effectiveSessionId,
        conversationIds: new Set(),
        status: "creating",
        workspace: params.workspace ?? null,
        sandbox: params.sandbox ?? "read-only",
        runnerKind: params.runnerKind ?? "hwlab-readonly-runner",
        sessionMode: params.sessionMode ?? params.implementationType ?? "controlled-readonly-session-registry",
        capabilityLevel: params.capabilityLevel ?? "read-only-session-tools",
        implementationType: params.implementationType ?? "controlled-readonly-session-registry",
        createdAt: timestamp,
        updatedAt: timestamp,
        idleTimeoutMs,
        expiresAt: plusMs(timestampMs, idleTimeoutMs),
        lastTraceId: optionalId(params.traceId),
        currentTraceId: null,
        turn: 0,
        durable: false,
        longLivedSession: true,
        codexStdio: false,
        readOnly: true,
        writeCapable: false,
        secretMaterialStored: false
      };
      sessions.set(effectiveSessionId, session);
    }

    session.conversationIds.add(conversationId);
    session.workspace = params.workspace ?? session.workspace;
    session.sandbox = params.sandbox ?? session.sandbox;
    session.runnerKind = params.runnerKind ?? session.runnerKind;
    session.sessionMode = params.sessionMode ?? session.sessionMode;
    session.capabilityLevel = params.capabilityLevel ?? session.capabilityLevel;
    session.implementationType = params.implementationType ?? session.implementationType;
    session.status = "busy";
    session.updatedAt = timestamp;
    session.expiresAt = plusMs(timestampMs, idleTimeoutMs);
    session.lastTraceId = optionalId(params.traceId);
    session.currentTraceId = optionalId(params.traceId);
    session.turn += 1;
    conversations.set(conversationId, effectiveSessionId);
    prune();

    return {
      ok: true,
      reused,
      session: publicSession(session, { conversationId, reused, timestamp })
    };
  }

  function release(sessionId, params = {}) {
    const session = sessions.get(requiredId(sessionId, "ses")) ?? null;
    if (!session) return null;
    const timestamp = timestampFor(params.now ?? defaultNow);
    const timestampMs = Date.parse(timestamp);
    session.status = CODE_AGENT_SESSION_STATUSES.includes(params.status) ? params.status : "idle";
    session.updatedAt = timestamp;
    session.expiresAt = plusMs(timestampMs, session.idleTimeoutMs);
    session.lastTraceId = optionalId(params.traceId) ?? session.lastTraceId;
    session.currentTraceId = null;
    session.statusReason = params.statusReason ?? null;
    return publicSession(session, {
      conversationId: params.conversationId,
      reused: params.reused,
      timestamp
    });
  }

  function fail(sessionId, params = {}) {
    return release(sessionId, { ...params, status: "failed" });
  }

  function interrupt(sessionId, params = {}) {
    return release(sessionId, { ...params, status: "interrupted" });
  }

  function get(sessionId, params = {}) {
    const session = sessions.get(requiredId(sessionId, "ses")) ?? null;
    return session ? publicSession(session, params) : null;
  }

  function getByConversation(conversationId, params = {}) {
    const sessionId = conversations.get(requiredId(conversationId, "cnv"));
    return sessionId ? get(sessionId, { ...params, conversationId }) : null;
  }

  function findSession(params = {}) {
    const conversationId = optionalId(params.conversationId);
    const sessionId = optionalId(params.sessionId);
    const traceId = optionalId(params.traceId);
    const threadId = optionalId(params.threadId);
    if (sessionId) {
      const session = get(sessionId, { conversationId });
      if (session) return session;
    }
    if (conversationId) {
      const session = getByConversation(conversationId, { conversationId });
      if (session) return session;
    }
    for (const session of sessions.values()) {
      const conversationIds = [...session.conversationIds];
      if (conversationId && conversationIds.includes(conversationId)) return publicSession(session, { conversationId });
      if (traceId && (session.lastTraceId === traceId || session.currentTraceId === traceId)) return publicSession(session, { conversationId: conversationIds[0] ?? null });
      if (threadId && session.threadId === threadId) return publicSession(session, { conversationId: conversationIds[0] ?? null });
    }
    return null;
  }

  function inspect(params = {}) {
    const session = findSession(params);
    const conversationId = optionalId(params.conversationId) ?? session?.conversationId ?? session?.conversationIds?.[0] ?? null;
    const facts = conversationId ? getConversationFacts(conversationId) : null;
    const factsFound = hasConversationFacts(facts);
    const requestedTraceId = optionalId(params.traceId);
    const traceIds = uniqueStrings([
      facts?.latestTraceId,
      session?.currentTraceId,
      session?.lastTraceId,
      ...(Array.isArray(facts?.traceIds) ? facts.traceIds : [])
    ]);
    const found = Boolean(session || factsFound || traceIds.length > 0);
    return {
      ok: found,
      action: "code-agent.session.inspect",
      status: found ? "found" : "not_found",
      query: {
        conversationId: optionalId(params.conversationId),
        sessionId: optionalId(params.sessionId),
        threadId: optionalId(params.threadId),
        traceId: requestedTraceId
      },
      session,
      conversationFacts: facts,
      traceIds,
      latestTraceId: traceIds[0] ?? null,
      valuesRedacted: true,
      secretMaterialStored: false
    };
  }

  function recordFact(conversationIdValue, fact, params = {}) {
    const conversationId = requiredId(conversationIdValue, "cnv");
    const timestamp = timestampFor(params.now ?? defaultNow);
    const sanitized = sanitizeConversationFact(fact, {
      conversationId,
      timestamp,
      maxFactToolCalls,
      maxFactSkills
    });
    if (!sanitized) return getConversationFacts(conversationId);
    const existing = conversationFacts.get(conversationId) ?? {
      conversationId,
      facts: []
    };
    existing.facts.push(sanitized);
    existing.facts = existing.facts.slice(-maxConversationFacts);
    conversationFacts.set(conversationId, existing);
    return getConversationFacts(conversationId);
  }

  function getConversationFacts(conversationIdValue, params = {}) {
    const conversationId = requiredId(conversationIdValue, "cnv");
    const stored = conversationFacts.get(conversationId);
    const facts = stored?.facts ?? [];
    const session = getByConversation(conversationId, params);
    return publicConversationFacts({
      conversationId,
      session,
      facts,
      maxFactToolCalls,
      maxConversationFacts
    });
  }

  function expireIdle(params = {}) {
    const timestamp = timestampFor(params.now ?? defaultNow);
    const timestampMs = Date.parse(timestamp);
    let expired = 0;
    for (const session of sessions.values()) {
      if (!sessionExpired(session, timestampMs)) continue;
      session.status = "expired";
      session.updatedAt = timestamp;
      session.currentTraceId = null;
      expired += 1;
    }
    return expired;
  }

  function describe() {
    return {
      kind: "code-agent-session-registry",
      implementationType: "controlled-readonly-session-registry",
      status: "available",
      sessionCount: sessions.size,
      conversationFactCount: conversationFacts.size,
      maxSessions,
      maxConversationFacts,
      idleTimeoutMs,
      statuses: [...CODE_AGENT_SESSION_STATUSES],
      lifecycleStatuses: [...CODE_AGENT_SESSION_LIFECYCLE_STATUSES],
      statusAliases: { ...CODE_AGENT_SESSION_STATUS_ALIASES },
      sessionMode: "controlled-readonly-session-registry",
      recentSessions: [...sessions.values()]
        .sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))
        .slice(0, 10)
        .map((session) => publicSession(session)),
      longLivedCodexStdio: false,
      longLivedSession: true,
      durable: false,
      secretMaterialStored: false,
      valuesRedacted: true
    };
  }

  function clear() {
    sessions.clear();
    conversations.clear();
    conversationFacts.clear();
  }

  function prune() {
    if (sessions.size <= maxSessions) return;
    const sorted = [...sessions.entries()]
      .sort((a, b) => String(a[1].updatedAt).localeCompare(String(b[1].updatedAt)));
    for (const [sessionId, session] of sorted.slice(0, sessions.size - maxSessions)) {
      sessions.delete(sessionId);
      for (const conversationId of session.conversationIds) {
        if (conversations.get(conversationId) === sessionId) {
          conversations.delete(conversationId);
        }
        conversationFacts.delete(conversationId);
      }
    }
  }

  return {
    acquire,
    release,
    fail,
    interrupt,
    get,
    getByConversation,
    findSession,
    inspect,
    recordFact,
    getConversationFacts,
    expireIdle,
    describe,
    clear
  };
}

function blockedAcquire({ code, summary, session, timestamp, traceId }) {
  const publicEvidence = session
    ? publicSession(session, { timestamp, reused: true })
    : {
        status: "failed",
        updatedAt: timestamp,
        lastTraceId: optionalId(traceId),
        secretMaterialStored: false,
        valuesRedacted: true
      };
  return {
    ok: false,
    code,
    message: summary,
    session: publicEvidence,
    blocker: {
      code,
      sourceIssue: "pikasTech/HWLAB#317",
      summary
    }
  };
}

function publicSession(session, { conversationId = null, reused = null } = {}) {
  const conversationIds = [...session.conversationIds];
  return decorateCodeAgentSession({
    sessionId: session.sessionId,
    conversationId: conversationId ?? conversationIds[0] ?? null,
    conversationIds,
    status: session.status,
    workspace: session.workspace,
    sandbox: session.sandbox,
    runnerKind: session.runnerKind,
    sessionMode: session.sessionMode,
    capabilityLevel: session.capabilityLevel,
    implementationType: session.implementationType,
    createdAt: session.createdAt,
    updatedAt: session.updatedAt,
    idleTimeoutMs: session.idleTimeoutMs,
    expiresAt: session.expiresAt,
    lastTraceId: session.lastTraceId,
    currentTraceId: session.currentTraceId,
    turn: session.turn,
    reused: reused === null ? undefined : Boolean(reused),
    durable: session.durable === true,
    durableSession: session.durable === true,
    longLivedSession: session.longLivedSession === true,
    codexStdio: session.codexStdio === true,
    readOnly: session.readOnly === true,
    writeCapable: session.writeCapable === true,
    secretMaterialStored: false,
    valuesRedacted: true,
    ...(session.statusReason ? { statusReason: session.statusReason } : {})
  }, { reused });
}

function publicConversationFacts({ conversationId, session, facts, maxFactToolCalls, maxConversationFacts }) {
  const latest = facts.at(-1) ?? null;
  const latestOperational = [...facts].reverse().find((fact) => fact.kind !== "session_context") ?? latest;
  const latestSessionFact = [...facts].reverse().find((fact) => fact.sessionId && fact.sessionMode !== "provider-text-request") ?? latestOperational;
  const latestSkills = [...facts].reverse().find((fact) => fact.skills?.status && fact.skills.status !== "not_requested")?.skills ?? null;
  const latestWorkspace = [...facts].reverse().find((fact) => fact.kind !== "session_context" && fact.workspace)?.workspace ??
    [...facts].reverse().find((fact) => fact.workspace)?.workspace ??
    session?.workspace ??
    null;
  const traceIds = [...new Set(facts.map((fact) => fact.traceId).filter(Boolean))].slice(-6);
  const recentToolCalls = facts
    .flatMap((fact) => fact.toolCalls?.items ?? [])
    .slice(-maxFactToolCalls);
  return {
    conversationId,
    sessionId: session?.sessionId ?? latestSessionFact?.sessionId ?? latest?.sessionId ?? null,
    sessionStatus: session?.status ?? latestSessionFact?.sessionStatus ?? latest?.sessionStatus ?? null,
    sessionLifecycleStatus: session?.lifecycleStatus ??
      normalizeFactLifecycleStatus(latestSessionFact?.sessionLifecycleStatus ?? latest?.sessionLifecycleStatus ?? latestSessionFact?.sessionStatus ?? latest?.sessionStatus),
    sessionMode: session?.sessionMode ?? latestSessionFact?.sessionMode ?? latest?.sessionMode ?? null,
    capabilityLevel: latestOperational?.capabilityLevel ?? session?.capabilityLevel ?? latest?.capabilityLevel ?? null,
    runnerKind: latestOperational?.runnerKind ?? session?.runnerKind ?? latest?.runnerKind ?? null,
    latestProvider: latestOperational?.provider ?? latest?.provider ?? null,
    latestBackend: latestOperational?.backend ?? latest?.backend ?? null,
    workspace: latestWorkspace,
    sandbox: latestOperational?.sandbox ?? session?.sandbox ?? latest?.sandbox ?? null,
    turnCount: facts.length,
    maxFacts: maxConversationFacts,
    traceIds,
    latestTraceId: traceIds.at(-1) ?? session?.lastTraceId ?? null,
    latestSkills,
    recentToolCalls,
    facts: facts.map((fact) => ({ ...fact })),
    valuesRedacted: true,
    secretMaterialStored: false,
    summary: conversationFactsSummary({ facts, session, latestSkills, latestWorkspace, traceIds, recentToolCalls })
  };
}

function sanitizeConversationFact(fact, { conversationId, timestamp, maxFactToolCalls, maxFactSkills }) {
  if (!fact || typeof fact !== "object") return null;
  const session = fact.session && typeof fact.session === "object" ? fact.session : null;
  const runner = fact.runner && typeof fact.runner === "object" ? fact.runner : null;
  const runnerTrace = fact.runnerTrace && typeof fact.runnerTrace === "object" ? fact.runnerTrace : null;
  const toolCalls = sanitizeToolCalls(fact.toolCalls, maxFactToolCalls);
  const skills = sanitizeSkillsFact(fact.skills, maxFactSkills);
  return {
    kind: safeFactText(fact.kind ?? "runner_turn", 80),
    conversationId,
    sessionId: safeFactText(session?.sessionId ?? fact.sessionId ?? runner?.sessionId, 120),
    sessionStatus: safeFactText(session?.status ?? fact.sessionStatus, 40),
    sessionLifecycleStatus: safeFactText(
      session?.lifecycleStatus ??
        runnerTrace?.sessionLifecycleStatus ??
        fact.sessionLifecycleStatus ??
        normalizeFactLifecycleStatus(session?.status ?? fact.sessionStatus),
      40
    ),
    sessionMode: safeFactText(fact.sessionMode ?? session?.sessionMode ?? runner?.sessionMode ?? runnerTrace?.sessionMode, 120),
    sessionReused: fact.sessionReuse?.reused === true || session?.reused === true || runner?.sessionReused === true,
    turn: numberOrNull(session?.turn ?? fact.sessionReuse?.turn ?? runnerTrace?.turn ?? runner?.turn),
    recordedAt: timestamp,
    traceId: safeFactText(fact.traceId ?? runnerTrace?.traceId, 120),
    provider: safeFactText(fact.provider ?? runner?.provider, 120),
    backend: safeFactText(fact.backend ?? runner?.backend, 160),
    runnerKind: safeFactText(runner?.kind ?? runnerTrace?.runnerKind ?? fact.runnerKind, 120),
    workspace: safeFactText(fact.workspace ?? runner?.workspace ?? runnerTrace?.workspace ?? session?.workspace, 240),
    sandbox: safeFactText(fact.sandbox ?? runner?.sandbox ?? runnerTrace?.sandbox ?? session?.sandbox, 120),
    capabilityLevel: safeFactText(fact.capabilityLevel ?? runner?.capabilityLevel ?? session?.capabilityLevel, 120),
    toolCalls,
    skills,
    runnerTrace: sanitizeRunnerTrace(runnerTrace),
    partialContext: sanitizePartialContext(fact.partialContext ?? fact.partialConversationContext ?? runnerTrace?.partialContext),
    valuesRedacted: true,
    secretMaterialStored: false
  };
}

function sanitizePartialContext(value) {
  if (!value || typeof value !== "object") return null;
  const assistantMessages = Array.isArray(value.assistantMessages)
    ? value.assistantMessages.map((item) => safeFactText(item, 240)).filter(Boolean).slice(-4)
    : [];
  const toolEvidence = Array.isArray(value.toolEvidence)
    ? value.toolEvidence.map((item) => safeFactText(item, 200)).filter(Boolean).slice(-6)
    : [];
  const summary = safeFactText(value.summary, 400);
  if (!summary && assistantMessages.length === 0 && toolEvidence.length === 0) return null;
  return {
    status: safeFactText(value.status ?? "partial", 40) ?? "partial",
    summary,
    assistantMessages,
    toolEvidence,
    valuesRedacted: true
  };
}

function sanitizeToolCalls(toolCalls, limit) {
  const items = Array.isArray(toolCalls)
    ? toolCalls.slice(0, limit).map((toolCall) => ({
        name: safeFactText(toolCall?.name, 120),
        type: safeFactText(toolCall?.type, 80),
        status: safeFactText(toolCall?.status, 40),
        route: safeFactText(toolCall?.route ?? toolCall?.hwlabApi?.route, 120),
        method: safeFactText(toolCall?.method, 16),
        traceId: safeFactText(toolCall?.traceId, 120),
        outputTruncated: toolCall?.outputTruncated === true
      })).filter((toolCall) => toolCall.name || toolCall.status || toolCall.route)
    : [];
  return {
    count: Array.isArray(toolCalls) ? toolCalls.length : 0,
    items,
    truncated: Array.isArray(toolCalls) && toolCalls.length > items.length
  };
}

function sanitizeSkillsFact(skills, limit) {
  if (!skills || typeof skills !== "object") {
    return {
      status: "not_requested",
      count: 0,
      totalCount: 0,
      names: [],
      blockers: [],
      truncated: false
    };
  }
  const items = Array.isArray(skills.items) ? skills.items : [];
  const names = items
    .map((item) => safeFactText(item?.name, 120))
    .filter(Boolean)
    .slice(0, limit);
  return {
    status: safeFactText(skills.status ?? "unknown", 40),
    count: numberOrNull(skills.count ?? items.length) ?? items.length,
    totalCount: numberOrNull(skills.totalCount ?? skills.count ?? items.length) ?? items.length,
    names,
    blockers: Array.isArray(skills.blockers)
      ? skills.blockers.map((blocker) => safeFactText(blocker?.code, 120)).filter(Boolean).slice(0, 6)
      : [],
    truncated: skills.truncated === true || items.length > names.length
  };
}

function sanitizeRunnerTrace(runnerTrace) {
  if (!runnerTrace || typeof runnerTrace !== "object") return null;
  return {
    traceId: safeFactText(runnerTrace.traceId, 120),
    runnerKind: safeFactText(runnerTrace.runnerKind, 120),
    sessionMode: safeFactText(runnerTrace.sessionMode, 120),
    sessionId: safeFactText(runnerTrace.sessionId, 120),
    sessionStatus: safeFactText(runnerTrace.sessionStatus, 80),
    sessionLifecycleStatus: safeFactText(runnerTrace.sessionLifecycleStatus ?? normalizeFactLifecycleStatus(runnerTrace.sessionStatus), 80),
    turn: numberOrNull(runnerTrace.turn),
    events: Array.isArray(runnerTrace.events)
      ? runnerTrace.events.map((event) => safeFactText(event, 120)).filter(Boolean).slice(-8)
      : [],
    route: safeFactText(runnerTrace.route, 120),
    status: safeFactText(runnerTrace.status, 80),
    partialContext: sanitizePartialContext(runnerTrace.partialContext),
    outputTruncated: runnerTrace.outputTruncated === true,
    valuesPrinted: false
  };
}

function normalizeFactLifecycleStatus(status) {
  return codeAgentSessionLifecycleSummary({ sessionStatus: status }).status;
}

function conversationFactsSummary({ facts, session, latestSkills, latestWorkspace, traceIds, recentToolCalls }) {
  const skillNames = latestSkills?.names?.slice(0, 5) ?? [];
  const toolNames = recentToolCalls.map((toolCall) => [toolCall.name, toolCall.status].filter(Boolean).join(":")).filter(Boolean).slice(-5);
  return [
    `turns=${facts.length}`,
    `sessionId=${session?.sessionId ?? facts.at(-1)?.sessionId ?? "unknown"}`,
    `sessionStatus=${session?.status ?? facts.at(-1)?.sessionStatus ?? "unknown"}`,
    latestWorkspace ? `workspace=${latestWorkspace}` : null,
    latestSkills ? `skills=${latestSkills.totalCount ?? latestSkills.count ?? skillNames.length}${skillNames.length ? ` (${skillNames.join(",")})` : ""}` : "skills=not_requested",
    traceIds.length ? `traceIds=${traceIds.join(",")}` : null,
    toolNames.length ? `tools=${toolNames.join(",")}` : null
  ].filter(Boolean).join(" / ");
}

function safeFactText(value, maxLength) {
  if (value === undefined || value === null || value === "") return null;
  const normalized = String(value)
    .replace(/\s+/gu, " ")
    .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gu, "Bearer ***")
    .replace(/sk-[A-Za-z0-9._-]+/gu, "sk-***")
    .replace(/([A-Za-z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD)[A-Za-z0-9_]*=)[^\s]+/giu, "$1***")
    .replace(/\/\/[^/@\s]+@/gu, "//***@")
    .trim();
  if (!normalized) return null;
  return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 12)}...[redacted]` : normalized;
}

function numberOrNull(value) {
  return Number.isFinite(value) ? value : null;
}

function sessionExpired(session, timestampMs) {
  if (!Number.isFinite(timestampMs) || session.status === "expired") return session.status === "expired";
  return Date.parse(session.expiresAt) <= timestampMs;
}

function timestampFor(now) {
  const value = typeof now === "function" ? now() : now;
  if (typeof value === "string" && !Number.isNaN(Date.parse(value))) return value;
  if (value instanceof Date && !Number.isNaN(value.valueOf())) return value.toISOString();
  return new Date().toISOString();
}

function plusMs(timestampMs, offsetMs) {
  return new Date(timestampMs + offsetMs).toISOString();
}

function requiredId(value, fallbackPrefix) {
  const id = optionalId(value);
  if (id) return id;
  return `${fallbackPrefix}_${randomUUID()}`;
}

function optionalId(value) {
  return typeof value === "string" && value.trim() ? value.trim() : null;
}

function uniqueStrings(values) {
  return [...new Set(values.map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean))];
}

function hasConversationFacts(facts) {
  return Boolean(facts && (
    facts.sessionId ||
    facts.sessionStatus ||
    facts.latestTraceId ||
    Number(facts.turnCount) > 0 ||
    (Array.isArray(facts.traceIds) && facts.traceIds.length > 0) ||
    (Array.isArray(facts.facts) && facts.facts.length > 0)
  ));
}

function positiveInteger(value, fallback) {
  return Number.isInteger(value) && value > 0 ? value : fallback;
}
