import type { BudgetVsActualRow } from "../../shared/types";

export type SectionType = "expense" | "income" | "transfer";

/** Aggregate of the 6 budget-vs-actual figures across a set of leaf rows. */
export interface Totals {
  monthActual: number;
  monthBudget: number;
  monthVariation: number;
  ytdActual: number;
  ytdBudget: number;
  ytdVariation: number;
}

const ZERO: Totals = {
  monthActual: 0,
  monthBudget: 0,
  monthVariation: 0,
  ytdActual: 0,
  ytdBudget: 0,
  ytdVariation: 0,
};

/** Sum every non-subtotal (leaf) row into a single Totals. */
export function sumLeaves(rows: BudgetVsActualRow[]): Totals {
  return rows
    .filter((r) => !r.is_parent)
    .reduce<Totals>(
      (acc, r) => ({
        monthActual: acc.monthActual + r.monthActual,
        monthBudget: acc.monthBudget + r.monthBudget,
        monthVariation: acc.monthVariation + r.monthVariation,
        ytdActual: acc.ytdActual + r.ytdActual,
        ytdBudget: acc.ytdBudget + r.ytdBudget,
        ytdVariation: acc.ytdVariation + r.ytdVariation,
      }),
      { ...ZERO },
    );
}

/** Variation as a fraction of budget (null when budget is 0 — mirrors the
 * row-level `*VariationPct` fields computed in `getBudgetVsActualData`). */
export function pct(variation: number, budget: number): number | null {
  return budget !== 0 ? variation / Math.abs(budget) : null;
}

/** Component-wise a + b across the 6 figures. */
function add(a: Totals, b: Totals): Totals {
  return {
    monthActual: a.monthActual + b.monthActual,
    monthBudget: a.monthBudget + b.monthBudget,
    monthVariation: a.monthVariation + b.monthVariation,
    ytdActual: a.ytdActual + b.ytdActual,
    ytdBudget: a.ytdBudget + b.ytdBudget,
    ytdVariation: a.ytdVariation + b.ytdVariation,
  };
}

export interface BudgetVsActualResults {
  income: Totals;
  expense: Totals;
  transfer: Totals;
  /** Income + expense: the operating result, before transfers. */
  resultBefore: Totals;
  /** resultBefore + transfers: the bottom-line total. */
  resultNet: Totals;
  hasTransfers: boolean;
}

/**
 * Income-statement roll-up for the budget-vs-actual report (Issue #277).
 *
 * Mirrors `compareResults.computeResults`, with one key sign difference: BVA
 * amounts already arrive signed the "accounting" way rather than ABS'd to
 * positive magnitudes. `getBudgetVsActualData` computes `monthActual`/
 * `ytdActual` as the raw (unsigned-by-us) `SUM(transactions.amount)` — negative
 * for expense categories, since expense transactions are stored as negative
 * amounts throughout this app (see reportService/dashboardService `amount < 0`
 * conventions) — and negates the user-entered (positive) budget figure to
 * match via `signFor`. So expense totals here are already ≤ 0, and the
 * operating result is a straight ADD of income + expense (not a subtraction
 * like the real-vs-real compare, whose expense figures are ABS'd positive
 * magnitudes). The net just adds the transfer total on top.
 */
export function computeResults(rows: BudgetVsActualRow[]): BudgetVsActualResults {
  const byType = (type: SectionType): Totals =>
    sumLeaves(rows.filter((r) => r.category_type === type));
  const income = byType("income");
  const expense = byType("expense");
  const transfer = byType("transfer");
  const resultBefore = add(income, expense);
  const resultNet = add(resultBefore, transfer);
  const hasTransfers = rows.some((r) => r.category_type === "transfer" && !r.is_parent);
  return { income, expense, transfer, resultBefore, resultNet, hasTransfers };
}
