'use strict';

/**
 * Compaction hook registry — Item E of prompt-catalog deep-dive.
 *
 * Plugins (skills, channels, custom tools) can register a per-tool
 * compaction function. The chat tool-result post-processor consults
 * this registry FIRST, falling through to the built-in compactor.js
 * heuristics only if no plugin claims the tool.
 *
 * Inspired by OpenClaw's `src/agents/harness/prompt-compaction-hook-helpers.ts`
 * — same idea: each plugin contributes its own compaction logic for
 * its tool results.
 *
 * Hook contract:
 *   compactor(toolName, resultStr, opts) -> compactedString | null
 *
 * - Return a STRING to take responsibility for the result. Compactor
 *   will use it directly.
 * - Return null/undefined to defer to the next hook (or built-in
 *   heuristics).
 *
 * Multiple hooks can register for the same toolName prefix — they're
 * tried in registration order; first non-null wins.
 *
 * Plugins register at bootstrap time via the manifest's compaction
 * field (future) or directly via registerCompactionHook().
 */

const { Registry } = require('../plugins/registry-base');
const { addDiagnostic } = require('../plugins/diagnostics');

const _registry = new Registry('compaction');
let _autoId = 0;

/**
 * Register a compaction hook. Returns a deregister closure.
 *
 * @param {object} opts
 * @param {string|RegExp} opts.toolName - Exact tool name OR a RegExp
 *                                        for prefix/pattern matching
 *                                        (e.g. /^slack_/)
 * @param {Function} opts.compactor - (toolName, resultStr, opts) => string|null
 * @param {string} [opts.id] - Stable id (defaults to auto-generated)
 * @param {number} [opts.priority] - Lower runs earlier (default 100)
 * @param {string} [opts.source] - Plugin id that owns this hook
 * @returns {() => boolean} deregister closure
 */
function registerCompactionHook(opts) {
  if (!opts || typeof opts.compactor !== 'function') {
    throw new Error('registerCompactionHook: opts.compactor (function) is required');
  }
  if (!opts.toolName) {
    throw new Error('registerCompactionHook: opts.toolName is required');
  }
  const id = opts.id || `auto-${++_autoId}-${Date.now()}`;
  return _registry.register({
    id,
    toolName: opts.toolName,
    compactor: opts.compactor,
    priority: opts.priority || 100,
    source: opts.source || 'inline',
  });
}

/**
 * Try registered hooks for `toolName`. Returns the first non-null/
 * non-undefined string returned by a matching hook, or null if no hook
 * claimed the result.
 *
 * On hook exception: emit diagnostic + skip to next hook (failure
 * isolation).
 */
function tryCompact(toolName, resultStr, opts = {}) {
  const matches = _registry.list().filter((h) => _matches(h.toolName, toolName));
  matches.sort((a, b) => a.priority - b.priority);
  for (const hook of matches) {
    try {
      const out = hook.compactor(toolName, resultStr, opts);
      if (typeof out === 'string') {
        return { compacted: out, hookId: hook.id, source: hook.source };
      }
    } catch (err) {
      addDiagnostic({
        level: 'error',
        pluginId: hook.source,
        kind: 'compaction',
        message: `compaction hook '${hook.id}' threw on ${toolName}: ${err.message}`,
        component: 'compaction-hooks',
      });
    }
  }
  return null;
}

function _matches(pattern, toolName) {
  if (pattern instanceof RegExp) return pattern.test(toolName);
  if (typeof pattern === 'string') return pattern === toolName;
  return false;
}

/** All registered hooks (for inspection/debug). */
function listHooks() {
  return _registry.list();
}

/** Test helper. */
function _resetForTest() {
  _registry.clear();
}

module.exports = {
  registerCompactionHook,
  tryCompact,
  listHooks,
  _resetForTest,
};
