// app/api/generate/route.js — the generation pipeline.
// The client calls this once per stage so the progress UI is honest:
//   stage "analyze"  -> content map of the source
//   stage "generate" -> full assignment (with one automatic JSON-repair retry)
//   stage "verify"   -> per-question accuracy verdicts
//   stage "question" -> regenerate one question / add a new one
import { NextResponse } from "next/server";
import { getSettings } from "@/lib/store";
import { chat } from "@/lib/providers";
import { resolveGeneration } from "@/lib/model-caps";
import { extractJson } from "@/lib/json-utils";
import { analyzePrompt, generatePrompt, verifyPrompt, questionPrompt } from "@/lib/prompts";
import { normalizeAssignment, normalizeQuestion } from "@/lib/schema";

export const dynamic = "force-dynamic";
export const maxDuration = 600;

async function chatJson(settings, prompt, opts = {}) {
  const raw = await chat(settings, { ...prompt, expectJson: true, ...opts });
  try {
    return extractJson(raw);
  } catch (firstErr) {
    // One repair attempt: ask the same model to re-emit valid JSON.
    const fixed = await chat(settings, {
      system: "You convert text into strictly valid JSON. Output ONLY the corrected JSON with no commentary and no code fences.",
      user: "The following was supposed to be a single valid JSON object but is malformed. Re-emit it as strictly valid JSON, preserving all content:\n\n" + String(raw).slice(0, 60000),
      expectJson: true,
      temperature: 0,
    });
    try {
      return extractJson(fixed);
    } catch {
      throw firstErr;
    }
  }
}

export async function POST(request) {
  try {
    const body = await request.json();
    const { stage, source, config } = body;
    const settings = getSettings();
    // Auto mode sizes the source budget to the selected model's context window.
    const { maxSourceChars } = await resolveGeneration(settings);

    if (!source || String(source).trim().length < 100) {
      throw new Error("The source material is too short to build a quality assignment from (minimum ~100 characters).");
    }

    if (stage === "analyze") {
      const prompt = analyzePrompt({ source, config, maxSourceChars });
      const analysis = await chatJson(settings, prompt, { maxTokens: 2500 });
      return NextResponse.json({ analysis });
    }

    if (stage === "generate") {
      const prompt = generatePrompt({ source, analysis: body.analysis, config, maxSourceChars });
      const raw = await chatJson(settings, prompt);
      const assignment = normalizeAssignment(raw, config);
      if (!assignment.questions.length) {
        throw new Error("The model did not return any usable questions. Try again, or switch to a stronger model in Settings.");
      }
      return NextResponse.json({ assignment });
    }

    if (stage === "verify") {
      const questions = body.questions || [];
      if (!questions.length) throw new Error("No questions to verify.");
      const prompt = verifyPrompt({ source, questions, config, maxSourceChars });
      const raw = await chatJson(settings, prompt, { temperature: 0.1 });
      const results = Array.isArray(raw?.results) ? raw.results : [];
      const byId = {};
      for (const r of results) {
        if (!r || !r.id) continue;
        const verdict = r.verdict === "pass" ? "pass" : "warn";
        const note = [r.issue, r.suggestedFix ? "Suggested fix: " + r.suggestedFix : ""].filter(Boolean).join(" ").trim();
        byId[r.id] = { status: verdict, note: verdict === "pass" ? "" : (note || "The reviewer flagged this question — double-check it.") };
      }
      return NextResponse.json({ verifications: byId });
    }

    if (stage === "question") {
      const { type, note, existingQuestions, replacing } = body;
      const prompt = questionPrompt({ source, config, existingQuestions, type, note, replacing, maxSourceChars });
      const raw = await chatJson(settings, prompt, { maxTokens: 2500 });
      let candidate = raw;
      if (Array.isArray(raw)) candidate = raw[0];
      else if (Array.isArray(raw?.questions) && raw.questions.length) candidate = raw.questions[0];
      else if (raw?.question && typeof raw.question === "object") candidate = raw.question;
      const question = normalizeQuestion(candidate);
      if (!question) throw new Error("The model returned a question in an unexpected shape. Try again.");
      return NextResponse.json({ question });
    }

    throw new Error("Unknown stage: " + stage);
  } catch (e) {
    return NextResponse.json({ error: String(e.message || e) }, { status: 400 });
  }
}
