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

/**
 * EvaluatorEngine: orchestrates finding normalization, rubric selection,
 * prompt assembly, and verdict parsing for QA evaluation of security output.
 */

import { RUBRICS } from './rubrics.js';
import { normalizeFindings, formatForEvaluation, parseVerdicts } from './formatter.js';

export class EvaluatorEngine {
  /**
   * @param {object} [options]
   * @param {object} [options.rubrics] - Custom rubrics (defaults to RUBRICS)
   * @param {number} [options.maxFindings=25] - Max findings per evaluation batch
   * @param {number} [options.maxCharsPerFinding=2000] - Truncate threshold per finding
   */
  constructor(options = {}) {
    this.rubrics = options.rubrics ?? RUBRICS;
    this.maxFindings = options.maxFindings ?? 25;
    this.maxCharsPerFinding = options.maxCharsPerFinding ?? 2000;
    this._lastResult = null;
  }

  /**
   * Auto-select rubric based on the predominant finding type.
   * Normalizes findings first, counts types, picks the most common.
   * Falls back to 'compliance' for unknown/mixed types.
   *
   * @param {object[]} findings - Raw or normalized findings
   * @returns {object} Selected rubric
   */
  selectRubric(findings) {
    const normalized = Array.isArray(findings[0]?.type)
      ? findings
      : normalizeFindings(findings);

    const counts = { static: 0, dynamic: 0, correlated: 0, unknown: 0 };
    for (const f of normalized) {
      counts[f.type] = (counts[f.type] ?? 0) + 1;
    }

    // Pick the type with the highest count (excluding unknown)
    let bestType = 'compliance';
    let bestCount = 0;
    for (const [type, count] of Object.entries(counts)) {
      if (type !== 'unknown' && count > bestCount) {
        bestType = type;
        bestCount = count;
      }
    }

    // Map to rubric: known types map directly, unknown falls back to compliance
    return this.rubrics[bestType] ?? this.rubrics.compliance;
  }

  /**
   * Build an evaluation prompt from raw findings.
   * Normalizes, selects rubric, formats for evaluation.
   *
   * @param {object[]} findings - Raw findings (any type)
   * @returns {{ prompt: string, findingCount: number, rubricType: string }}
   */
  buildPrompt(findings) {
    const normalized = normalizeFindings(findings);
    const rubric = this.selectRubric(findings);
    const prompt = formatForEvaluation(normalized, rubric, {
      maxFindings: this.maxFindings,
      maxCharsPerFinding: this.maxCharsPerFinding,
    });

    const result = {
      prompt,
      findingCount: normalized.length,
      rubricType: rubric.type,
    };

    this._lastResult = result;
    return result;
  }

  /**
   * Parse LLM response text into structured verdicts.
   * @param {string} responseText - Raw LLM response
   * @returns {{ verdicts: object[], parseErrors: string[] }}
   */
  parseResponse(responseText) {
    return parseVerdicts(responseText);
  }

  /**
   * Most recent buildPrompt result, for observability.
   * @returns {{ prompt: string, findingCount: number, rubricType: string }|null}
   */
  get lastResult() {
    return this._lastResult;
  }
}
