// src/layer/adapters/webfetch-truncate-large.ts
//
// ToolResultAdapter that compresses large WebFetch tool outputs into a head+tail
// preview, while indexing the full content into the per-session KB.
//
// Match condition: toolName === "WebFetch" 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, matches
//     bash-truncate-large header strings for consistent kb-search.
//
// 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 for WebFetch results.
 * Priority:
 *   1. First URL-like token (http:// or https://), with trailing punctuation stripped.
 *   2. First HTML <title>...</title> inner text (case-insensitive, lazy), trimmed,
 *      truncated to 40 chars.
 *   3. Fallback: first 8 chars of toolCallId.
 *
 * IMPORTANT: no /g flag — regexes created fresh each call, no lastIndex side effect.
 */
function deriveQueryHint(rawContent: string, toolCallId: string): string {
  // Strategy 1: first URL token anywhere in content
  const urlMatch = /https?:\/\/[^\s<>"'`]+/.exec(rawContent);
  if (urlMatch !== null && typeof urlMatch[0] === "string") {
    return urlMatch[0].replace(/[.,;:)\]}>]+$/, "");
  }

  // Strategy 2: first <title>...</title> inner text
  const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(rawContent);
  if (titleMatch !== null && typeof titleMatch[1] === "string") {
    const inner = titleMatch[1].trim();
    if (inner.length > 0) return inner.slice(0, 40);
  }

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

export const webfetchTruncateLarge: ToolResultAdapter = {
  id: "webfetch-truncate-large",

  match(r: ToolResult): boolean {
    if (r.toolName !== "WebFetch") 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 suggestedQ = deriveQueryHint(rawContent, r.toolCallId);

    // STABLE preview format — EXACT byte strings, no clocks, no random.
    // Header strings are byte-identical to bash-truncate-large for kb-search compatibility.
    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"),
    };
  },
};
