/**
 * Pure metric computations extracted from complexity.js.
 *
 * Contains Halstead derived metrics, LOC metrics, and Maintainability Index —
 * all stateless math that can be reused by visitor-based and standalone paths.
 */

import type { HalsteadDerivedMetrics, LOCMetrics, TreeSitterNode } from '../types.js';

// ─── Halstead Derived Metrics ─────────────────────────────────────────────

/** Halstead delivered-bugs denominator (industry standard: V / 3000). */
const HALSTEAD_BUGS_DIVISOR = 3000;

/** Sum all values in a count map. */
function sumCounts(map: Map<string, number>): number {
  let total = 0;
  for (const c of map.values()) total += c;
  return total;
}

/**
 * Compute Halstead derived metrics from raw operator/operand counts.
 *
 * @param {Map<string, number>} operators - operator type/text → count
 * @param {Map<string, number>} operands  - operand text → count
 * @returns {{ n1: number, n2: number, bigN1: number, bigN2: number, vocabulary: number, length: number, volume: number, difficulty: number, effort: number, bugs: number }}
 */
export function computeHalsteadDerived(
  operators: Map<string, number>,
  operands: Map<string, number>,
): HalsteadDerivedMetrics {
  const n1 = operators.size;
  const n2 = operands.size;
  const bigN1 = sumCounts(operators);
  const bigN2 = sumCounts(operands);

  const vocabulary = n1 + n2;
  const length = bigN1 + bigN2;
  const volume = vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
  const difficulty = n2 > 0 ? (n1 / 2) * (bigN2 / n2) : 0;
  const effort = difficulty * volume;
  const bugs = volume / HALSTEAD_BUGS_DIVISOR;

  return {
    n1,
    n2,
    bigN1,
    bigN2,
    vocabulary,
    length,
    volume: +volume.toFixed(2),
    difficulty: +difficulty.toFixed(2),
    effort: +effort.toFixed(2),
    bugs: +bugs.toFixed(4),
  };
}

// ─── LOC Metrics ──────────────────────────────────────────────────────────

const C_STYLE_PREFIXES = ['//', '/*', '*', '*/'];

const COMMENT_PREFIXES = new Map<string, string[]>([
  ['javascript', C_STYLE_PREFIXES],
  ['typescript', C_STYLE_PREFIXES],
  ['tsx', C_STYLE_PREFIXES],
  ['go', C_STYLE_PREFIXES],
  ['rust', C_STYLE_PREFIXES],
  ['java', C_STYLE_PREFIXES],
  ['csharp', C_STYLE_PREFIXES],
  ['python', ['#']],
  ['ruby', ['#']],
  ['php', ['//', '#', '/*', '*', '*/']],
]);

/**
 * Compute LOC metrics from a function node's source text.
 *
 * @param {object} functionNode - tree-sitter node
 * @param {string} [language] - Language ID (falls back to C-style prefixes)
 * @returns {{ loc: number, sloc: number, commentLines: number }}
 */
export function computeLOCMetrics(functionNode: TreeSitterNode, language?: string): LOCMetrics {
  const text = functionNode.text;
  const lines = text.split('\n');
  const loc = lines.length;
  const prefixes = (language && COMMENT_PREFIXES.get(language)) || C_STYLE_PREFIXES;

  let commentLines = 0;
  let blankLines = 0;

  for (const line of lines) {
    const trimmed = line.trim();
    if (trimmed === '') {
      blankLines++;
    } else if (prefixes.some((p) => trimmed.startsWith(p))) {
      commentLines++;
    }
  }

  const sloc = Math.max(1, loc - blankLines - commentLines);
  return { loc, sloc, commentLines };
}

// ─── Maintainability Index ────────────────────────────────────────────────

/**
 * SEI Maintainability Index formula coefficients.
 * Original: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
 * Microsoft normalization: max(0, min(100, MI * 100/171))
 */
const MI_BASE = 171;
const MI_VOLUME_COEFF = 5.2;
const MI_CYCLOMATIC_COEFF = 0.23;
const MI_LOC_COEFF = 16.2;
const MI_COMMENT_AMPLITUDE = 50;
const MI_COMMENT_SCALE = 2.4;
const MI_NORMALIZE_SCALE = 100;

/**
 * Compute normalized Maintainability Index (0-100 scale).
 *
 * @param {number} volume - Halstead volume
 * @param {number} cyclomatic - Cyclomatic complexity
 * @param {number} sloc - Source lines of code
 * @param {number} [commentRatio] - Comment ratio (0-1), optional
 * @returns {number} Normalized MI (0-100)
 */
export function computeMaintainabilityIndex(
  volume: number,
  cyclomatic: number,
  sloc: number,
  commentRatio?: number,
): number {
  const safeVolume = Math.max(volume, 1);
  const safeSLOC = Math.max(sloc, 1);

  let mi =
    MI_BASE -
    MI_VOLUME_COEFF * Math.log(safeVolume) -
    MI_CYCLOMATIC_COEFF * cyclomatic -
    MI_LOC_COEFF * Math.log(safeSLOC);

  if (commentRatio != null && commentRatio > 0) {
    mi += MI_COMMENT_AMPLITUDE * Math.sin(Math.sqrt(MI_COMMENT_SCALE * commentRatio));
  }

  const normalized = Math.max(0, Math.min(MI_NORMALIZE_SCALE, (mi * MI_NORMALIZE_SCALE) / MI_BASE));
  return +normalized.toFixed(1);
}
