// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.
// NERF is a trademark of defconxt.

/**
 * NERF Token Budget Manager
 *
 * Analyzes input complexity and allocates context tokens across
 * phases (research, planning, execution, review) based on
 * file count, LOC, language diversity, and max file size.
 *
 * @module review/budget
 */

import { resolveInput, detectLanguage } from './engine.js';

// ---------------------------------------------------------------------------
// Complexity levels
// ---------------------------------------------------------------------------

/** @enum {string} */
export const Complexity = Object.freeze({
  SIMPLE: 'simple',
  MEDIUM: 'medium',
  COMPLEX: 'complex',
  EXTREME: 'extreme',
});

/**
 * Thresholds for complexity classification.
 */
const THRESHOLDS = {
  simple:  { maxFiles: 3,   maxLoc: 500,   maxLanguages: 1 },
  medium:  { maxFiles: 15,  maxLoc: 3000,  maxLanguages: 3 },
  complex: { maxFiles: 50,  maxLoc: 15000, maxLanguages: 5 },
  // anything above complex is extreme
};

/**
 * Default token budgets per complexity level.
 */
const DEFAULT_BUDGETS = {
  simple:  { total: 4000,  weights: { research: 0.15, planning: 0.15, execution: 0.50, review: 0.20 } },
  medium:  { total: 8000,  weights: { research: 0.20, planning: 0.20, execution: 0.40, review: 0.20 } },
  complex: { total: 12000, weights: { research: 0.25, planning: 0.20, execution: 0.35, review: 0.20 } },
  extreme: { total: 16000, weights: { research: 0.25, planning: 0.25, execution: 0.30, review: 0.20 } },
};

// ---------------------------------------------------------------------------
// Complexity Analysis
// ---------------------------------------------------------------------------

/**
 * Complexity metrics for a set of source files.
 */
export class ComplexityMetrics {
  constructor(opts = {}) {
    this.fileCount = opts.fileCount ?? 0;
    this.totalLoc = opts.totalLoc ?? 0;
    this.languages = opts.languages ?? [];
    this.languageCount = opts.languageCount ?? 0;
    this.maxFileSize = opts.maxFileSize ?? 0;
    this.avgFileSize = opts.avgFileSize ?? 0;
    this.complexity = opts.complexity ?? Complexity.SIMPLE;
  }
}

/**
 * Analyze complexity of resolved source files.
 *
 * @param {import('./engine.js').SourceFile[]} sources
 * @returns {ComplexityMetrics}
 */
export function analyzeComplexity(sources) {
  if (!sources.length) {
    return new ComplexityMetrics({ complexity: Complexity.SIMPLE });
  }

  const langSet = new Set();
  let totalLoc = 0;
  let maxFileSize = 0;

  for (const src of sources) {
    const loc = src.content.split('\n').length;
    totalLoc += loc;
    if (loc > maxFileSize) maxFileSize = loc;
    langSet.add(src.language);
  }

  const fileCount = sources.length;
  const languageCount = langSet.size;
  const avgFileSize = Math.round(totalLoc / fileCount);
  const languages = [...langSet].sort();

  // Classify complexity
  let complexity;
  if (fileCount <= THRESHOLDS.simple.maxFiles &&
      totalLoc <= THRESHOLDS.simple.maxLoc &&
      languageCount <= THRESHOLDS.simple.maxLanguages) {
    complexity = Complexity.SIMPLE;
  } else if (fileCount <= THRESHOLDS.medium.maxFiles &&
             totalLoc <= THRESHOLDS.medium.maxLoc &&
             languageCount <= THRESHOLDS.medium.maxLanguages) {
    complexity = Complexity.MEDIUM;
  } else if (fileCount <= THRESHOLDS.complex.maxFiles &&
             totalLoc <= THRESHOLDS.complex.maxLoc &&
             languageCount <= THRESHOLDS.complex.maxLanguages) {
    complexity = Complexity.COMPLEX;
  } else {
    complexity = Complexity.EXTREME;
  }

  return new ComplexityMetrics({
    fileCount,
    totalLoc,
    languages,
    languageCount,
    maxFileSize,
    avgFileSize,
    complexity,
  });
}

// ---------------------------------------------------------------------------
// Token Budget
// ---------------------------------------------------------------------------

/**
 * Structured token budget with per-phase allocations.
 */
export class TokenBudget {
  /**
   * @param {object} opts
   * @param {number} opts.total
   * @param {object} opts.phases - { research, planning, execution, review }
   * @param {ComplexityMetrics} opts.metrics
   */
  constructor(opts = {}) {
    this.total = opts.total ?? 0;
    this.phases = opts.phases ?? {};
    this.metrics = opts.metrics ?? new ComplexityMetrics();
  }

  toReport() {
    const lines = [
      `Token Budget (${this.metrics.complexity})`,
      `  Total: ${this.total} tokens`,
      `  Research:  ${this.phases.research} tokens`,
      `  Planning:  ${this.phases.planning} tokens`,
      `  Execution: ${this.phases.execution} tokens`,
      `  Review:    ${this.phases.review} tokens`,
      '',
      `Complexity: ${this.metrics.complexity}`,
      `  Files: ${this.metrics.fileCount}`,
      `  LOC: ${this.metrics.totalLoc}`,
      `  Languages: ${this.metrics.languages.join(', ')} (${this.metrics.languageCount})`,
      `  Max file: ${this.metrics.maxFileSize} lines`,
      `  Avg file: ${this.metrics.avgFileSize} lines`,
    ];
    return lines.join('\n');
  }

  toJSON() {
    return {
      total: this.total,
      phases: this.phases,
      complexity: this.metrics.complexity,
      metrics: {
        fileCount: this.metrics.fileCount,
        totalLoc: this.metrics.totalLoc,
        languages: this.metrics.languages,
        languageCount: this.metrics.languageCount,
        maxFileSize: this.metrics.maxFileSize,
        avgFileSize: this.metrics.avgFileSize,
      },
    };
  }
}

// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------

/**
 * Create a token budget for the given input.
 *
 * @param {string} input     - File path, directory, or code string
 * @param {object} [options]
 * @param {string} [options.language]  - Override language detection
 * @param {number} [options.total]     - Override total token budget
 * @param {object} [options.weights]   - Override phase weights { research, planning, execution, review }
 * @returns {Promise<TokenBudget>}
 */
export async function createTokenBudget(input, options = {}) {
  const sources = await resolveInput(input, { language: options.language });
  const metrics = analyzeComplexity(sources);

  const budgetConfig = DEFAULT_BUDGETS[metrics.complexity];
  const total = options.total ?? budgetConfig.total;
  const weights = { ...budgetConfig.weights, ...options.weights };

  // Normalize weights
  const weightSum = Object.values(weights).reduce((a, b) => a + b, 0);
  const phases = {};
  for (const [phase, weight] of Object.entries(weights)) {
    phases[phase] = Math.round((weight / weightSum) * total);
  }

  return new TokenBudget({ total, phases, metrics });
}
