/**
 * Diff risk-floor — deterministic review-need classifier.
 *
 * Given the set of changed files in a diff, derive a *minimum* review
 * requirement ("floor") from the change surface. This is the mechanical half
 * of the agent reflection loop (design §6): risky surfaces (auth, data, infra)
 * trip a review requirement regardless of what the agent self-reports.
 *
 * Precedence (authoritative ordering, see design §5):
 *   CI/tests  >  human merge  >  reviewer verdict  >  self-reflection
 * This module sits at the *floor*. It NEVER overrides CI or a human; a
 * `needs_review: false` verdict means "no surface tripped the floor", not
 * "safe to merge". Consumers MUST keep CI/tests authoritative above it.
 *
 * Pure and deterministic: no IO, no clock, no randomness. Same input → same
 * verdict. Safe to call from a Stop hook via `node -e` or to port inline.
 */

/** Review surfaces, ordered most- to least-sensitive. */
export type ReviewSurface = 'auth' | 'data' | 'infra' | 'build' | 'ui' | 'test' | 'docs' | 'none';

export interface RiskFloorInput {
  /** Paths of changed files, repo-relative. Order-insensitive. */
  filesChanged: string[];
  /** Optional diff size signals; reserved for future weighting. */
  insertions?: number;
  deletions?: number;
}

export interface RiskFloorVerdict {
  /** True when the change surface meets/exceeds the review threshold. */
  needs_review: boolean;
  /** Aggregate risk score in [0, 1] — the max surface weight across files. */
  score: number;
  /** The dominant (highest-weight) surface across all changed files. */
  surface: ReviewSurface;
  /** Human-readable explanation naming the surface and tripping files. */
  reason: string;
}

/** Default review threshold; `score >= THRESHOLD` ⇒ `needs_review`. */
export const DEFAULT_RISK_THRESHOLD = 0.5;

interface SurfaceRule {
  surface: ReviewSurface;
  weight: number;
  /** Case-insensitive regex matched against the file path. */
  pattern: RegExp;
}

/**
 * Surface classification rules, evaluated highest-weight first. The first
 * rule whose pattern matches a path classifies that file; the file's surface
 * is the highest-risk surface it matches (rules are pre-sorted by weight).
 */
const SURFACE_RULES: readonly SurfaceRule[] = [
  {
    surface: 'auth',
    weight: 1.0,
    pattern: /auth|login|session|token|permission|rbac|credential|secret/i,
  },
  {
    surface: 'data',
    weight: 0.9,
    pattern: /migration|prisma|schema|\.sql|entity|repository|seed/i,
  },
  {
    surface: 'infra',
    weight: 0.85,
    pattern: /docker|\.woodpecker|compose|traefik|deploy|helm|k8s|terraform/i,
  },
  {
    surface: 'build',
    weight: 0.6,
    pattern: /package\.json|tsconfig|turbo\.json|pnpm-|\.config\.|eslint|vite/i,
  },
  { surface: 'ui', weight: 0.4, pattern: /\.tsx|\.css|components\/|apps\/web\// },
  { surface: 'test', weight: 0.2, pattern: /\.spec\.|\.test\.|__tests__\// },
  { surface: 'docs', weight: 0.1, pattern: /\.md$|docs\// },
];

const NONE_WEIGHT = 0.0;

/** Classify a single path to its highest-risk surface and weight. */
function classify(path: string): { surface: ReviewSurface; weight: number } {
  for (const rule of SURFACE_RULES) {
    if (rule.pattern.test(path)) {
      return { surface: rule.surface, weight: rule.weight };
    }
  }
  return { surface: 'none', weight: NONE_WEIGHT };
}

/**
 * Evaluate the review risk-floor for a diff.
 *
 * @param input         changed files (+ optional size signals)
 * @param threshold     review cutoff; defaults to {@link DEFAULT_RISK_THRESHOLD}
 */
export function evaluateRiskFloor(
  input: RiskFloorInput,
  threshold: number = DEFAULT_RISK_THRESHOLD,
): RiskFloorVerdict {
  const files = (input.filesChanged ?? []).filter((f) => typeof f === 'string' && f.length > 0);

  if (files.length === 0) {
    return {
      needs_review: false,
      score: 0,
      surface: 'none',
      reason: 'no files changed',
    };
  }

  let topSurface: ReviewSurface = 'none';
  let topWeight = NONE_WEIGHT;
  const tripping: string[] = [];

  for (const file of files) {
    const { surface, weight } = classify(file);
    if (weight > topWeight) {
      topWeight = weight;
      topSurface = surface;
      tripping.length = 0;
      tripping.push(file);
    } else if (weight === topWeight && surface === topSurface && surface !== 'none') {
      tripping.push(file);
    }
  }

  const needs_review = topWeight >= threshold;
  const reason =
    topSurface === 'none'
      ? `no sensitive surface in ${files.length} changed file(s)`
      : `${topSurface} surface (weight ${topWeight}) in: ${tripping.join(', ')}`;

  return { needs_review, score: topWeight, surface: topSurface, reason };
}
