import { KGCommand } from '../../core/commands/KGCommand';
import { KGCore } from '../../core/KGCore';

/**
 * Result of tool execution
 */
export interface ToolResult {
  success: boolean;
  result: string;
}

/**
 * Parameter definition for tool parameters
 */
export interface ToolParameter {
  type: 'string' | 'number' | 'boolean' | 'array' | 'object';
  description: string;
  required?: boolean;
  items?: ToolParameter; // For array types
  properties?: Record<string, ToolParameter>; // For object types
}

/**
 * OpenAI-compatible JSON Schema for function parameters
 */
export interface OpenAIFunctionParameters {
  type: 'object';
  properties: Record<string, unknown>;
  required?: string[];
}

/**
 * OpenAI-compatible tool definition
 */
export interface OpenAIToolDefinition {
  type: 'function';
  function: {
    name: string;
    description: string;
    parameters: OpenAIFunctionParameters;
  };
}

/**
 * Tool definition schema (internal format)
 */
export interface ToolDefinition {
  name: string;
  description: string;
  parameters: Record<string, ToolParameter>;
}

/**
 * Abstract base class for all agent tools
 * Provides integration with the existing command system and core architecture
 */
export abstract class BaseTool {
  abstract readonly name: string;
  abstract readonly description: string;
  abstract readonly parameters: Record<string, ToolParameter>;

  /**
   * Execute the tool with given parameters
   * @param params Tool parameters
   * @returns Promise resolving to tool execution result
   */
  abstract execute(params: Record<string, unknown>): Promise<ToolResult>;

  /**
   * Get the tool definition in OpenAI function calling format
   */
  getDefinition(): OpenAIToolDefinition {
    return {
      type: 'function',
      function: {
        name: this.name,
        description: this.description,
        parameters: this.convertToJsonSchema(this.parameters)
      }
    };
  }

  /**
   * Convert internal ToolParameter map to OpenAI-compatible JSON Schema
   */
  private convertToJsonSchema(params: Record<string, ToolParameter>): OpenAIFunctionParameters {
    const properties: Record<string, unknown> = {};
    const required: string[] = [];

    for (const [name, param] of Object.entries(params)) {
      properties[name] = this.convertParamToJsonSchema(param);
      if (param.required) {
        required.push(name);
      }
    }

    return {
      type: 'object',
      properties,
      ...(required.length > 0 ? { required } : {})
    };
  }

  /**
   * Convert a single ToolParameter to JSON Schema format
   */
  private convertParamToJsonSchema(param: ToolParameter): Record<string, unknown> {
    const schema: Record<string, unknown> = {
      type: param.type,
      description: param.description
    };

    if (param.type === 'array' && param.items) {
      schema.items = this.convertParamToJsonSchema(param.items);
    }

    if (param.type === 'object' && param.properties) {
      const properties: Record<string, unknown> = {};
      const required: string[] = [];

      for (const [name, prop] of Object.entries(param.properties)) {
        properties[name] = this.convertParamToJsonSchema(prop);
        if (prop.required) {
          required.push(name);
        }
      }

      schema.properties = properties;
      if (required.length > 0) {
        schema.required = required;
      }
    }

    return schema;
  }

  /**
   * Validate parameters against the tool's parameter schema
   * @param params Parameters to validate
   * @returns True if valid, throws error if invalid
   */
  protected validateParameters(params: Record<string, unknown>): boolean {
    for (const [paramName, paramDef] of Object.entries(this.parameters)) {
      const value = params[paramName];
      
      // Check required parameters
      if (paramDef.required && (value === undefined || value === null)) {
        throw new Error(`Required parameter '${paramName}' is missing`);
      }
      
      // Skip type checking for undefined optional parameters
      if (value === undefined || value === null) {
        continue;
      }
      
      // Type validation
      if (!this.validateParameterType(value, paramDef)) {
        throw new Error(`Parameter '${paramName}' has invalid type. Expected: ${paramDef.type}`);
      }
    }
    
    return true;
  }

  /**
   * Validate a single parameter value against its type definition
   */
  private validateParameterType(value: unknown, paramDef: ToolParameter): boolean {
    switch (paramDef.type) {
      case 'string':
        return typeof value === 'string';
      case 'number':
        return typeof value === 'number';
      case 'boolean':
        return typeof value === 'boolean';
      case 'array':
        if (!Array.isArray(value)) return false;
        if (paramDef.items) {
          return value.every(item => this.validateParameterType(item, paramDef.items!));
        }
        return true;
      case 'object':
        if (typeof value !== 'object' || value === null || Array.isArray(value)) {
          return false;
        }
        if (paramDef.properties) {
          const obj = value as Record<string, unknown>;
          for (const [propName, propDef] of Object.entries(paramDef.properties)) {
            if (propDef.required && !(propName in obj)) {
              return false;
            }
            if (propName in obj && !this.validateParameterType(obj[propName], propDef)) {
              return false;
            }
          }
        }
        return true;
      default:
        return false;
    }
  }

  /**
   * Execute a command through the existing command system
   * This provides undo/redo functionality and proper state management
   * @param command Command to execute
   */
  protected async executeCommand(command: KGCommand): Promise<void> {
    const core = KGCore.instance();
    return core.executeCommand(command);
  }

  /**
   * Get the current project from KGCore
   */
  protected getCurrentProject() {
    const core = KGCore.instance();
    return core.getCurrentProject();
  }

  /**
   * Create a successful tool result
   */
  protected createSuccessResult(result: string): ToolResult {
    return {
      success: true,
      result
    };
  }

  /**
   * Create a failed tool result
   */
  protected createErrorResult(result: string): ToolResult {
    return {
      success: false,
      result
    };
  }
}