#!/usr/bin/env node
/**
 * OpenClaw Context — CCR Store
 * Compress-Cache-Retrieve: reversible lossy compression.
 *
 * Stores original content keyed by BLAKE3-ish hash, allowing retrieval
 * of data that was dropped during compression. TTL-based expiration.
 *
 * Migrated from Headroom `crates/headroom-core/src/ccr/`
 *
 * Usage:
 *   const { CcrStore } = require('./ccr-store');
 *   const store = new CcrStore();
 *   store.put(hash, originalPayload);
 *   const original = store.get(hash);  // → string | null
 */

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

// ─── Configuration ───────────────────────────────────────────────

const DEFAULT_TTL_MS = 5 * 60 * 1000;  // 5 minutes
const DEFAULT_CAPACITY = 1000;
const CLEANUP_INTERVAL_MS = 60 * 1000; // 1 minute

// ─── In-Memory CCR Store ─────────────────────────────────────────

class CcrStore {
  constructor(options = {}) {
    this.ttlMs = options.ttl || DEFAULT_TTL_MS;
    this.capacity = options.capacity || DEFAULT_CAPACITY;
    this.store = new Map();  // hash → { payload, expiry }
    this.lastCleanup = Date.now();
  }

  /**
   * Store original content under hash key.
   * Returns true if stored, false if capacity full.
   */
  put(hash, payload) {
    // Evict oldest if at capacity
    if (this.store.size >= this.capacity) {
      const oldest = this.store.keys().next().value;
      this.store.delete(oldest);
    }

    this.store.set(hash, {
      payload,
      expiry: Date.now() + this.ttlMs,
    });
    return true;
  }

  /**
   * Retrieve original content by hash.
   * Returns null if expired or not found.
   */
  get(hash) {
    const entry = this.store.get(hash);
    if (!entry) return null;

    if (Date.now() > entry.expiry) {
      this.store.delete(hash);
      return null;
    }

    return entry.payload;
  }

  /**
   * Check if hash exists and is valid.
   */
  has(hash) {
    const entry = this.store.get(hash);
    if (!entry) return false;
    if (Date.now() > entry.expiry) {
      this.store.delete(hash);
      return false;
    }
    return true;
  }

  /**
   * Evict expired entries.
   */
  cleanup() {
    const now = Date.now();
    for (const [hash, entry] of this.store) {
      if (now > entry.expiry) {
        this.store.delete(hash);
      }
    }
    this.lastCleanup = now;
  }

  /**
   * Get store stats.
   */
  stats() {
    this.cleanup();
    return {
      size: this.store.size,
      capacity: this.capacity,
      ttlMs: this.ttlMs,
    };
  }

  /**
   * Clear all entries.
   */
  clear() {
    this.store.clear();
  }
}

// ─── File-based CCR Store (persistent across sessions) ───────────

class FileCcrStore extends CcrStore {
  constructor(options = {}) {
    super(options);
    this.dbPath = options.dbPath || path.join(
      process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME, '.openclaw', 'state'),
      'ccr-store.json'
    );
    this._load();
  }

  _load() {
    try {
      if (fs.existsSync(this.dbPath)) {
        const data = JSON.parse(fs.readFileSync(this.dbPath, 'utf8'));
        for (const [hash, entry] of Object.entries(data)) {
          if (entry.expiry > Date.now()) {
            this.store.set(hash, entry);
          }
        }
      }
    } catch {
      // Start fresh on corruption
    }
  }

  _save() {
    const dir = path.dirname(this.dbPath);
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
    const data = Object.fromEntries(this.store);
    fs.writeFileSync(this.dbPath, JSON.stringify(data), 'utf8');
  }

  put(hash, payload) {
    const result = super.put(hash, payload);
    this._save();
    return result;
  }
}

// ─── Utility: hash content ───────────────────────────────────────

function ccrHash(content) {
  return crypto.createHash('sha256').update(
    typeof content === 'string' ? content : JSON.stringify(content)
  ).digest('hex').substring(0, 24);
}

// ─── Singleton (shared across session) ───────────────────────────

let _instance = null;

function getCcrStore(options = {}) {
  if (!_instance) {
    _instance = new CcrStore(options);
  }
  return _instance;
}

function resetCcrStore() {
  if (_instance) _instance.clear();
  _instance = null;
}

// ─── CLI ─────────────────────────────────────────────────────────

function main() {
  const args = process.argv.slice(2);

  if (args.length === 0) {
    const store = getCcrStore();
    console.log(JSON.stringify(store.stats(), null, 2));
    return;
  }

  const cmd = args[0];
  const store = getCcrStore();

  switch (cmd) {
    case 'put': {
      if (args.length < 3) { console.log('Usage: ccr-store put <hash> <data>'); return; }
      const hash = args[1];
      const data = args.slice(2).join(' ');
      store.put(hash, data);
      console.log(JSON.stringify({ ok: true, hash }));
      break;
    }
    case 'get': {
      if (args.length < 2) { console.log('Usage: ccr-store get <hash>'); return; }
      const data = store.get(args[1]);
      console.log(data || JSON.stringify({ error: 'not_found' }));
      break;
    }
    case 'stats': {
      console.log(JSON.stringify(store.stats(), null, 2));
      break;
    }
    case 'clear': {
      store.clear();
      console.log(JSON.stringify({ ok: true }));
      break;
    }
    default:
      console.log(JSON.stringify({ error: 'unknown_command', cmd }));
  }
}

module.exports = {
  CcrStore,
  FileCcrStore,
  ccrHash,
  getCcrStore,
  resetCcrStore,
};

if (require.main === module) {
  main();
}
