/**
 * CompactionPolicy - Token budget management and compaction decisions
 *
 * Implements contracts §16; DD §10.3.
 *
 * @module packages/runtime/src/context/CompactionPolicy
 */

import type { PromptLayer, BudgetFitResult } from '@aircoding/contracts'

export interface CompactionConfig {
  max_tokens: number
  compaction_threshold: number  // fraction of max_tokens that triggers compaction
  min_compact_tokens: number    // minimum tokens to free to consider compaction successful
  immutable_layers: string[]    // layers that can never be dropped
}

const DEFAULT_CONFIG: CompactionConfig = {
  max_tokens: 200000,
  compaction_threshold: 0.85,  // compact when 85% full
  min_compact_tokens: 40000,   // must free at least 40K tokens
  immutable_layers: ['runtime_invariant', 'role']
}

export interface CompactionDecision {
  should_compact: boolean
  reason?: string
  layers_to_compact?: PromptLayer[]
  estimated_tokens_freed?: number
}

export interface CompactionResult {
  ok: boolean
  compacted_layers: PromptLayer[]    // layers that were compacted/summarized
  summary_content: string             // compaction summary
  tokens_freed: number
  remaining_tokens: number
  warnings: string[]
}

export class CompactionPolicy {
  private config: CompactionConfig

  constructor(config?: Partial<CompactionConfig>) {
    this.config = { ...DEFAULT_CONFIG, ...config }
  }

  /**
   * Check if compaction should be triggered.
   * Returns decision with recommendation.
   */
  should_compact(layers: PromptLayer[], current_token_count: number): CompactionDecision {
    const threshold = this.config.max_tokens * this.config.compaction_threshold

    if (current_token_count < threshold) {
      return { should_compact: false, reason: `Tokens (${current_token_count}) below threshold (${threshold})` }
    }

    // Find compactable layers (not immutable, not L0/L1)
    const compactable = layers.filter(l => !this.config.immutable_layers.includes(l.level))
    if (compactable.length === 0) {
      return { should_compact: false, reason: 'No compactable layers found' }
    }

    // Estimate how many tokens we could free
    // Target compactable layers from highest level (most recent / least important)
    const sorted = [...compactable].sort((a, b) => {
      const level_order: Record<string, number> = {
        runtime_invariant: 0, role: 1, safety: 2, project_rules: 3,
        architecture: 4, task_spec: 5, evidence: 6, conversation: 7,
        tool_output: 8, user_override: 9, system_debug: 10
      }
      return level_order[b.level] - level_order[a.level]
    })

    let estimated_freed = 0
    const to_compact: PromptLayer[] = []
    const target = current_token_count - (this.config.max_tokens * 0.6) // Compact to 60%

    for (const layer of sorted) {
      if (estimated_freed >= target) break
      estimated_freed += layer.token_estimate || 0
      to_compact.push(layer)
    }

    if (to_compact.length === 0 || estimated_freed < this.config.min_compact_tokens) {
      return {
        should_compact: false,
        reason: `Insufficient tokens to free: ${estimated_freed} < ${this.config.min_compact_tokens}`
      }
    }

    return {
      should_compact: true,
      reason: `Token count (${current_token_count}) exceeds threshold (${threshold})`,
      layers_to_compact: to_compact,
      estimated_tokens_freed: estimated_freed
    }
  }

  /**
   * Execute compaction on the given layers.
   * Returns compacted result with summary.
   */
  compact(layers_to_compact: PromptLayer[], all_layers: PromptLayer[]): CompactionResult {
    const warnings: string[] = []
    let tokens_freed = 0

    // Check immutable layers are preserved
    const immutable_preserved = all_layers.filter(l => this.config.immutable_layers.includes(l.level))
    if (immutable_preserved.length < this.config.immutable_layers.length) {
      warnings.push('Some immutable layers were in the compaction set - preserving them')
    }

    // Generate summary of compacted layers
    const summary_parts: string[] = []
    for (const layer of layers_to_compact) {
      const level = layer.level
      const tokens = layer.token_estimate || 0
      tokens_freed += tokens
      summary_parts.push(`- ${level}: ~${Math.round(tokens)} tokens (source: ${layer.source_ref || 'inline'})`)
    }

    const summary_content = [
      '# Compaction Summary',
      '',
      `Compact ${new Date().toISOString()}: removed ${layers_to_compact.length} layers, freed ~${Math.round(tokens_freed)} tokens`,
      '',
      '## Compacted Layers',
      ...summary_parts,
      '',
      '## Preserved Layers',
      ...all_layers
        .filter(l => !layers_to_compact.includes(l))
        .map(l => `- ${l.level}: ~${Math.round(l.token_estimate || 0)} tokens`)
    ].join('\n')

    const remaining_tokens = all_layers
      .filter(l => !layers_to_compact.includes(l))
      .reduce((sum, l) => sum + (l.token_estimate || 0), 0)

    return {
      ok: true,
      compacted_layers: layers_to_compact,
      summary_content,
      tokens_freed,
      remaining_tokens,
      warnings
    }
  }

  /**
   * Fit layers into a token budget.
   * Returns fitted layers, omitted layers, and omissions report.
   */
  fit_to_budget(layers: PromptLayer[], budget: number): BudgetFitResult {
    // Sort by priority (lower = higher priority)
    const sorted = [...layers].sort((a, b) => a.priority - b.priority)

    const fitted: PromptLayer[] = []
    const omitted: PromptLayer[] = []
    const omissions: string[] = []
    let total_tokens = 0

    for (const layer of sorted) {
      const tokens = layer.token_estimate || 0

      if (this.config.immutable_layers.includes(layer.level)) {
        // Never omit immutable layers
        fitted.push(layer)
        total_tokens += tokens
        if (total_tokens > budget) {
          omissions.push(`WARNING: Budget exceeded by immutable layer: ${layer.level}`)
        }
        continue
      }

      if (total_tokens + tokens <= budget) {
        fitted.push(layer)
        total_tokens += tokens
      } else {
        omitted.push(layer)
        omissions.push(`Omitted ${layer.level}: would exceed budget (${total_tokens} + ${Math.round(tokens)} > ${budget})`)
      }
    }

    return { fitted, omitted, omissions, total_tokens }
  }
}

export function createCompactionPolicy(config?: Partial<CompactionConfig>): CompactionPolicy {
  return new CompactionPolicy(config)
}