// wall-e/tools/smart-truncation.js
// Smart output truncation for the coding agent.
// Inspired by OpenCode's truncate.ts (packages/opencode/src/tool/truncate.ts).
// Saves full output to temp file and returns a hint to grep/read it.
'use strict';

const fs = require('node:fs');
const path = require('node:path');

const DATA_DIR = process.env.WALL_E_DATA_DIR || path.join(process.env.HOME || '/tmp', '.walle', 'data');
const TRUNCATION_DIR = path.join(DATA_DIR, 'truncated');
const MAX_LINES = 2000;
const MAX_BYTES = 50 * 1024; // 50KB
const RETENTION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days

/**
 * Truncate large tool output. If output exceeds limits, saves full output
 * to a temp file and returns a hint telling the LLM to use read_file/grep_files.
 *
 * Ported from OpenCode's Truncate service (tool/truncate.ts).
 *
 * @param {string} text - Full tool output
 * @param {string} sessionId - Coding session ID
 * @param {string} toolCallId - Tool call ID for unique filename
 * @returns {{ content: string, truncated: boolean, savedPath?: string }}
 */
function truncateOutput(text, sessionId, toolCallId) {
  const lines = text.split('\n');
  const totalBytes = Buffer.byteLength(text, 'utf-8');

  // Check if truncation is needed
  if (lines.length <= MAX_LINES && totalBytes <= MAX_BYTES) {
    return { content: text, truncated: false };
  }

  // Build truncated preview (first N lines up to byte limit)
  const preview = [];
  let bytes = 0;
  for (let i = 0; i < lines.length && i < MAX_LINES; i++) {
    const lineBytes = Buffer.byteLength(lines[i], 'utf-8') + (i > 0 ? 1 : 0);
    if (bytes + lineBytes > MAX_BYTES) break;
    preview.push(lines[i]);
    bytes += lineBytes;
  }

  // Save full output to file
  if (!fs.existsSync(TRUNCATION_DIR)) {
    fs.mkdirSync(TRUNCATION_DIR, { recursive: true });
  }
  const filename = `${sessionId}-${toolCallId}.txt`;
  const savedPath = path.join(TRUNCATION_DIR, filename);
  fs.writeFileSync(savedPath, text, 'utf8');

  const removed = lines.length - preview.length;
  const hint = [
    '',
    `...${removed} lines truncated...`,
    '',
    `Output truncated. Full output saved to ${savedPath}`,
    'Use read_file with offset/limit or grep_files to search the full content.',
  ].join('\n');

  return {
    content: preview.join('\n') + hint,
    truncated: true,
    savedPath,
  };
}

/**
 * Clean up truncation files older than the retention period.
 * @param {number} [retentionMs=RETENTION_MS] - Retention period in milliseconds.
 *   Pass 0 to delete ALL files (test cleanup).
 */
function cleanupTruncationFiles(retentionMs = RETENTION_MS) {
  if (!fs.existsSync(TRUNCATION_DIR)) return;

  const cutoff = Date.now() - retentionMs;
  const entries = fs.readdirSync(TRUNCATION_DIR);

  for (const entry of entries) {
    const filePath = path.join(TRUNCATION_DIR, entry);
    try {
      const stat = fs.statSync(filePath);
      if (stat.mtimeMs < cutoff) {
        fs.unlinkSync(filePath);
      }
    } catch {
      // Ignore errors on individual files
    }
  }
}

module.exports = {
  truncateOutput,
  cleanupTruncationFiles,
  TRUNCATION_DIR,
  MAX_LINES,
  MAX_BYTES,
};
