## Resolver Profiling Infrastructure
##
## This module provides profiling tools for measuring resolver performance
## and identifying optimization opportunities.
##
## **Features:**
## - Operation timing with high precision
## - Call count tracking
## - Hot path identification (top 10 by time and frequency)
## - Optimization recommendations
## - CSV export for detailed analysis

import times
import tables
import algorithm
import strformat
import strutils

# ============================================================================
# Profiling Data Structures
# ============================================================================

type
  OperationKind* = enum
    ## Types of resolver operations to profile
    VariantUnification
    GraphConstruction
    ConflictDetection
    TopologicalSort
    SolverExecution
    BuildSynthesis
    CacheOperation
    HashCalculation
    PackageResolution
    DependencyFetch

  OperationTiming* = object
    ## Timing data for a single operation
    kind*: OperationKind
    name*: string
    startTime*: float
    endTime*: float
    duration*: float

  OperationStats* = object
    ## Aggregated statistics for an operation type
    kind*: OperationKind
    name*: string
    callCount*: int
    totalTime*: float
    minTime*: float
    maxTime*: float
    avgTime*: float
    percentOfTotal*: float

  Profiler* = ref object
    ## Main profiler object
    enabled*: bool
    timings*: seq[OperationTiming]
    startTime*: float
    endTime*: float
    totalTime*: float

# ============================================================================
# Global Profiler Instance
# ============================================================================

var globalProfiler* = Profiler(
  enabled: false,
  timings: @[],
  startTime: 0.0,
  endTime: 0.0,
  totalTime: 0.0
)

# ============================================================================
# Profiler Control
# ============================================================================

proc enableProfiler*() =
  ## Enable profiling
  globalProfiler.enabled = true
  globalProfiler.timings = @[]
  globalProfiler.startTime = epochTime()

proc disableProfiler*() =
  ## Disable profiling
  globalProfiler.enabled = false
  globalProfiler.endTime = epochTime()
  globalProfiler.totalTime = globalProfiler.endTime - globalProfiler.startTime

proc isEnabled*(): bool =
  ## Check if profiler is enabled
  return globalProfiler.enabled

proc clearProfiler*() =
  ## Clear all profiling data
  globalProfiler.timings = @[]
  globalProfiler.startTime = 0.0
  globalProfiler.endTime = 0.0
  globalProfiler.totalTime = 0.0

# ============================================================================
# Operation Timing
# ============================================================================

proc startOperation*(kind: OperationKind, name: string = ""): int =
  ## Start timing an operation
  ##
  ## Returns an operation ID that should be passed to endOperation()
  ##
  ## **Example:**
  ## ```nim
  ## let opId = startOperation(VariantUnification, "unify-nginx")
  ## # ... do work ...
  ## endOperation(opId)
  ## ```
  
  if not globalProfiler.enabled:
    return -1
  
  let timing = OperationTiming(
    kind: kind,
    name: name,
    startTime: epochTime(),
    endTime: 0.0,
    duration: 0.0
  )
  
  globalProfiler.timings.add(timing)
  return globalProfiler.timings.len - 1

proc endOperation*(opId: int) =
  ## End timing an operation
  ##
  ## **Example:**
  ## ```nim
  ## let opId = startOperation(VariantUnification)
  ## # ... do work ...
  ## endOperation(opId)
  ## ```
  
  if not globalProfiler.enabled or opId < 0 or opId >= globalProfiler.timings.len:
    return
  
  let endTime = epochTime()
  globalProfiler.timings[opId].endTime = endTime
  globalProfiler.timings[opId].duration = endTime - globalProfiler.timings[opId].startTime

template profileOperation*(kind: OperationKind, name: string, body: untyped) =
  ## Profile a block of code
  ##
  ## **Example:**
  ## ```nim
  ## profileOperation(VariantUnification, "unify-nginx"):
  ##   let result = unifyVariants(demands)
  ## ```
  
  let opId = startOperation(kind, name)
  try:
    body
  finally:
    endOperation(opId)

# ============================================================================
# Statistics Calculation
# ============================================================================

proc calculateStats*(): seq[OperationStats] =
  ## Calculate aggregated statistics for all operations
  ##
  ## Returns statistics sorted by total time (descending)
  
  if globalProfiler.timings.len == 0:
    return @[]
  
  # Group timings by operation kind
  var statsByKind = initTable[OperationKind, OperationStats]()
  
  for timing in globalProfiler.timings:
    if timing.kind notin statsByKind:
      statsByKind[timing.kind] = OperationStats(
        kind: timing.kind,
        name: $timing.kind,
        callCount: 0,
        totalTime: 0.0,
        minTime: high(float),
        maxTime: 0.0,
        avgTime: 0.0,
        percentOfTotal: 0.0
      )
    
    var stats = statsByKind[timing.kind]
    stats.callCount += 1
    stats.totalTime += timing.duration
    stats.minTime = min(stats.minTime, timing.duration)
    stats.maxTime = max(stats.maxTime, timing.duration)
    statsByKind[timing.kind] = stats
  
  # Calculate averages and percentages
  let totalTime = globalProfiler.totalTime
  
  for kind, stats in statsByKind.mpairs:
    stats.avgTime = stats.totalTime / stats.callCount.float
    stats.percentOfTotal = (stats.totalTime / totalTime) * 100.0
  
  # Convert to sequence and sort by total time
  result = @[]
  for stats in statsByKind.values:
    result.add(stats)
  
  result.sort do (a, b: OperationStats) -> int:
    if a.totalTime > b.totalTime: -1
    elif a.totalTime < b.totalTime: 1
    else: 0

proc getHotPaths*(limit: int = 10): seq[OperationStats] =
  ## Get top N operations by total time
  ##
  ## **Example:**
  ## ```nim
  ## let hotPaths = getHotPaths(10)
  ## for path in hotPaths:
  ##   echo fmt"{path.name}: {path.totalTime:.3f}s ({path.percentOfTotal:.1f}%)"
  ## ```
  
  let allStats = calculateStats()
  
  if allStats.len <= limit:
    return allStats
  
  return allStats[0..<limit]

proc getBottlenecks*(threshold: float = 15.0): seq[OperationStats] =
  ## Get operations consuming more than threshold% of total time
  ##
  ## **Example:**
  ## ```nim
  ## let bottlenecks = getBottlenecks(15.0)  # Operations using >15% of time
  ## ```
  
  let allStats = calculateStats()
  
  result = @[]
  for stats in allStats:
    if stats.percentOfTotal >= threshold:
      result.add(stats)

# ============================================================================
# Reporting
# ============================================================================

proc printReport*() =
  ## Print profiling report to stdout
  
  if globalProfiler.timings.len == 0:
    echo "No profiling data available"
    return
  
  echo ""
  echo "=" .repeat(80)
  echo "RESOLVER PROFILING REPORT"
  echo "=" .repeat(80)
  echo ""
  echo fmt"Total time: {globalProfiler.totalTime:.3f}s"
  echo fmt"Total operations: {globalProfiler.timings.len}"
  echo ""
  
  # Print statistics table
  echo "Operation Statistics:"
  echo "-" .repeat(80)
  echo "Operation                       Calls      Total        Avg        Min        Max      %"
  echo "-" .repeat(80)
  
  let stats = calculateStats()
  for s in stats:
    echo fmt"{s.name:<30} {s.callCount:>8} {s.totalTime:>10.3f}s {s.avgTime:>10.6f}s {s.minTime:>10.6f}s {s.maxTime:>10.6f}s {s.percentOfTotal:>5.1f}%"
  
  echo "-" .repeat(80)
  echo ""
  
  # Print hot paths
  echo "Hot Paths (Top 10 by time):"
  echo "-" .repeat(80)
  
  let hotPaths = getHotPaths(10)
  for i, path in hotPaths:
    echo fmt"{i+1:>2}. {path.name:<30} {path.totalTime:>10.3f}s ({path.percentOfTotal:>5.1f}%)"
  
  echo ""
  
  # Print bottlenecks
  let bottlenecks = getBottlenecks(15.0)
  if bottlenecks.len > 0:
    echo "Bottlenecks (>15% of total time):"
    echo "-" .repeat(80)
    
    for bottleneck in bottlenecks:
      echo fmt"⚠️  {bottleneck.name}: {bottleneck.totalTime:.3f}s ({bottleneck.percentOfTotal:.1f}%)"
    
    echo ""

proc getOptimizationRecommendations*(): seq[string] =
  ## Get optimization recommendations based on profiling data
  ##
  ## **Example:**
  ## ```nim
  ## let recommendations = getOptimizationRecommendations()
  ## for rec in recommendations:
  ##   echo rec
  ## ```
  
  result = @[]
  
  let bottlenecks = getBottlenecks(15.0)
  
  if bottlenecks.len == 0:
    result.add("✅ No major bottlenecks detected (all operations <15% of total time)")
    return
  
  for bottleneck in bottlenecks:
    case bottleneck.kind:
    of VariantUnification:
      result.add(fmt"🔧 Optimize variant unification ({bottleneck.percentOfTotal:.1f}% of time)")
      result.add("   → Consider bit vector representation for O(1) operations")
      result.add("   → Cache unification results for repeated demands")
    
    of GraphConstruction:
      result.add(fmt"🔧 Optimize graph construction ({bottleneck.percentOfTotal:.1f}% of time)")
      result.add("   → Use indexed lookups instead of linear scans")
      result.add("   → Parallelize independent subgraph construction")
    
    of ConflictDetection:
      result.add(fmt"🔧 Optimize conflict detection ({bottleneck.percentOfTotal:.1f}% of time)")
      result.add("   → Build package index for O(n) instead of O(n²) checks")
      result.add("   → Use bloom filters for quick negative checks")
    
    of SolverExecution:
      result.add(fmt"🔧 Optimize solver execution ({bottleneck.percentOfTotal:.1f}% of time)")
      result.add("   → Implement clause learning and caching")
      result.add("   → Use better heuristics for variable selection")
    
    of HashCalculation:
      result.add(fmt"🔧 Optimize hash calculation ({bottleneck.percentOfTotal:.1f}% of time)")
      result.add("   → Cache hash results for repeated inputs")
      result.add("   → Use faster hash algorithm (xxh3 instead of blake2b)")
    
    of CacheOperation:
      result.add(fmt"🔧 Optimize cache operations ({bottleneck.percentOfTotal:.1f}% of time)")
      result.add("   → Increase cache size to improve hit rate")
      result.add("   → Use more efficient cache data structure")
    
    else:
      result.add(fmt"🔧 Optimize {bottleneck.name} ({bottleneck.percentOfTotal:.1f}% of time)")

proc printOptimizationRecommendations*() =
  ## Print optimization recommendations
  
  echo ""
  echo "=" .repeat(80)
  echo "OPTIMIZATION RECOMMENDATIONS"
  echo "=" .repeat(80)
  echo ""
  
  let recommendations = getOptimizationRecommendations()
  for rec in recommendations:
    echo rec
  
  echo ""

# ============================================================================
# CSV Export
# ============================================================================

proc exportToCSV*(filename: string) =
  ## Export profiling data to CSV file
  ##
  ## **Example:**
  ## ```nim
  ## exportToCSV("profiling_results.csv")
  ## ```
  
  var csv = "Operation,Name,CallCount,TotalTime,AvgTime,MinTime,MaxTime,PercentOfTotal\n"
  
  let stats = calculateStats()
  for s in stats:
    csv.add(fmt"{s.kind},{s.name},{s.callCount},{s.totalTime},{s.avgTime},{s.minTime},{s.maxTime},{s.percentOfTotal}\n")
  
  writeFile(filename, csv)
  echo fmt"Profiling data exported to {filename}"

proc exportDetailedToCSV*(filename: string) =
  ## Export detailed timing data to CSV file
  ##
  ## **Example:**
  ## ```nim
  ## exportDetailedToCSV("profiling_detailed.csv")
  ## ```
  
  var csv = "Operation,Name,StartTime,EndTime,Duration\n"
  
  for timing in globalProfiler.timings:
    csv.add(fmt"{timing.kind},{timing.name},{timing.startTime},{timing.endTime},{timing.duration}\n")
  
  writeFile(filename, csv)
  echo fmt"Detailed profiling data exported to {filename}"

# ============================================================================
# Example Usage
# ============================================================================

when isMainModule:
  import std/random
  
  # Enable profiler
  enableProfiler()
  
  # Simulate some operations
  for i in 0..<100:
    profileOperation(VariantUnification, fmt"unify-{i}"):
      sleep(rand(1..10))  # Simulate work
    
    if i mod 10 == 0:
      profileOperation(GraphConstruction, fmt"graph-{i}"):
        sleep(rand(5..15))
    
    if i mod 20 == 0:
      profileOperation(ConflictDetection, fmt"conflict-{i}"):
        sleep(rand(10..30))
  
  # Disable profiler
  disableProfiler()
  
  # Print report
  printReport()
  printOptimizationRecommendations()
  
  # Export to CSV
  exportToCSV("profiling_results.csv")
  exportDetailedToCSV("profiling_detailed.csv")
