// SPEC: pikasTech/HWLAB#2351 Workbench JSON parse/projection merge cost reduction.
// Responsibility: Pure bounded-window planning for Workbench session message projection refreshes.

import type { ChatMessage } from "@/types";
import { firstNonEmptyString } from "@/utils";
import { isTerminalMessageStatus, messageHasTerminalResponse } from "./workbench-message-projection-runtime";

export interface ProjectionWindowOptions {
  traceId?: string | null;
  limit: number;
}

export function boundedProjectionMessageLimit(value: number | null | undefined, fallback: number): number {
  const number = Number(value);
  if (Number.isFinite(number) && number > 0) return Math.max(1, Math.trunc(number));
  return Math.max(1, Math.trunc(fallback));
}

export function selectProjectionMessageWindow(source: ChatMessage[], options: ProjectionWindowOptions): ChatMessage[] {
  const limit = boundedProjectionMessageLimit(options.limit, source.length || 1);
  const tail = source.slice(-limit);
  const traceId = firstNonEmptyString(options.traceId);
  if (!traceId) return tail;
  const selected = new Set(tail);
  for (const message of source) {
    if (messageTraceId(message) === traceId) selected.add(message);
  }
  return source.filter((message) => selected.has(message));
}

export function mergeBoundedProjectionMessages(existing: ChatMessage[], incoming: ChatMessage[]): ChatMessage[] {
  if (existing.length === 0) return incoming;
  if (incoming.length === 0) return existing;
  const incomingByKey = new Map<string, ChatMessage>();
  for (const message of incoming) {
    const key = messageProjectionKey(message);
    if (key) incomingByKey.set(key, message);
  }
  if (incomingByKey.size === 0) return existing;
  const seen = new Set<string>();
  let changed = false;
  const merged = existing.map((message) => {
    const key = messageProjectionKey(message);
    if (!key) return message;
    seen.add(key);
    const next = incomingByKey.get(key) ?? message;
    if (next !== message) changed = true;
    return next;
  });
  for (const message of incoming) {
    const key = messageProjectionKey(message);
    if (!key || seen.has(key)) continue;
    seen.add(key);
    merged.push(message);
    changed = true;
  }
  return changed ? merged : existing;
}

export function traceProjectionIsTerminalSealed(traceId: string | null | undefined, source: ChatMessage[]): boolean {
  const id = firstNonEmptyString(traceId);
  if (!id) return false;
  const message = [...source].reverse().find((item) => item.role === "agent" && messageTraceId(item) === id) ?? null;
  return Boolean(message && isTerminalMessageStatus(message.status) && messageHasTerminalResponse(message));
}

function messageProjectionKey(message: ChatMessage): string | null {
  const traceId = messageTraceId(message);
  if (traceId) return `trace:${message.role}:${traceId}`;
  const messageId = firstNonEmptyString(message.messageId, message.id);
  return messageId ? `message:${messageId}` : null;
}

function messageTraceId(message: ChatMessage): string | null {
  return firstNonEmptyString(message.traceId, message.runnerTrace?.traceId) ?? null;
}
