/**
 * Tool Registry - manages all available tools and their schemas
 * Mirrors Claude Code's tool system: each tool has a schema + executor
 *
 * Deferred Loading Strategy:
 *   Tools marked with `deferred: true` are NOT included in the initial API call.
 *   The LLM uses the ToolSearch tool to discover and activate them on-demand.
 *   This saves token budget when many tools (especially MCP tools) are registered.
 */
export class ToolRegistry {
  constructor() {
    this.tools = new Map();
    this._skillFilter = null; // Set by orchestrator during skill execution
    this._activatedDeferred = new Set(); // Deferred tools activated this session
  }

  register(name, { description, parameters, execute, deferred = false }) {
    this.tools.set(name, { name, description, parameters, execute, deferred: !!deferred });
  }

  get(name) {
    return this.tools.get(name);
  }

  has(name) {
    return this.tools.has(name);
  }

  /** Remove a tool by name */
  unregister(name) {
    this.tools.delete(name);
    this._activatedDeferred.delete(name);
  }

  /** Remove all tools (for re-registration on workspace switch) */
  clear() {
    this.tools.clear();
    this._activatedDeferred.clear();
  }

  /** Get all tool schemas for API calls (Anthropic tool format) */
  getToolSchemas() {
    const schemas = [];
    for (const [name, tool] of this.tools) {
      // If a skill filter is active, only include allowed tools
      if (this._skillFilter && !this._skillFilter.includes(name)) continue;
      // Skip deferred tools unless they've been activated
      if (tool.deferred && !this._activatedDeferred.has(name)) continue;
      schemas.push({
        name,
        description: tool.description,
        input_schema: tool.parameters,
      });
    }
    // Add cache_control to last tool for prompt caching
    if (schemas.length > 0) {
      schemas[schemas.length - 1].cache_control = { type: 'ephemeral' };
    }
    return schemas;
  }

  /** Execute a tool by name with given input (with timeout protection) */
  async execute(name, input, hooks = {}) {
    const tool = this.tools.get(name);
    if (!tool) {
      return { error: `Unknown tool: ${name}` };
    }
    // Auto-activate deferred tools on first execution
    if (tool.deferred && !this._activatedDeferred.has(name)) {
      this._activatedDeferred.add(name);
    }
    // Timeout: long-running tools get 5min, Bash = 60s, others = 30s
    // AutoTestPipeline generate calls LLM via WorkflowExecutor (can take 2-3min)
    const LONG_RUNNING_TOOLS = new Set([
      'AutoTestPipeline', 'WorkflowRun', 'VLCompile', 'VLAutoFix', 'VLCascadeEdit',
    ]);
    const isBash = name === 'Bash' || name === 'bash' || name === 'RunCommand';
    const isLong = LONG_RUNNING_TOOLS.has(name);
    const timeoutMs = input?.timeout || (isLong ? 300000 : isBash ? 60000 : 30000);
    const emit = (type, payload = {}) => {
      try {
        hooks.onEvent?.({
          type,
          tool: name,
          ...(payload && typeof payload === 'object' ? payload : { value: payload }),
        });
      } catch {}
    };
    const emitText = (message, meta = {}) => {
      const text = String(message ?? '');
      const payload = {
        level: meta.level || 'info',
        message: text,
        data: meta.data ?? null,
      };
      emit('message', payload);
      try { hooks.onText?.(text, payload); } catch {}
    };
    const runtime = {
      toolName: name,
      input,
      emit,
      text: emitText,
      info: (message, data = null) => emitText(message, { level: 'info', data }),
      warn: (message, data = null) => emitText(message, { level: 'warn', data }),
      error: (message, data = null) => emitText(message, { level: 'error', data }),
      progress: (message, data = null) => emit('progress', { message: String(message ?? ''), data }),
    };
    try {
      const result = await Promise.race([
        tool.execute(input, runtime),
        new Promise((_, reject) => setTimeout(() => reject(new Error(`Tool ${name} timed out after ${timeoutMs / 1000}s`)), timeoutMs)),
      ]);
      return { result };
    } catch (err) {
      if (err.message?.includes('timed out')) {
        this._toolTimeouts = (this._toolTimeouts || 0) + 1;
      }
      return { error: err.message };
    }
  }

  /** Activate a deferred tool (make it available in subsequent API calls) */
  activateDeferred(name) {
    if (this.tools.has(name) && this.tools.get(name).deferred) {
      this._activatedDeferred.add(name);
      return true;
    }
    return false;
  }

  /** List deferred (not yet loaded) tools — for ToolSearch */
  listDeferredTools() {
    const deferred = [];
    for (const [name, tool] of this.tools) {
      if (tool.deferred && !this._activatedDeferred.has(name)) {
        deferred.push({ name, description: tool.description });
      }
    }
    return deferred;
  }

  /** List all tools including deferred (for diagnostics) */
  listTools() {
    return Array.from(this.tools.keys());
  }

  /** List active (non-deferred + activated) tool names */
  listActiveTools() {
    return Array.from(this.tools.entries())
      .filter(([name, tool]) => !tool.deferred || this._activatedDeferred.has(name))
      .map(([name]) => name);
  }
}
