/**
 * Plain-language humanizer for config-audit findings.
 *
 * Pure functions. Never mutate inputs. Translates technical scanner output
 * into user-friendly language at output-formatting time. Adds three new
 * fields to each finding:
 *   - userImpactCategory: human-readable label per scanner (research/02)
 *   - userActionLanguage: one-line urgency phrase per severity
 *   - relevanceContext: deterministic file-pattern heuristic
 *
 * Original id, scanner, severity, file, line, evidence, category, autoFixable
 * are preserved exactly. Title, description, recommendation are replaced when
 * a translation is found; otherwise the originals are kept.
 *
 * Lookup order (per scanner prefix):
 *   1. exact title in TRANSLATIONS[prefix].static
 *   2. first regex match in TRANSLATIONS[prefix].patterns
 *   3. TRANSLATIONS[prefix]._default
 *   4. fallthrough: original strings (when scanner prefix has no entry)
 *
 * Zero external dependencies.
 */

import { TRANSLATIONS } from './humanizer-data.mjs';

/**
 * Map scanner prefix to user-facing impact-category label (research/02 line 124).
 */
const SCANNER_TO_CATEGORY = {
  CML: 'Configuration mistake',
  SET: 'Configuration mistake',
  HKV: 'Configuration mistake',
  RUL: 'Configuration mistake',
  MCP: 'Configuration mistake',
  IMP: 'Configuration mistake',
  CNF: 'Conflict',
  COL: 'Conflict',
  TOK: 'Wasted tokens',
  CPS: 'Wasted tokens',
  DIS: 'Dead config',
  GAP: 'Missed opportunity',
  PLH: 'Configuration mistake',
};

/**
 * Map severity to one-line action-language phrase (research/02 line 134).
 */
const SEVERITY_TO_ACTION = {
  critical: 'Fix this now',
  high: 'Fix soon',
  medium: 'Fix when convenient',
  low: 'Optional cleanup',
  info: 'FYI',
};

/**
 * Compute relevance context from a finding's file path. Deterministic, in-process,
 * no subprocess. Conservative — defaults to 'affects-everyone' when ambiguous.
 *
 * @param {string|null|undefined} filePath
 * @returns {'test-fixture-no-impact' | 'affects-this-machine-only' | 'affects-everyone'}
 */
export function computeRelevanceContext(filePath) {
  if (typeof filePath !== 'string' || filePath.length === 0) {
    return 'affects-everyone';
  }
  if (filePath.includes('/tests/fixtures/') || filePath.includes('/test/fixtures/')) {
    return 'test-fixture-no-impact';
  }
  // Match basename pattern *.local.* (e.g., settings.local.json, claude.local.md)
  const basename = filePath.split('/').pop() || '';
  if (/\.local\./.test(basename)) {
    return 'affects-this-machine-only';
  }
  return 'affects-everyone';
}

/**
 * Look up translation for a finding by scanner prefix and title.
 * Returns the translation object or null when no match (caller falls through to original).
 *
 * @param {string} scanner
 * @param {string} title
 * @returns {{title:string, description:string, recommendation:string} | null}
 */
function lookupTranslation(scanner, title) {
  const entry = TRANSLATIONS[scanner];
  if (!entry) return null;

  // 1. Exact static match
  if (typeof title === 'string' && entry.static && Object.prototype.hasOwnProperty.call(entry.static, title)) {
    return entry.static[title];
  }

  // 2. Pattern match
  if (Array.isArray(entry.patterns) && typeof title === 'string') {
    for (const p of entry.patterns) {
      if (p.regex instanceof RegExp && p.regex.test(title)) {
        return p.translation;
      }
    }
  }

  // 3. Default
  if (entry._default) {
    return entry._default;
  }

  return null;
}

/**
 * Humanize a single finding. Pure — never mutates input. Returns a new object.
 *
 * @param {object} finding - finding object from scanner output
 * @returns {object} new finding with translated title/description/recommendation +
 *                   userImpactCategory, userActionLanguage, relevanceContext fields
 */
export function humanizeFinding(finding) {
  if (!finding || typeof finding !== 'object') {
    return finding;
  }

  const translation = lookupTranslation(finding.scanner, finding.title);
  const category = SCANNER_TO_CATEGORY[finding.scanner] || 'Other';
  const action = SEVERITY_TO_ACTION[finding.severity] || 'FYI';
  const relevance = computeRelevanceContext(finding.file);

  const out = {
    // Preserve identifying / structural fields exactly
    id: finding.id,
    scanner: finding.scanner,
    severity: finding.severity,
    // Replace prose if a translation exists; otherwise keep originals
    title: translation ? translation.title : finding.title,
    description: translation ? translation.description : finding.description,
    file: finding.file ?? null,
    line: finding.line ?? null,
    evidence: finding.evidence ?? null,
    category: finding.category ?? null,
    recommendation: translation ? translation.recommendation : finding.recommendation,
    autoFixable: finding.autoFixable ?? false,
    // New humanized fields
    userImpactCategory: category,
    userActionLanguage: action,
    relevanceContext: relevance,
  };

  // Preserve optional details payload if present (v5 N6)
  if (finding.details && typeof finding.details === 'object') {
    out.details = finding.details;
  }

  return out;
}

/**
 * Humanize an array of findings. Pure — returns a new array of new objects.
 *
 * @param {object[]} findings
 * @returns {object[]}
 */
export function humanizeFindings(findings) {
  if (!Array.isArray(findings)) return findings;
  return findings.map(humanizeFinding);
}

/**
 * Humanize a top-level envelope produced by `runAllScanners`. Walks
 * `env.scanners[].findings`. Pure — returns a new envelope with new
 * scanner objects and new finding objects. The envelope-level shape
 * (scanners array, target_path, total_duration_ms, aggregate, etc.)
 * is preserved.
 *
 * @param {object} env
 * @returns {object}
 */
export function humanizeEnvelope(env) {
  if (!env || typeof env !== 'object' || !Array.isArray(env.scanners)) {
    return env;
  }

  const newScanners = env.scanners.map((s) => {
    if (!s || typeof s !== 'object') return s;
    if (!Array.isArray(s.findings)) return s;
    return {
      ...s,
      findings: humanizeFindings(s.findings),
    };
  });

  return {
    ...env,
    scanners: newScanners,
  };
}
