#!/usr/bin/env node
/**
 * strip-skills-snapshot.mjs
 * 
 * Strips the `skillsSnapshot` field from all sessions in sessions.json.
 * These snapshots are large (often 5-10MB each) and regenerated on demand,
 * so keeping them in persisted sessions wastes disk and slows I/O.
 * 
 * Uses proper-lockfile to coordinate with the gateway and avoid race conditions.
 * Run hourly via cron to keep sessions.json lean.
 */

import { readFileSync, writeFileSync, mkdirSync, statSync } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
import lockfile from 'proper-lockfile';

const SESSIONS_FILE = join(homedir(), '.openclaw/agents/main/sessions/sessions.json');
const BACKUP_DIR = join(dirname(SESSIONS_FILE), 'backups');

// Lock options matching gateway behavior
const LOCK_OPTIONS = {
  stale: 60000,      // Consider lock stale after 60s
  retries: {
    retries: 10,     // Retry up to 10 times
    minTimeout: 500, // Start with 500ms delay
    maxTimeout: 5000 // Max 5s between retries
  }
};

function formatBytes(bytes) {
  if (bytes < 1024) return bytes + ' B';
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
  return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
}

async function main() {
  let release = null;
  
  try {
    // Acquire lock (same as gateway uses)
    console.error('Acquiring lock on sessions.json...');
    release = await lockfile.lock(SESSIONS_FILE, LOCK_OPTIONS);
    console.error('Lock acquired.');

    // Ensure backup directory exists
    mkdirSync(BACKUP_DIR, { recursive: true });

    // Read current sessions
    const sizeBefore = statSync(SESSIONS_FILE).size;
    const raw = readFileSync(SESSIONS_FILE, 'utf8');
    const sessions = JSON.parse(raw);

    // Create backup
    const backupPath = join(BACKUP_DIR, `sessions.json.pre-strip-${Date.now()}`);
    writeFileSync(backupPath, raw);

    // Strip skillsSnapshot from each session
    let strippedCount = 0;
    for (const [key, session] of Object.entries(sessions)) {
      if (session.skillsSnapshot) {
        delete session.skillsSnapshot;
        strippedCount++;
      }
      // Also strip from messages if present (older format)
      if (session.messages && Array.isArray(session.messages)) {
        for (const msg of session.messages) {
          if (msg.skillsSnapshot) {
            delete msg.skillsSnapshot;
          }
        }
      }
    }

    // Write stripped sessions
    const stripped = JSON.stringify(sessions, null, 2);
    writeFileSync(SESSIONS_FILE, stripped);

    const sizeAfter = statSync(SESSIONS_FILE).size;
    const saved = sizeBefore - sizeAfter;
    const savedPercent = sizeBefore > 0 ? ((saved / sizeBefore) * 100).toFixed(1) : '0';

    // Output results (for cron job to report)
    console.log(JSON.stringify({
      sizeBefore: formatBytes(sizeBefore),
      sizeAfter: formatBytes(sizeAfter),
      saved: formatBytes(saved),
      savedPercent: savedPercent + '%',
      sessionsStripped: strippedCount,
      backupPath
    }, null, 2));

  } catch (err) {
    if (err.code === 'ELOCKED') {
      console.error('ERROR: Could not acquire lock - sessions.json is locked by another process');
      console.error('The gateway may be busy. Will retry on next cron run.');
      process.exit(1);
    }
    throw err;
  } finally {
    // Always release lock
    if (release) {
      await release();
      console.error('Lock released.');
    }
  }
}

main().catch(err => {
  console.error('Fatal error:', err);
  process.exit(1);
});
