/**
 * Prediction Scoring Loop
 *
 * Buba predicts Jake's score before review.
 * After review, compare prediction vs actual.
 * Track calibration over time.
 *
 * This creates a calibration dataset:
 * - predicted: { score, confidence }
 * - actual: { score, approved }
 * - delta: actual - predicted
 */

import * as fs from "node:fs";
import * as path from "node:path";
import type {
  CalibrationEntry,
  CalibrationCurve,
  PredictionResponse,
  DimensionScore,
} from "../types.js";
import { applyCalibration, computePredictionAccuracy } from "../analysis/calibration.js";

// ═══════════════════════════════════════════════════════════════
// PREDICTION STORE — JSONL file for calibration entries
// ═══════════════════════════════════════════════════════════════

const PREDICTIONS_FILE = "predictions.jsonl";

function getPredictionsPath(dataDir: string): string {
  return path.join(dataDir, PREDICTIONS_FILE);
}

/**
 * Store a prediction for later comparison.
 */
export async function storePrediction(
  dataDir: string,
  prediction: {
    taskType: string;
    workProductId: string;
    predictedScore: number;
    predictedConfidence: number;
  }
): Promise<void> {
  const dir = path.dirname(getPredictionsPath(dataDir));
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }

  const entry = {
    ...prediction,
    timestamp: new Date().toISOString(),
    resolved: false,
  };

  fs.appendFileSync(
    getPredictionsPath(dataDir),
    JSON.stringify(entry) + "\n",
    "utf-8"
  );
}

/**
 * Resolve a prediction with actual results after Jake's review.
 */
export async function resolvePrediction(
  dataDir: string,
  workProductId: string,
  actual: {
    score: number;
    approved: boolean;
  }
): Promise<CalibrationEntry | null> {
  const filePath = getPredictionsPath(dataDir);
  if (!fs.existsSync(filePath)) return null;

  const lines = fs.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);

  // Find the unresolved prediction for this work product
  for (let i = lines.length - 1; i >= 0; i--) {
    try {
      const entry = JSON.parse(lines[i]!) as any;
      if (entry.workProductId === workProductId && !entry.resolved) {
        const calibrationEntry: CalibrationEntry = {
          taskType: entry.taskType,
          predicted: {
            score: entry.predictedScore,
            confidence: entry.predictedConfidence,
          },
          actual: {
            score: actual.score,
            approved: actual.approved,
          },
          delta: actual.score - entry.predictedScore,
          timestamp: new Date().toISOString(),
        };

        // Mark as resolved
        entry.resolved = true;
        entry.actual = actual;
        entry.delta = calibrationEntry.delta;
        lines[i] = JSON.stringify(entry);
        fs.writeFileSync(filePath, lines.join("\n") + "\n", "utf-8");

        return calibrationEntry;
      }
    } catch {
      // Skip malformed lines
    }
  }

  return null;
}

// ═══════════════════════════════════════════════════════════════
// PREDICTION ENGINE
// ═══════════════════════════════════════════════════════════════

/**
 * Generate a prediction for a work product.
 *
 * Uses:
 * - Historical approval rates for this task type
 * - Self-assessed scores
 * - Calibration curve correction
 * - Known risk factors from learned patterns
 */
export function predict(
  params: {
    taskType: string;
    serverType?: string;
    selfAssessedScores?: DimensionScore[];
    rawConfidence: number;
  },
  calibrationCurve?: CalibrationCurve,
  historicalData?: {
    approvalRate: number;
    avgScore: number;
    count: number;
  }
): PredictionResponse {
  // Base predicted score from self-assessment or historical average
  let predictedScore: number;

  if (params.selfAssessedScores && params.selfAssessedScores.length > 0) {
    predictedScore =
      params.selfAssessedScores.reduce((sum, s) => sum + s.score, 0) /
      params.selfAssessedScores.length;
  } else if (historicalData) {
    predictedScore = historicalData.avgScore;
  } else {
    predictedScore = 7.0; // Default assumption
  }

  // Apply calibration to confidence
  let calibratedConfidence = params.rawConfidence;
  if (calibrationCurve) {
    calibratedConfidence = applyCalibration(
      params.rawConfidence,
      calibrationCurve
    );
  }

  // Predict decision based on score and confidence
  const approveProb = computeApprovalProbability(
    predictedScore,
    calibratedConfidence,
    historicalData?.approvalRate
  );

  const rejectProb = computeRejectionProbability(
    predictedScore,
    calibratedConfidence,
    historicalData?.approvalRate
  );

  const needsWorkProb = Math.max(0, 1 - approveProb - rejectProb);

  let predictedDecision: "approve" | "reject" | "needs_work";
  if (approveProb > rejectProb && approveProb > needsWorkProb) {
    predictedDecision = "approve";
  } else if (rejectProb > needsWorkProb) {
    predictedDecision = "reject";
  } else {
    predictedDecision = "needs_work";
  }

  // Identify risk factors
  const riskFactors: string[] = [];
  if (calibratedConfidence < 0.5) {
    riskFactors.push("Low confidence — uncertain about quality");
  }
  if (predictedScore < 6) {
    riskFactors.push("Predicted score below typical approval threshold");
  }

  // Check individual dimension scores
  if (params.selfAssessedScores) {
    for (const score of params.selfAssessedScores) {
      if (score.score < 5) {
        riskFactors.push(
          `${score.dimension} score (${score.score}) below minimum threshold`
        );
      }
    }
  }

  // Suggested improvements
  const suggestedImprovements: string[] = [];
  if (predictedScore < 7) {
    suggestedImprovements.push(
      "Consider additional self-review before submission"
    );
  }
  if (riskFactors.length > 2) {
    suggestedImprovements.push(
      "Multiple risk factors detected — run through full self-check"
    );
  }

  return {
    predictedScore: Math.round(predictedScore * 10) / 10,
    calibratedConfidence: Math.round(calibratedConfidence * 100) / 100,
    predictedDecision,
    decisionProbabilities: {
      approve: Math.round(approveProb * 100) / 100,
      reject: Math.round(rejectProb * 100) / 100,
      needsWork: Math.round(needsWorkProb * 100) / 100,
    },
    riskFactors,
    suggestedImprovements,
  };
}

// ═══════════════════════════════════════════════════════════════
// PROBABILITY CALCULATIONS
// ═══════════════════════════════════════════════════════════════

function computeApprovalProbability(
  score: number,
  confidence: number,
  historicalRate?: number
): number {
  // Score-based probability (sigmoid-like)
  const scoreFactor = 1 / (1 + Math.exp(-(score - 6) * 0.8));

  // Confidence-based factor
  const confidenceFactor = confidence;

  // Historical rate as prior
  const priorRate = historicalRate ?? 0.7;

  // Weighted combination
  return (
    scoreFactor * 0.5 + confidenceFactor * 0.2 + priorRate * 0.3
  );
}

function computeRejectionProbability(
  score: number,
  confidence: number,
  historicalRate?: number
): number {
  // Inverse of approval
  const scoreFactor = 1 / (1 + Math.exp((score - 4) * 0.8));
  const lowConfidenceFactor = 1 - confidence;
  const priorRejectionRate = 1 - (historicalRate ?? 0.7);

  return (
    scoreFactor * 0.5 + lowConfidenceFactor * 0.2 + priorRejectionRate * 0.3
  );
}

// ═══════════════════════════════════════════════════════════════
// HISTORICAL CALIBRATION ANALYSIS
// ═══════════════════════════════════════════════════════════════

/**
 * Load all resolved predictions and compute accuracy metrics.
 */
export async function loadCalibrationHistory(
  dataDir: string
): Promise<{
  entries: CalibrationEntry[];
  accuracy: number;
  bias: number; // positive = optimistic, negative = pessimistic
  count: number;
}> {
  const filePath = getPredictionsPath(dataDir);
  if (!fs.existsSync(filePath)) {
    return { entries: [], accuracy: 0, bias: 0, count: 0 };
  }

  const lines = fs.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
  const entries: CalibrationEntry[] = [];

  for (const line of lines) {
    try {
      const data = JSON.parse(line) as any;
      if (data.resolved && data.actual) {
        entries.push({
          taskType: data.taskType,
          predicted: {
            score: data.predictedScore,
            confidence: data.predictedConfidence,
          },
          actual: data.actual,
          delta: data.delta,
          timestamp: data.timestamp,
        });
      }
    } catch {
      // Skip malformed
    }
  }

  const accuracy = computePredictionAccuracy(entries);
  const bias =
    entries.length > 0
      ? entries.reduce((sum, e) => sum + e.delta, 0) / entries.length
      : 0;

  return {
    entries,
    accuracy,
    bias: Math.round(bias * 100) / 100,
    count: entries.length,
  };
}
