// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-01060505 Workbench性能 draft-2026-06-19-p0.
// Responsibility: Policy-driven Workbench session cache trimming without hidden capacity defaults.

import type { ChatMessage, WorkbenchSessionRecord } from "@/types";

export interface WorkbenchSessionCacheState {
  sessionOrder: string[];
  sessionsById: Record<string, WorkbenchSessionRecord>;
  messagesBySessionId: Record<string, ChatMessage[]>;
}

export interface WorkbenchSessionCachePolicy {
  retainSessionIds?: Iterable<string | null | undefined>;
  maxSessions?: number | null;
  maxMessagesPerSession?: number | null;
}

export interface WorkbenchSessionCacheTrimResult<T extends WorkbenchSessionCacheState> {
  state: T;
  evictedSessionIds: string[];
  trimmedMessageSessionIds: string[];
}

export interface WorkbenchDroppedSessionCacheCleanup<T extends WorkbenchSessionCacheState> {
  state: T;
  droppedSessionIds: string[];
}

export function trimWorkbenchSessionCache<T extends WorkbenchSessionCacheState>(state: T, policy: WorkbenchSessionCachePolicy): WorkbenchSessionCacheTrimResult<T> {
  const retain = new Set(normalizeIds(policy.retainSessionIds));
  const maxSessions = positiveIntegerOrNull(policy.maxSessions);
  const maxMessages = positiveIntegerOrNull(policy.maxMessagesPerSession);
  const keptOrder = maxSessions === null ? [...state.sessionOrder] : keepSessionOrder(state.sessionOrder, retain, maxSessions);
  const kept = new Set(keptOrder);
  const evictedSessionIds = Object.keys(state.sessionsById).filter((sessionId) => !kept.has(sessionId));
  const sessionsById = pickRecordKeys(state.sessionsById, kept);
  const messagesBySessionId: Record<string, ChatMessage[]> = {};
  const trimmedMessageSessionIds: string[] = [];
  for (const sessionId of Object.keys(state.messagesBySessionId)) {
    if (!kept.has(sessionId)) continue;
    const messages = state.messagesBySessionId[sessionId] ?? [];
    if (maxMessages !== null && messages.length > maxMessages) {
      messagesBySessionId[sessionId] = messages.slice(messages.length - maxMessages);
      trimmedMessageSessionIds.push(sessionId);
    } else {
      messagesBySessionId[sessionId] = messages;
    }
  }
  return {
    state: { ...state, sessionOrder: keptOrder, sessionsById, messagesBySessionId },
    evictedSessionIds,
    trimmedMessageSessionIds
  };
}

// Mechanical source: /root/opencode/packages/app/src/context/global-sync/event-reducer.ts:50-91 cleanupDroppedSessionCaches.
export function cleanupDroppedWorkbenchSessionCaches<T extends WorkbenchSessionCacheState>(state: T, keepSessionIds: Iterable<string | null | undefined>): WorkbenchDroppedSessionCacheCleanup<T> {
  const keep = new Set(normalizeIds(keepSessionIds));
  const stale = [
    ...Object.keys(state.sessionsById),
    ...Object.keys(state.messagesBySessionId),
    ...state.sessionOrder
  ].filter((sessionId, index, list) => Boolean(sessionId) && !keep.has(sessionId) && list.indexOf(sessionId) === index);
  if (stale.length === 0) return { state, droppedSessionIds: [] };
  const dropped = new Set(stale);
  return {
    state: {
      ...state,
      sessionOrder: state.sessionOrder.filter((sessionId) => !dropped.has(sessionId)),
      sessionsById: omitRecordKeys(state.sessionsById, dropped),
      messagesBySessionId: omitRecordKeys(state.messagesBySessionId, dropped)
    },
    droppedSessionIds: stale
  };
}

function keepSessionOrder(order: string[], retain: Set<string>, maxSessions: number): string[] {
  const kept: string[] = [];
  for (const sessionId of order) {
    if (retainedOrBelowLimit(sessionId, retain, kept.length, maxSessions)) kept.push(sessionId);
  }
  for (const sessionId of retain) {
    if (order.includes(sessionId) && !kept.includes(sessionId)) kept.push(sessionId);
  }
  return kept;
}

function retainedOrBelowLimit(sessionId: string, retain: Set<string>, count: number, maxSessions: number): boolean {
  return retain.has(sessionId) || count < maxSessions;
}

function pickRecordKeys<T>(record: Record<string, T>, keys: Set<string>): Record<string, T> {
  const next: Record<string, T> = {};
  for (const key of Object.keys(record)) if (keys.has(key)) next[key] = record[key] as T;
  return next;
}

function omitRecordKeys<T>(record: Record<string, T>, keys: Set<string>): Record<string, T> {
  const next: Record<string, T> = {};
  for (const key of Object.keys(record)) if (!keys.has(key)) next[key] = record[key] as T;
  return next;
}

function normalizeIds(values: Iterable<string | null | undefined> | null | undefined): string[] {
  if (!values) return [];
  return [...values].map((value) => String(value ?? "").trim()).filter(Boolean);
}

function positiveIntegerOrNull(value: number | null | undefined): number | null {
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
  return Math.trunc(value);
}

