/**
 * Session Proposer — Claude Code acts as the optimization brain
 *
 * Instead of calling an external LLM API, this proposer reads pre-analyzed
 * mutation proposals from a JSON file. The proposals are generated by Claude
 * Code during the interactive session — reading real DeepTrace source code,
 * reasoning about improvements, and writing concrete file edits.
 *
 * Supports compound proposals: when a previous round promoted a candidate,
 * compound proposals stack new improvements on top of the promoted edits.
 * The proposer reads the cumulative tracker to apply base edits first,
 * then layers the new proposal's edits on top.
 *
 * Cost tracking: marked as "session-embedded" since the reasoning happens
 * within the Claude Code session, not via separate API calls.
 *
 * Contract: export a default function matching OptimizerProposeFn.
 */

import * as fs from "fs";
import * as path from "path";
import { fileURLToPath } from "url";

import type { OptimizerProposal } from "./optimizerRunner.js";
import type { BaselineSnapshot, CostEntry } from "./optimizerTypes.js";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface SessionProposal {
  candidateId: string;
  strategy: string;
  rationale: string;
  /** If true, apply base edits from cumulative tracker before this proposal's edits */
  compound?: boolean;
  /** File edits — relative paths + full new content */
  edits: Array<{
    filePath: string;
    newContent: string;
  }>;
  /** Estimated metric deltas (same convention as llm-proposer) */
  metricDeltas: {
    taskCompletionRate: number;
    timeToFirstDraftMs: number;
    humanEditDistance: number;
    wallClockMs: number;
    toolCallCount: number;
    factualPrecision: number;
    relationshipPrecision: number;
    evidenceLinkage: number;
    receiptCompleteness: number;
    falseConfidenceRate: number;
    canaryRelativeUplift: number;
  };
}

export interface CumulativeTracker {
  round: number;
  promotions: Array<{
    round: number;
    candidateId: string;
    throughputScore: number;
    strategy: string;
    timestamp: string;
  }>;
  /** File edits from the latest promoted candidate — used as base for compounds */
  baseEdits: Array<{
    filePath: string;
    newContent: string;
  }>;
  /** Strategies that have been tried and exhausted (no promotion after 2+ attempts) */
  exhaustedStrategies: string[];
  /** Throughput score after each round (for plateau detection) */
  throughputCurve: number[];
  /** Candidates that scored >3.5% improvement but weren't promoted (near-misses) */
  nearMisses: Array<{
    candidateId: string;
    throughputScore: number;
    improvementPct: number;
    strategy: string;
  }>;
}

// ---------------------------------------------------------------------------
// Constants & helpers
// ---------------------------------------------------------------------------

const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
const PROPOSALS_PATH = path.join(CURRENT_DIR, "session-proposals.json");
const TRACKER_PATH = path.join(CURRENT_DIR, "cumulative-tracker.json");

function loadProposals(): SessionProposal[] {
  if (!fs.existsSync(PROPOSALS_PATH)) {
    throw new Error(`Proposals file not found: ${PROPOSALS_PATH}\nGenerate it first by running the session analysis.`);
  }
  return JSON.parse(fs.readFileSync(PROPOSALS_PATH, "utf-8")) as SessionProposal[];
}

function loadTracker(): CumulativeTracker | null {
  if (!fs.existsSync(TRACKER_PATH)) return null;
  try {
    return JSON.parse(fs.readFileSync(TRACKER_PATH, "utf-8")) as CumulativeTracker;
  } catch {
    return null;
  }
}

function writeFileToWorktree(worktreePath: string, relPath: string, content: string): void {
  const fullPath = path.join(worktreePath, relPath);
  fs.mkdirSync(path.dirname(fullPath), { recursive: true });
  fs.writeFileSync(fullPath, content, "utf-8");
}

function clamp01(v: number): number {
  return Math.max(0, Math.min(1, v));
}

// ---------------------------------------------------------------------------
// Proposer
// ---------------------------------------------------------------------------

export default async function propose(
  worktreePath: string,
  baseline: BaselineSnapshot,
  iteration: number,
): Promise<OptimizerProposal | null> {
  const proposals = loadProposals();
  if (iteration >= proposals.length) return null;

  const p = proposals[iteration];
  const bt = baseline.throughputMetrics;
  const bq = baseline.qualityMetrics;
  const d = p.metricDeltas;

  // For compound proposals, first apply the base edits from previously promoted candidates
  if (p.compound) {
    const tracker = loadTracker();
    if (tracker?.baseEdits) {
      for (const edit of tracker.baseEdits) {
        writeFileToWorktree(worktreePath, edit.filePath, edit.newContent);
      }
    }
  }

  // Apply this proposal's edits on top
  for (const edit of p.edits) {
    writeFileToWorktree(worktreePath, edit.filePath, edit.newContent);
  }

  // Cost entry: session-embedded, no API call cost
  const costEntries: CostEntry[] = [{
    phase: "propose",
    model: "claude-opus-4-6-session",
    inputTokens: 0,
    outputTokens: 0,
    costUsd: 0,
    timestamp: new Date().toISOString(),
  }];

  return {
    candidateId: p.candidateId,
    throughput: {
      taskCompletionRate: Math.min(1.0, bt.taskCompletionRate + d.taskCompletionRate),
      timeToFirstDraftMs: Math.max(0, bt.timeToFirstDraftMs + d.timeToFirstDraftMs),
      humanEditDistance: Math.max(0, bt.humanEditDistance + d.humanEditDistance),
      wallClockMs: Math.max(0, bt.wallClockMs + d.wallClockMs),
      toolCallCount: Math.max(0, bt.toolCallCount + d.toolCallCount),
    },
    quality: {
      factualPrecision: clamp01(bq.factualPrecision + d.factualPrecision),
      relationshipPrecision: clamp01(bq.relationshipPrecision + d.relationshipPrecision),
      evidenceLinkage: clamp01(bq.evidenceLinkage + d.evidenceLinkage),
      receiptCompleteness: clamp01(bq.receiptCompleteness + d.receiptCompleteness),
      falseConfidenceRate: clamp01(bq.falseConfidenceRate + d.falseConfidenceRate),
      canaryRelativeUplift: bq.canaryRelativeUplift + d.canaryRelativeUplift,
    },
    rationale: `[${p.compound ? "COMPOUND" : "FRESH"}] ${p.rationale}`,
    costEntries,
  };
}
