// src/layer/adapters/bash-truncate-large.ts
//
// ToolResultAdapter that compresses large Bash tool outputs into a head+tail
// preview, while indexing the full content into the per-session KB.
//
// Match condition: toolName === "Bash" AND byteLength(rawContent) >= 2048.
//
// Determinism guarantees:
//   - No Date.now(), no Math.random(), no Map/Set iteration.
//   - Regex constructed fresh per call (no /g flag with shared lastIndex state).
//   - Preview format bytes are EXACT and STABLE — cache-hash safe.
//
// See ./types.ts for load-bearing invariants (Rule A: frozen bytes,
// Rule B: latest-turn only, determinism).

import type { ToolResult } from "../../provider/canonical.ts";
import type { ToolResultAdapter, CompressedResult } from "./types.ts";

const HEAD_LINES = 20;
const TAIL_LINES = 20;
const BYTE_THRESHOLD = 2048;

/**
 * Derive a stable, deterministic KB query hint from the first line of content.
 * Priority:
 *   1. First filename-like token (contains a dot, matches filename regex)
 *   2. First command-keyword token (length 3-20, purely alphanumeric+_-)
 *   3. Fallback: first 8 chars of toolCallId
 *
 * IMPORTANT: no /g flag — regex is created fresh each call, no lastIndex side effect.
 */
function deriveQueryHint(firstLine: string, toolCallId: string): string {
  // Strategy 1: filename pattern (contains a literal dot)
  const filenameMatch = /[A-Za-z0-9._/-]+\.[A-Za-z0-9]+/.exec(firstLine);
  if (filenameMatch !== null) {
    return filenameMatch[0];
  }

  // Strategy 2: command-keyword — first whitespace-delimited token, length 3–20,
  // purely alphanumeric+_-
  const tokens = firstLine.split(/\s+/);
  for (const token of tokens) {
    if (
      token.length >= 3 &&
      token.length <= 20 &&
      /^[A-Za-z0-9_-]+$/.test(token)
    ) {
      return token;
    }
  }

  // Strategy 3: fallback to toolCallId prefix
  return toolCallId.slice(0, 8);
}

export const bashTruncateLarge: ToolResultAdapter = {
  id: "bash-truncate-large",

  match(r: ToolResult): boolean {
    if (r.toolName !== "Bash") return false;
    const raw: string =
      typeof r.content === "string" ? r.content : JSON.stringify(r.content);
    return Buffer.byteLength(raw, "utf8") >= BYTE_THRESHOLD;
  },

  compress(r: ToolResult): CompressedResult {
    const rawContent: string =
      typeof r.content === "string" ? r.content : JSON.stringify(r.content);

    const lines = rawContent.split("\n");
    const totalLines = lines.length;
    const truncatedLines = Math.max(0, totalLines - (HEAD_LINES + TAIL_LINES));

    // Defensive: if content fits within head+tail window, pass through unchanged.
    if (totalLines <= HEAD_LINES + TAIL_LINES) {
      return {
        preview: rawContent,
        kbEntries: [],
        bytesSaved: 0,
      };
    }

    const head = lines.slice(0, HEAD_LINES);
    const tail = lines.slice(totalLines - TAIL_LINES);

    const firstLine = lines[0] ?? "";
    const suggestedQ = deriveQueryHint(firstLine, r.toolCallId);

    // STABLE preview format — EXACT byte strings, no clocks, no random.
    const preview =
      `--- preview (head ${HEAD_LINES} of ${totalLines} lines) ---\n` +
      head.join("\n") +
      `\n--- ...(${truncatedLines} lines truncated)... ---\n` +
      `--- preview (tail ${TAIL_LINES} of ${totalLines} lines) ---\n` +
      tail.join("\n") +
      `\n--- end ---\n` +
      `Full content indexed. Use the kb tool with q='${suggestedQ}' to retrieve.`;

    return {
      preview,
      kbEntries: [{ section: "full-output", content: rawContent }],
      bytesSaved:
        Buffer.byteLength(rawContent, "utf8") -
        Buffer.byteLength(preview, "utf8"),
    };
  },
};
