import { computeBudgetStatus } from "@capakraken/engine";
import { fmtEur } from "../lib/format-utils.js";
import { buildProjectEstimateGraph } from "./computation-graph-project-estimate.js";
import { loadProjectGraphSnapshot, type ProjectGraphInput } from "./computation-graph-project-snapshot.js";
import { type GraphLink, type GraphNode, l, n } from "./computation-graph-shared.js";

export async function readProjectGraphSnapshot(
  ctx: Parameters<typeof loadProjectGraphSnapshot>[0],
  input: ProjectGraphInput,
) {
  const {
    project,
    latestVersion,
    effortRuleCount,
    experienceRuleCount,
    projectAllocations,
  } = await loadProjectGraphSnapshot(ctx, input);

  const nodes: GraphNode[] = [];
  const links: GraphLink[] = [];

  const hasBudget = project.budgetCents > 0;
  const hasDateRange = !!(project.startDate && project.endDate);
  nodes.push(
    n("input.budgetCents", "Project Budget", hasBudget ? fmtEur(project.budgetCents) : "Not set", hasBudget ? "EUR" : "—", "INPUT", hasBudget ? `Budget for ${project.name}` : `No budget defined for ${project.name}`, 0),
    n("input.winProbability", "Win Probability", `${project.winProbability}%`, "%", "INPUT", "Project win probability", 0),
    ...(hasDateRange ? [
      n("input.projectStart", "Project Start", project.startDate!.toISOString().slice(0, 10), "date", "INPUT", "Project start date", 0),
      n("input.projectEnd", "Project End", project.endDate!.toISOString().slice(0, 10), "date", "INPUT", "Project end date", 0),
    ] : []),
  );

  const estimateGraph = buildProjectEstimateGraph({
    project,
    latestVersion,
    effortRuleCount,
    experienceRuleCount,
  });
  nodes.push(...estimateGraph.nodes);
  links.push(...estimateGraph.links);

  if (projectAllocations.length > 0) {
    const budgetStatus = computeBudgetStatus(
      project.budgetCents,
      project.winProbability,
      projectAllocations.map((allocation) => ({
        status: allocation.status as unknown as string,
        dailyCostCents: allocation.dailyCostCents,
        startDate: allocation.startDate,
        endDate: allocation.endDate,
        hoursPerDay: allocation.hoursPerDay,
      })) as Parameters<typeof computeBudgetStatus>[2],
      project.startDate ?? new Date(),
      project.endDate ?? new Date(),
    );

    nodes.push(
      n("budget.confirmedCents", "Confirmed", fmtEur(budgetStatus.confirmedCents), "EUR", "BUDGET", "Confirmed allocation costs", 2, "Σ(CONFIRMED allocs)"),
      n("budget.proposedCents", "Proposed", fmtEur(budgetStatus.proposedCents), "EUR", "BUDGET", "Proposed allocation costs", 2, "Σ(PROPOSED allocs)"),
      n("budget.allocatedCents", "Allocated", fmtEur(budgetStatus.allocatedCents), "EUR", "BUDGET", "Total allocated", 2, "confirmed + proposed"),
      n("budget.remainingCents", "Remaining", hasBudget ? fmtEur(budgetStatus.remainingCents) : "N/A", hasBudget ? "EUR" : "—", "BUDGET", hasBudget ? "Remaining budget" : "Cannot compute — no budget set", 3, hasBudget ? "budget - allocated" : "needs budget"),
      n("budget.utilizationPct", "Utilization", hasBudget ? `${budgetStatus.utilizationPercent.toFixed(1)}%` : "N/A", hasBudget ? "%" : "—", "BUDGET", hasBudget ? "Budget utilization" : "Cannot compute — no budget set", 3, hasBudget ? "allocated / budget × 100" : "needs budget"),
      n("budget.weightedCents", "Win-Weighted", fmtEur(budgetStatus.winProbabilityWeightedCents), "EUR", "BUDGET", "Win-weighted cost", 3, "allocated × winProb / 100"),
      n("budget.allocCount", "Allocations", `${projectAllocations.length}`, "count", "BUDGET", `${projectAllocations.length} resource allocations on project`, 1),
    );

    links.push(
      l("budget.allocCount", "budget.confirmedCents", "Σ confirmed", 1),
      l("budget.allocCount", "budget.proposedCents", "Σ proposed", 1),
      l("budget.confirmedCents", "budget.allocatedCents", "+", 2),
      l("budget.proposedCents", "budget.allocatedCents", "+", 2),
      l("input.budgetCents", "budget.remainingCents", "−", 2),
      l("budget.allocatedCents", "budget.remainingCents", "−", 2),
      l("budget.allocatedCents", "budget.utilizationPct", "÷ budget × 100", 2),
      l("input.budgetCents", "budget.utilizationPct", "÷", 1),
      l("budget.allocatedCents", "budget.weightedCents", "× winProb / 100", 1),
      l("input.winProbability", "budget.weightedCents", "×", 1),
    );

    if (estimateGraph.estimatedCostCents != null) {
      const estimatedCost = estimateGraph.estimatedCostCents;
      const gapCents = budgetStatus.allocatedCents - estimatedCost;
      nodes.push(
        n("budget.estVsActualGap", "Est. vs Actual", fmtEur(Math.abs(gapCents)), "EUR", "BUDGET",
          gapCents > 0
            ? `Actual allocations exceed estimate by ${fmtEur(gapCents)}`
            : gapCents < 0
              ? `Actual allocations under estimate by ${fmtEur(Math.abs(gapCents))}`
              : "Actual allocations match estimate",
          3, "allocated - estCost"),
      );
      links.push(
        l("budget.allocatedCents", "budget.estVsActualGap", "−", 1),
        l("est.totalCostCents", "budget.estVsActualGap", "−", 1),
      );
    }
  }

  return {
    nodes,
    links,
    meta: {
      projectName: project.name,
      projectCode: project.shortCode,
    },
  };
}
