/**
 * Token Estimation Service
 * 
 * Multi-layer token estimation system with ML-based accuracy improvement
 */

import { PrismaClient } from '@prisma/client';
import { logger } from '../utils/logger';

const prisma = new PrismaClient();

export interface TokenEstimate {
  estimated_tokens: number;
  confidence: 'low' | 'medium' | 'high';
  breakdown?: {
    base: number;
    languageFactor: number;
    htmlComplexity: number;
    safetyBuffer: number;
    mlAdjustment: number;
  };
}

export interface BatchEstimateResult {
  estimates: Record<string, TokenEstimate>;
  total_tokens: number;
  cache_hit: boolean;
}

export class TokenEstimator {
  
  /**
   * Layer 1: Base tokenization (character count / 4)
   */
  private estimateBaseTokens(content: string): number {
    return Math.ceil(content.length / 4);
  }
  
  /**
   * Layer 2: Language-specific factors
   * 
   * Based on linguistic analysis of 131 languages
   * Factor represents token density compared to English baseline
   */
  private getLanguageFactor(targetLang: string): number {
    const factors: Record<string, number> = {
      // CJK languages (most efficient)
      'zh': 0.60, 'zh-cn': 0.60, 'zh-tw': 0.60, 'zh-hk': 0.60,
      'ja': 0.65,
      'ko': 0.70,
      
      // Compact languages
      'th': 0.75, 'vi': 0.80, 'id': 0.85, 'ms': 0.85,
      'tl': 0.85, 'sw': 0.85, 'yo': 0.85,
      
      // Average efficiency
      'en': 1.00, 'es': 1.05, 'fr': 1.08, 'pt': 1.05,
      'it': 1.06, 'ro': 1.07, 'ca': 1.05, 'gl': 1.05,
      'nl': 1.15, 'af': 1.10, 'sq': 1.08, 'az': 1.25,
      'eu': 1.18, 'be': 1.12, 'bn': 0.75, 'bs': 1.10,
      
      // Verbose languages
      'de': 1.20, 'sv': 1.12, 'no': 1.12, 'nb': 1.12,
      'da': 1.12, 'fi': 1.25, 'hu': 1.22, 'cs': 1.18,
      'pl': 1.18, 'ru': 1.10, 'uk': 1.12, 'bg': 1.10,
      'hr': 1.12, 'sk': 1.15, 'sl': 1.12, 'sr': 1.10,
      
      // Agglutinative languages (most verbose)
      'tr': 1.28, 'kk': 1.25, 'uz': 1.25, 'mn': 1.22,
      'ky': 1.22, 'tk': 1.25, 'tt': 1.20,
      
      // Romance languages
      'la': 1.08, 'eo': 1.10, 'ia': 1.05,
      
      // Germanic languages
      'is': 1.18, 'fo': 1.15, 'fy': 1.12, 'lb': 1.15,
      
      // Slavic languages
      'mk': 1.10, 'mt': 1.12, 'cy': 1.15, 'ga': 1.15,
      'gd': 1.18, 'br': 1.12,
      
      // Baltic languages
      'lt': 1.18, 'lv': 1.15, 'et': 1.20,
      
      // Semitic languages
      'ar': 0.85, 'he': 0.85, 'ur': 0.88, 'fa': 0.90,
      'ps': 0.92,
      
      // Indic languages
      'hi': 0.78, 'mr': 0.78, 'ne': 0.80, 'pa': 0.80,
      'gu': 0.80, 'or': 0.82, 'ta': 0.75, 'te': 0.75,
      'kn': 0.78, 'ml': 0.80, 'si': 0.82, 'my': 0.80,
      'km': 0.85, 'lo': 0.85,
      
      // African languages
      'ha': 0.90, 'ig': 0.88, 'zu': 0.92, 'xh': 0.92,
      'sn': 0.90, 'st': 0.90, 'tn': 0.90, 'ny': 0.90,
      'mg': 0.88, 'rw': 0.88, 'so': 0.90, 'am': 0.85,
      'ti': 0.85, 'om': 0.88,
      
      // Southeast Asian languages
      'jw': 0.85, 'su': 0.85, 'tg': 0.88, 'ceb': 0.88,
      'hmn': 0.90,
      
      // Indigenous languages
      'mi': 0.92, 'sm': 0.90, 'haw': 0.92, 'qu': 0.95,
      'ay': 0.95, 'gn': 0.95,
      
      // Constructed languages
      'vo': 1.10,
      
      // Other languages
      'ht': 1.05, 'co': 1.08, 'ku': 0.95, 'yi': 0.88,
    };
    
    return factors[targetLang.toLowerCase()] || 1.00;
  }
  
  /**
   * Layer 3: HTML complexity analysis
   * 
   * Returns complexity level and overhead percentage
   */
  public analyzeHTMLComplexity(content: string): {
    level: 'none' | 'light' | 'medium' | 'heavy';
    overhead: number;
  } {
    const htmlTagCount = (content.match(/<[^>]+>/g) || []).length;
    const totalLength = content.length;
    const htmlRatio = htmlTagCount > 0 ? (htmlTagCount * 10) / totalLength : 0;
    
    if (htmlRatio === 0) {
      return { level: 'none', overhead: 0 };
    } else if (htmlRatio < 0.05) {
      return { level: 'light', overhead: 0.05 };
    } else if (htmlRatio < 0.15) {
      return { level: 'medium', overhead: 0.10 };
    } else {
      return { level: 'heavy', overhead: 0.15 };
    }
  }
  
  /**
   * Layer 4: Safety buffer (5-8%)
   * 
   * Ensures we never underestimate
   */
  private calculateSafetyBuffer(baseTokens: number, complexity: ReturnType<typeof this.analyzeHTMLComplexity>): number {
    const baseBuffer = 0.05; // 5% minimum
    const complexityBonus = complexity.level === 'heavy' ? 0.03 : 0.00;
    return Math.ceil(baseTokens * (baseBuffer + complexityBonus));
  }
  
  /**
   * Layer 5: ML adjustment from historical data
   * 
   * Learns from actual token usage to improve accuracy over time
   */
  private async getMLAdjustment(targetLang: string): Promise<number> {
    try {
      const stats = await prisma.$queryRaw<Array<{ ml_adjustment_ratio: number }>>`
        SELECT ml_adjustment_ratio 
        FROM accuracy_stats 
        WHERE language = ${targetLang} 
        AND sample_count >= 10
        LIMIT 1
      `;
      
      if (stats && stats.length > 0) {
        return stats[0].ml_adjustment_ratio || 1.00;
      }
    } catch (error) {
      logger.warn('Failed to get ML adjustment', { targetLang, error });
    }
    
    return 1.00; // Default: no adjustment
  }
  
  /**
   * Main estimation function
   * 
   * Combines all 5 layers to produce accurate token estimate
   */
  async estimateSingle(
    content: string,
    _sourceLang: string,
    targetLang: string
  ): Promise<TokenEstimate> {
    // Layer 1: Base tokens
    const baseTokens = this.estimateBaseTokens(content);
    
    // Layer 2: Language factor
    const langFactor = this.getLanguageFactor(targetLang);
    const languageAdjusted = Math.ceil(baseTokens * langFactor);
    
    // Layer 3: HTML complexity
    const complexity = this.analyzeHTMLComplexity(content);
    const complexityTokens = Math.ceil(languageAdjusted * complexity.overhead);
    
    // Layer 4: Safety buffer
    const safetyBuffer = this.calculateSafetyBuffer(languageAdjusted, complexity);
    
    // Layer 5: ML adjustment
    const mlAdjustment = await this.getMLAdjustment(targetLang);
    
    // Calculate final estimate
    const subtotal = languageAdjusted + complexityTokens + safetyBuffer;
    const finalTokens = Math.ceil(subtotal * mlAdjustment);
    
    // Determine confidence level
    const sampleCount = await this.getSampleCount(targetLang);
    let confidence: 'low' | 'medium' | 'high' = 'low';
    if (sampleCount >= 100) confidence = 'high';
    else if (sampleCount >= 10) confidence = 'medium';
    
    return {
      estimated_tokens: finalTokens,
      confidence,
      breakdown: {
        base: baseTokens,
        languageFactor: languageAdjusted - baseTokens,
        htmlComplexity: complexityTokens,
        safetyBuffer,
        mlAdjustment: finalTokens - subtotal,
      },
    };
  }
  
  /**
   * Batch estimation for multiple languages
   * 
   * @param content Content to translate
   * @param sourceLang Source language code
   * @param targetLangs Array of target language codes
   * @returns Estimates for all languages and total
   */
  async estimateBatch(
    content: string,
    sourceLang: string,
    targetLangs: string[]
  ): Promise<BatchEstimateResult> {
    const estimates: Record<string, TokenEstimate> = {};
    let totalTokens = 0;
    
    for (const targetLang of targetLangs) {
      const estimate = await this.estimateSingle(content, sourceLang, targetLang);
      estimates[targetLang] = estimate;
      totalTokens += estimate.estimated_tokens;
    }
    
    return {
      estimates,
      total_tokens: totalTokens,
      cache_hit: false, // Caching handled by WordPress layer
    };
  }
  
  /**
   * Get sample count for a language (for confidence calculation)
   */
  private async getSampleCount(language: string): Promise<number> {
    try {
      const result = await prisma.$queryRaw<Array<{ sample_count: number }>>`
        SELECT sample_count 
        FROM accuracy_stats 
        WHERE language = ${language}
        LIMIT 1
      `;
      
      return result && result.length > 0 ? result[0].sample_count : 0;
    } catch {
      return 0;
    }
  }
}

export const tokenEstimator = new TokenEstimator();
