/**
 * generateRoiSlide.ts — local copy for forge0x2B-dashboard
 *
 * Generates a CFO-ready 6-slide ROI presentation from a PRISM ROI estimate.
 * Uses the DirectLlmProvider from forgesmith; falls back to deterministic
 * templates when the LLM is unavailable.
 *
 * NOTE: When forgesmith publishes a version that includes this generator,
 * this file can be removed and the import updated to "forgesmith".
 */

import type { LlmProvider } from "forgesmith";
import type { RoiEstimate, RoiSlide, RoiSlideOutput, RoiSlideType } from "./roiTypes";

// ─── Helpers ──────────────────────────────────────────────────────────────────

const CURRENCY_SYMBOL: Record<string, string> = {
  EUR: "€",
  USD: "$",
  GBP: "£",
};

function fmtK(n: number, sym: string): string {
  if (n >= 1_000_000) return `${sym}${(n / 1_000_000).toFixed(1)}M`;
  if (n >= 1_000) return `${sym}${Math.round(n / 1_000)}k`;
  return `${sym}${Math.round(n)}`;
}

// ─── Deterministic fallback ───────────────────────────────────────────────────

function buildFallback(
  roiEstimate: RoiEstimate,
  organizationName: string | undefined,
  currency: string
): RoiSlideOutput {
  const r = roiEstimate;
  const sym = CURRENCY_SYMBOL[currency] ?? "€";
  const org = organizationName ?? "Your Organization";
  const costYr = fmtK(r.maintenanceCostPerYear, sym);
  const saveYr = fmtK(r.savedCostPerYear, sym);
  const saveMo = fmtK(r.savedCostPerMonth, sym);

  const slides: RoiSlide[] = [
    {
      type: "title",
      title: "Technical Debt Cost Analysis",
      content: `${org} · Architecture Health Report · ${new Date().toLocaleDateString("en-GB", { month: "long", year: "numeric" })}`,
      highlight: r.currentGrade,
      highlightLabel: "Current Architecture Grade",
    },
    {
      type: "problem",
      title: "The Hidden Cost of Architecture Debt",
      content: `Your codebase currently has grade ${r.currentGrade} (${r.currentScore}/100). This translates directly to developer time lost to maintenance overhead every month.`,
      highlight: fmtK(r.maintenanceCostPerMonth, sym),
      highlightLabel: "Estimated monthly maintenance cost",
      bullets: [
        `Import cycles: ${r.breakdown.importCycles.count} cycles — ${r.breakdown.importCycles.hoursPerMonth}h/mo in debugging`,
        `Documentation drift: ${r.breakdown.documentationDrift.count} drifted capabilities — ${r.breakdown.documentationDrift.hoursPerMonth}h/mo`,
        `High-churn files: ${r.breakdown.highChurnFiles.count} hot files — ${r.breakdown.highChurnFiles.hoursPerMonth}h/mo in review overhead`,
        `Orphaned capabilities: ${r.breakdown.orphanedCode.count} untracked modules — ${r.breakdown.orphanedCode.hoursPerMonth}h/mo`,
      ],
    },
    {
      type: "cost-breakdown",
      title: "Where Developer Time Goes",
      content: `Total: ${r.estimatedMaintenanceHoursPerMonth}h/month in maintenance overhead = ${costYr}/year at current state`,
      bullets: [
        `Import cycles → ${fmtK(r.breakdown.importCycles.cost, sym)}/mo (${r.breakdown.importCycles.hoursPerMonth}h debugging)`,
        `Doc drift → ${fmtK(r.breakdown.documentationDrift.cost, sym)}/mo (${r.breakdown.documentationDrift.hoursPerMonth}h searching context)`,
        `Churn overhead → ${fmtK(r.breakdown.highChurnFiles.cost, sym)}/mo (${r.breakdown.highChurnFiles.hoursPerMonth}h in review)`,
        `Orphaned code → ${fmtK(r.breakdown.orphanedCode.cost, sym)}/mo (${r.breakdown.orphanedCode.hoursPerMonth}h ownership gaps)`,
      ],
    },
    {
      type: "scenarios",
      title: "What Reaching Grade B Saves",
      content: `Moving from ${r.currentGrade} to ${r.targetGrade} cuts maintenance overhead by ~${Math.round((r.savedCostPerMonth / Math.max(r.maintenanceCostPerMonth, 1)) * 100)}%.`,
      highlight: saveYr,
      highlightLabel: `Annual savings at grade ${r.targetGrade}`,
      bullets: [
        `Monthly savings: ${saveMo}/mo`,
        `ROI multiple: ${r.roiMultiple}× return on remediation investment`,
        `Break-even: ~${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}`,
        `Remediation effort: ~${r.estimatedRemediationDays} engineering days`,
      ],
    },
    {
      type: "recommendation",
      title: "Recommended Remediation Path",
      content: r.recommendation,
      bullets: [
        `Step 1: Resolve ${r.breakdown.importCycles.count} import cycles (est. ${Math.round(r.estimatedRemediationDays * 0.4)} days)`,
        `Step 2: Fix ${r.breakdown.documentationDrift.count} drifted capability docs (est. ${Math.round(r.estimatedRemediationDays * 0.3)} days)`,
        `Step 3: Assign ownership for ${r.breakdown.orphanedCode.count} orphaned capabilities (est. ${Math.round(r.estimatedRemediationDays * 0.2)} days)`,
        `Step 4: Add coverage to top churn files (est. ${Math.round(r.estimatedRemediationDays * 0.1)} days)`,
      ],
    },
    {
      type: "cta",
      title: `Approve ${new Date().toLocaleString("default", { month: "short" })} Refactoring Budget`,
      content: `Investment: ~${fmtK(r.estimatedRemediationDays * 8 * 150, sym)} (${r.estimatedRemediationDays} days of engineering effort)\nReturn: ${saveYr}/year in recovered developer capacity`,
      highlight: `${r.roiMultiple}×`,
      highlightLabel: "ROI multiple",
      bullets: [
        `${saveYr}/year recurring savings`,
        `Break-even in ${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}`,
        `Measurable via PRISM architecture grade (${r.currentGrade} → ${r.targetGrade})`,
      ],
    },
  ];

  const executiveSummary = [
    `• Current architecture grade ${r.currentGrade} (${r.currentScore}/100) costs ~${costYr}/year in maintenance overhead.`,
    `• Improving to grade ${r.targetGrade} saves ~${saveYr}/year — ${r.roiMultiple}× return on a ${r.estimatedRemediationDays}-day remediation effort.`,
    `• Break-even: ~${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}. Primary sources: ${r.breakdown.importCycles.count} import cycles, ${r.breakdown.documentationDrift.count} drifted docs, ${r.breakdown.orphanedCode.count} orphaned capabilities.`,
  ].join("\n");

  const oneLinePitch = `Moving from ${r.currentGrade} to ${r.targetGrade} saves ${saveYr} annually — ${r.roiMultiple}× ROI on a ${r.estimatedRemediationDays}-day investment.`;

  return {
    title: `Technical Debt ROI — ${org}`,
    slides,
    executiveSummary,
    oneLinePitch,
  };
}

// ─── LLM prompts ──────────────────────────────────────────────────────────────

function buildSystemPrompt(): string {
  return (
    "You are a VP of Engineering creating a concise, business-value-focused presentation for C-suite audiences. " +
    "Generate ONLY valid JSON — no markdown fences, no prose outside the JSON object."
  );
}

function buildUserPrompt(roiEstimate: RoiEstimate, organizationName: string | undefined, currency: string): string {
  const r = roiEstimate;
  const sym = CURRENCY_SYMBOL[currency] ?? "€";
  const org = organizationName ?? "the organization";

  return [
    `Generate a 6-slide executive ROI presentation for ${org}.`,
    ``,
    `## ROI Data`,
    `Current grade: ${r.currentGrade} (score ${r.currentScore}/100)`,
    `Target grade: ${r.targetGrade} (score ${r.targetScore}/100)`,
    `Monthly maintenance cost: ${sym}${r.maintenanceCostPerMonth.toLocaleString()}`,
    `Annual maintenance cost: ${sym}${r.maintenanceCostPerYear.toLocaleString()}`,
    `Monthly savings if improved: ${sym}${r.savedCostPerMonth.toLocaleString()}`,
    `Annual savings if improved: ${sym}${r.savedCostPerYear.toLocaleString()}`,
    `ROI multiple: ${r.roiMultiple}x`,
    `Break-even: ${r.breakEvenMonths} months`,
    `Remediation days: ${r.estimatedRemediationDays}`,
    ``,
    `## Pain sources`,
    `Import cycles: ${r.breakdown.importCycles.count} cycles, ${r.breakdown.importCycles.hoursPerMonth}h/mo, ${sym}${r.breakdown.importCycles.cost.toLocaleString()}/mo`,
    `Doc drift: ${r.breakdown.documentationDrift.count} drifted, ${r.breakdown.documentationDrift.hoursPerMonth}h/mo, ${sym}${r.breakdown.documentationDrift.cost.toLocaleString()}/mo`,
    `High churn: ${r.breakdown.highChurnFiles.count} files, ${r.breakdown.highChurnFiles.hoursPerMonth}h/mo, ${sym}${r.breakdown.highChurnFiles.cost.toLocaleString()}/mo`,
    `Orphaned: ${r.breakdown.orphanedCode.count} capabilities, ${r.breakdown.orphanedCode.hoursPerMonth}h/mo, ${sym}${r.breakdown.orphanedCode.cost.toLocaleString()}/mo`,
    ``,
    `## Output Format`,
    `Return a single JSON object:`,
    `{`,
    `  "title": "<deck title>",`,
    `  "slides": [`,
    `    { "type": "<title|problem|cost-breakdown|scenarios|recommendation|cta>", "title": "...", "content": "...", "highlight": "...", "highlightLabel": "...", "bullets": ["..."] }`,
    `  ],`,
    `  "executiveSummary": "<3-bullet summary separated by \\n>",`,
    `  "oneLinePitch": "<one sentence for email subject line>"`,
    `}`,
    `Rules: exactly 6 slides in order: title, problem, cost-breakdown, scenarios, recommendation, cta. Return ONLY valid JSON.`,
  ].join("\n");
}

function parseResponse(raw: string): RoiSlideOutput | null {
  let text = raw.trim();
  if (text.startsWith("```")) {
    text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
  }
  try {
    const parsed = JSON.parse(text) as {
      title?: unknown;
      slides?: unknown[];
      executiveSummary?: unknown;
      oneLinePitch?: unknown;
    };
    const rawSlides = Array.isArray(parsed.slides) ? parsed.slides : [];
    if (rawSlides.length === 0) return null;

    const VALID_TYPES: RoiSlideType[] = ["title", "problem", "cost-breakdown", "scenarios", "recommendation", "cta"];

    const slides: RoiSlide[] = rawSlides.map((s, i) => {
      const slide = (s ?? {}) as Record<string, unknown>;
      const type: RoiSlideType =
        typeof slide.type === "string" && VALID_TYPES.includes(slide.type as RoiSlideType)
          ? (slide.type as RoiSlideType)
          : VALID_TYPES[i] ?? "recommendation";
      const bullets = Array.isArray(slide.bullets)
        ? (slide.bullets as unknown[]).filter((b): b is string => typeof b === "string").slice(0, 4)
        : undefined;
      return {
        type,
        title: typeof slide.title === "string" ? slide.title : `Slide ${i + 1}`,
        content: typeof slide.content === "string" ? slide.content : "",
        ...(typeof slide.highlight === "string" && slide.highlight ? { highlight: slide.highlight } : {}),
        ...(typeof slide.highlightLabel === "string" && slide.highlightLabel ? { highlightLabel: slide.highlightLabel } : {}),
        ...(bullets && bullets.length > 0 ? { bullets } : {}),
      };
    });

    return {
      title: typeof parsed.title === "string" ? parsed.title : "Technical Debt ROI",
      slides,
      executiveSummary: typeof parsed.executiveSummary === "string" ? parsed.executiveSummary : "",
      oneLinePitch: typeof parsed.oneLinePitch === "string" ? parsed.oneLinePitch : "",
    };
  } catch {
    return null;
  }
}

// ─── Main export ──────────────────────────────────────────────────────────────

export interface GenerateRoiSlideOpts {
  roiEstimate: RoiEstimate;
  organizationName?: string;
  currency: "EUR" | "USD" | "GBP";
  llm: LlmProvider;
}

export async function generateRoiSlide(opts: GenerateRoiSlideOpts): Promise<RoiSlideOutput> {
  const { roiEstimate, organizationName, currency, llm } = opts;
  try {
    const response = await llm.complete({
      systemPrompt: buildSystemPrompt(),
      messages: [{ role: "user", content: buildUserPrompt(roiEstimate, organizationName, currency) }],
      maxTokens: 3000,
    });
    const parsed = parseResponse(response.content);
    if (parsed) return parsed;
  } catch {
    // Fall through to deterministic fallback
  }
  return buildFallback(roiEstimate, organizationName, currency);
}
