/**
 * Modular Tool System for Terminal Execution
 * Inspired by AGIAgent, AutoGen, and ReAct patterns
 *
 * Architecture:
 * - Base Tool interface for extensibility
 * - Tool Registry for managing available tools
 * - Enhanced Intent Analysis for smart tool selection
 * - Structured error handling and output formatting
 */

/**
 * Base Tool Interface
 * All tools must extend this class
 */
class BaseTool {
  /**
   * @param {Object} config - Tool configuration
   * @param {string} config.name - Unique tool name
   * @param {string} config.description - What this tool does
   * @param {Array} config.parameters - Parameter definitions
   * @param {Object} config.options - Tool-specific options
   */
  constructor(config) {
    if (!config.name) {
      throw new Error('Tool must have a name');
    }
    this.name = config.name;
    this.description = config.description || '';
    this.parameters = config.parameters || [];
    this.options = config.options || {};
    this.enabled = config.enabled !== false;
  }

  /**
   * Execute the tool with given parameters
   * Must be implemented by subclasses
   *
   * @param {Object} params - Execution parameters
   * @returns {Promise<ToolResult>} Execution result
   */
  async execute(params) {
    throw new Error(`Tool ${this.name} must implement execute() method`);
  }

  /**
   * Validate parameters before execution
   *
   * @param {Object} params - Parameters to validate
   * @returns {Object} Validation result with valid flag and errors array
   */
  validate(params) {
    const errors = [];

    for (const param of this.parameters) {
      const value = params[param.name];

      // Check required parameters
      if (param.required && value === undefined) {
        errors.push(`Required parameter '${param.name}' is missing`);
        continue;
      }

      // Type validation
      if (value !== undefined && param.type) {
        const actualType = Array.isArray(value) ? 'array' : typeof value;
        if (actualType !== param.type) {
          errors.push(
            `Parameter '${param.name}' should be ${param.type}, got ${actualType}`
          );
        }
      }
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }

  /**
   * Get tool metadata
   */
  getMetadata() {
    return {
      name: this.name,
      description: this.description,
      parameters: this.parameters,
      enabled: this.enabled
    };
  }
}

/**
 * Tool Result Structure
 */
class ToolResult {
  /**
   * @param {Object} result
   * @param {boolean} result.success - Whether execution succeeded
   * @param {*} result.data - Result data
   * @param {string} result.output - Formatted output string
   * @param {Error} result.error - Error if failed
   * @param {Object} result.metadata - Additional metadata
   */
  constructor(result) {
    this.success = result.success !== false;
    this.data = result.data;
    this.output = result.output || '';
    this.error = result.error;
    this.metadata = result.metadata || {};
    this.timestamp = new Date().toISOString();
  }

  /**
   * Create a success result
   */
  static success(data, output = '', metadata = {}) {
    return new ToolResult({
      success: true,
      data,
      output,
      metadata
    });
  }

  /**
   * Create a failure result
   */
  static failure(error, output = '', metadata = {}) {
    return new ToolResult({
      success: false,
      error,
      output,
      metadata
    });
  }

  toJSON() {
    return {
      success: this.success,
      data: this.data,
      output: this.output,
      error: this.error ? this.error.message : null,
      metadata: this.metadata,
      timestamp: this.timestamp
    };
  }
}

/**
 * Tool Registry
 * Manages available tools and their execution
 */
class ToolRegistry {
  constructor() {
    this.tools = new Map();
    this.middlewares = [];
    this.executionHistory = [];
    this.maxHistorySize = 100;
  }

  /**
   * Register a new tool
   *
   * @param {BaseTool} tool - Tool instance to register
   */
  register(tool) {
    if (!(tool instanceof BaseTool)) {
      throw new Error('Tool must extend BaseTool');
    }

    if (this.tools.has(tool.name)) {
      throw new Error(`Tool '${tool.name}' is already registered`);
    }

    this.tools.set(tool.name, tool);
    return this;
  }

  /**
   * Unregister a tool
   *
   * @param {string} name - Tool name to unregister
   */
  unregister(name) {
    return this.tools.delete(name);
  }

  /**
   * Get a tool by name
   *
   * @param {string} name - Tool name
   * @returns {BaseTool|null}
   */
  get(name) {
    return this.tools.get(name) || null;
  }

  /**
   * Check if a tool exists and is enabled
   *
   * @param {string} name - Tool name
   */
  has(name) {
    const tool = this.tools.get(name);
    return tool && tool.enabled;
  }

  /**
   * List all available tools
   *
   * @param {Object} options - Listing options
   * @param {boolean} options.includeDisabled - Include disabled tools
   */
  list(options = {}) {
    const tools = Array.from(this.tools.values());

    if (!options.includeDisabled) {
      return tools.filter(t => t.enabled);
    }

    return tools;
  }

  /**
   * List tools metadata
   */
  listMetadata() {
    return this.list().map(tool => tool.getMetadata());
  }

  /**
   * Execute a tool by name
   *
   * @param {string} name - Tool name
   * @param {Object} params - Execution parameters
   * @returns {Promise<ToolResult>}
   */
  async execute(name, params = {}) {
    const startTime = Date.now();

    try {
      // Get tool
      const tool = this.get(name);
      if (!tool) {
        throw new Error(`Tool '${name}' not found or disabled`);
      }

      // Validate parameters
      const validation = tool.validate(params);
      if (!validation.valid) {
        throw new Error(`Parameter validation failed: ${validation.errors.join(', ')}`);
      }

      // Run before middlewares
      for (const mw of this.middlewares) {
        if (mw.before) {
          await mw.before(name, params);
        }
      }

      // Execute tool
      let result = await tool.execute(params);

      // Run after middlewares
      for (const mw of this.middlewares) {
        if (mw.after) {
          result = await mw.after(name, params, result) || result;
        }
      }

      // Record history
      this.recordExecution({
        tool: name,
        params,
        result: result.toJSON(),
        duration: Date.now() - startTime,
        timestamp: new Date().toISOString()
      });

      return result;
    } catch (error) {
      const result = ToolResult.failure(error, error.message);

      // Record failure
      this.recordExecution({
        tool: name,
        params,
        result: result.toJSON(),
        duration: Date.now() - startTime,
        timestamp: new Date().toISOString()
      });

      return result;
    }
  }

  /**
   * Add middleware for execution hooks
   *
   * @param {Object} middleware - Middleware with before/after hooks
   */
  use(middleware) {
    this.middlewares.push(middleware);
    return this;
  }

  /**
   * Record execution in history
   */
  recordExecution(record) {
    this.executionHistory.push(record);

    // Limit history size
    if (this.executionHistory.length > this.maxHistorySize) {
      this.executionHistory.shift();
    }
  }

  /**
   * Get execution history
   */
  getHistory(options = {}) {
    let history = this.executionHistory;

    if (options.tool) {
      history = history.filter(r => r.tool === options.tool);
    }

    if (options.limit) {
      history = history.slice(-options.limit);
    }

    return history;
  }

  /**
   * Clear execution history
   */
  clearHistory() {
    this.executionHistory = [];
  }

  /**
   * Get statistics
   */
  getStats() {
    const stats = {
      totalExecutions: this.executionHistory.length,
      toolUsage: {},
      successRate: 0,
      avgDuration: 0
    };

    let successCount = 0;
    let totalDuration = 0;

    for (const record of this.executionHistory) {
      stats.toolUsage[record.tool] = (stats.toolUsage[record.tool] || 0) + 1;

      if (record.result.success) {
        successCount++;
      }

      totalDuration += record.duration;
    }

    if (stats.totalExecutions > 0) {
      stats.successRate = (successCount / stats.totalExecutions * 100).toFixed(2);
      stats.avgDuration = (totalDuration / stats.totalExecutions).toFixed(2);
    }

    return stats;
  }
}

/**
 * Export all classes
 */
module.exports = {
  BaseTool,
  ToolResult,
  ToolRegistry
};
