export interface TruncationOptions {
  maxTokens?: number;
  maxCharacters?: number;
  preserveLatest?: boolean;
  prioritizeErrors?: boolean;
}

export class LogTruncator {
  // Rough estimate: 1 token ≈ 4 characters
  private static readonly CHARS_PER_TOKEN = 4;
  private static readonly DEFAULT_MAX_TOKENS = 8000; // Safe limit for most LLMs
  
  /**
   * Truncate logs intelligently to fit within token limits
   */
  static truncateLogs(
    logs: Array<{ type: string; text: string; timestamp: Date }>,
    options: TruncationOptions = {}
  ): Array<{ type: string; text: string; timestamp: Date }> {
    const {
      maxTokens = this.DEFAULT_MAX_TOKENS,
      maxCharacters = maxTokens * this.CHARS_PER_TOKEN,
      preserveLatest = true,
      prioritizeErrors = true
    } = options;
    
    if (logs.length === 0) return [];
    
    // Calculate total size
    const totalChars = logs.reduce((sum, log) => sum + log.text.length, 0);
    
    // If within limits, return as-is
    if (totalChars <= maxCharacters) {
      return logs;
    }
    
    // Separate logs by type if prioritizing errors
    if (prioritizeErrors) {
      const errors = logs.filter(log => log.type === 'error' || log.type === 'warn');
      const others = logs.filter(log => log.type !== 'error' && log.type !== 'warn');
      
      // Calculate space for each category
      const errorChars = errors.reduce((sum, log) => sum + log.text.length, 0);
      const errorSpace = Math.min(errorChars, maxCharacters * 0.6); // Up to 60% for errors
      const otherSpace = maxCharacters - errorSpace;
      
      // Truncate each category
      const truncatedErrors = this.truncateLogsSimple(errors, errorSpace, preserveLatest);
      const truncatedOthers = this.truncateLogsSimple(others, otherSpace, preserveLatest);
      
      // Combine and sort by timestamp
      return [...truncatedErrors, ...truncatedOthers].sort(
        (a, b) => a.timestamp.getTime() - b.timestamp.getTime()
      );
    }
    
    // Simple truncation without prioritization
    return this.truncateLogsSimple(logs, maxCharacters, preserveLatest);
  }
  
  /**
   * Simple truncation that preserves either latest or earliest logs
   */
  private static truncateLogsSimple(
    logs: Array<{ type: string; text: string; timestamp: Date }>,
    maxCharacters: number,
    preserveLatest: boolean
  ): Array<{ type: string; text: string; timestamp: Date }> {
    if (logs.length === 0) return [];
    
    const result: Array<{ type: string; text: string; timestamp: Date }> = [];
    let currentSize = 0;
    
    // Add truncation notice size
    const truncationNotice = { 
      type: 'info', 
      text: `... ${logs.length} logs truncated to fit context window ...`,
      timestamp: new Date()
    };
    const noticeSize = truncationNotice.text.length;
    
    // Process logs in order (latest first if preserveLatest)
    const orderedLogs = preserveLatest ? [...logs].reverse() : logs;
    
    for (const log of orderedLogs) {
      const logSize = log.text.length;
      
      // Check if we can fit this log plus the truncation notice
      if (currentSize + logSize + noticeSize <= maxCharacters) {
        result.push(log);
        currentSize += logSize;
      } else if (currentSize + noticeSize < maxCharacters) {
        // Truncate this log to fit
        const remainingSpace = maxCharacters - currentSize - noticeSize;
        if (remainingSpace > 50) { // Only truncate if meaningful content remains
          result.push({
            ...log,
            text: log.text.substring(0, remainingSpace - 3) + '...'
          });
        }
        break;
      } else {
        break;
      }
    }
    
    // Restore original order
    if (preserveLatest) {
      result.reverse();
    }
    
    // Add truncation notice if we actually truncated
    if (result.length < logs.length) {
      const position = preserveLatest ? 0 : result.length;
      result.splice(position, 0, truncationNotice);
    }
    
    return result;
  }
  
  /**
   * Truncate a single message intelligently
   */
  static truncateMessage(message: string, maxCharacters: number): string {
    if (message.length <= maxCharacters) {
      return message;
    }
    
    // For very small limits, just truncate
    if (maxCharacters < 100) {
      return message.substring(0, maxCharacters - 3) + '...';
    }
    
    // Try to truncate at a reasonable boundary
    const halfSize = Math.floor(maxCharacters / 2) - 20;
    
    // Show beginning and end
    const start = message.substring(0, halfSize);
    const end = message.substring(message.length - halfSize);
    
    return `${start}\n... truncated ${message.length - maxCharacters} characters ...\n${end}`;
  }
  
  /**
   * Estimate token count for a string
   */
  static estimateTokens(text: string): number {
    // Rough estimate: 1 token ≈ 4 characters
    // Adjust for whitespace and punctuation
    const wordCount = text.split(/\s+/).length;
    const charCount = text.length;
    
    // Average between character and word-based estimates
    const charEstimate = charCount / this.CHARS_PER_TOKEN;
    const wordEstimate = wordCount * 1.3; // Words average ~1.3 tokens
    
    return Math.ceil((charEstimate + wordEstimate) / 2);
  }
  
  /**
   * Group similar logs to reduce redundancy
   */
  static groupSimilarLogs(
    logs: Array<{ type: string; text: string; timestamp: Date }>
  ): Array<{ type: string; text: string; timestamp: Date; count?: number }> {
    const grouped: Array<{ 
      type: string; 
      text: string; 
      timestamp: Date; 
      count?: number;
      pattern?: string;
    }> = [];
    
    const patterns = new Map<string, { 
      count: number; 
      firstTimestamp: Date;
      lastTimestamp: Date;
      type: string;
    }>();
    
    for (const log of logs) {
      // Create a pattern by removing numbers and timestamps
      const pattern = log.text
        .replace(/\d+/g, 'N')
        .replace(/\b[0-9a-f]{8,}\b/gi, 'HEX')
        .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/g, 'TIMESTAMP');
      
      const existing = patterns.get(pattern);
      if (existing && existing.type === log.type) {
        existing.count++;
        existing.lastTimestamp = log.timestamp;
      } else {
        patterns.set(pattern, {
          count: 1,
          firstTimestamp: log.timestamp,
          lastTimestamp: log.timestamp,
          type: log.type
        });
        grouped.push({ ...log, pattern });
      }
    }
    
    // Replace grouped logs with summaries
    return grouped.map(log => {
      if (log.pattern) {
        const patternInfo = patterns.get(log.pattern);
        if (patternInfo && patternInfo.count > 1) {
          return {
            type: log.type,
            text: `${log.text} (repeated ${patternInfo.count} times)`,
            timestamp: patternInfo.firstTimestamp,
            count: patternInfo.count
          };
        }
      }
      delete log.pattern;
      return log;
    });
  }
}