// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-0104010803 Workbench唯一投影 draft-2026-06-28-p0-d518-session-timeline-consistency.
// Responsibility: Stable Workbench scoped keys for queues, caches, storage, timeline rows and diagnostics. Adapted from OpenCode app/utils/server-scope.ts.

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

export type WorkbenchScopePart = unknown;

const EMPTY_SCOPE_PART = "~";
const SCOPE_SEPARATOR = "\u0000";

export function composeWorkbenchScopedKey(scope: string, ...parts: WorkbenchScopePart[]): string {
  const normalizedScope = checkedScopePart("Workbench scope", normalizeScopePart(scope));
  return [normalizedScope, ...parts.map((part) => checkedScopePart("Workbench scoped key part", normalizeScopePart(part)))].join(SCOPE_SEPARATOR);
}

export function workbenchRealtimeScopeKey(sessionId: WorkbenchScopePart, traceId: WorkbenchScopePart): string {
  return composeWorkbenchScopedKey("workbench.realtime", sessionId, traceId);
}

export function workbenchSessionScopeKey(sessionId: WorkbenchScopePart): string {
  return composeWorkbenchScopedKey("workbench.session", sessionId);
}

export function workbenchTraceScopeKey(traceId: WorkbenchScopePart): string {
  return composeWorkbenchScopedKey("workbench.trace", traceId);
}

export function workbenchMessageScopeKey(message: Pick<ChatMessage, "id" | "messageId" | "turnId" | "traceId" | "sessionId" | "role" | "createdAt">): string {
  return composeWorkbenchScopedKey(
    "workbench.message",
    firstScopePart(message.sessionId),
    firstScopePart(message.messageId, message.id, message.turnId, message.traceId, message.createdAt),
    firstScopePart(message.role)
  );
}

export function workbenchTimelineRowScopeKey(input: { sessionId?: WorkbenchScopePart; traceId?: WorkbenchScopePart; rowId?: WorkbenchScopePart; role?: WorkbenchScopePart; type?: WorkbenchScopePart }): string {
  return composeWorkbenchScopedKey("workbench.timeline.row", input.sessionId, input.traceId, input.type, input.role, input.rowId);
}

export function firstScopePart(...values: WorkbenchScopePart[]): string | null {
  for (const value of values) {
    const text = normalizeScopeText(value);
    if (text) return text;
  }
  return null;
}

export function normalizeScopeText(value: WorkbenchScopePart): string | null {
  if (value === null || value === undefined) return null;
  const text = String(value).trim();
  return text ? text : null;
}

export function splitWorkbenchScopedKey(key: string): string[] {
  return key.split(SCOPE_SEPARATOR);
}

export function workbenchPathKey(path: string): string {
  const value = isWindowsPath(path) ? path.replaceAll("\\", "/") : path;
  const trimmed = trimTrailingSlashes(value);
  if (!trimmed && value.startsWith("/")) return "/";
  if (isDrivePath(trimmed)) return `${trimmed}/`;
  return trimmed;
}

function normalizeScopePart(value: WorkbenchScopePart): string {
  const text = normalizeScopeText(value);
  return text ?? EMPTY_SCOPE_PART;
}

function checkedScopePart(label: string, value: string): string {
  if (value.includes(SCOPE_SEPARATOR)) throw new Error(`${label} cannot contain null bytes`);
  return value;
}

function trimTrailingSlashes(value: string): string {
  for (let index = value.length - 1; index >= 0; index -= 1) {
    if (value[index] !== "/") return value.slice(0, index + 1);
  }
  return "";
}

function isWindowsPath(value: string): boolean {
  return value[1] === ":" || value.startsWith("\\\\");
}

function isDrivePath(value: string): boolean {
  if (value.length !== 2) return false;
  const code = value.charCodeAt(0);
  return value[1] === ":" && ((code >= 65 && code <= 90) || (code >= 97 && code <= 122));
}


