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

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

/** Aggregate of the 6 comparable figures across a set of leaf rows. */
export interface Totals {
  monthCurrent: number;
  monthPrevious: number;
  monthDelta: number;
  ytdCurrent: number;
  ytdPrevious: number;
  ytdDelta: number;
}

const ZERO: Totals = {
  monthCurrent: 0,
  monthPrevious: 0,
  monthDelta: 0,
  ytdCurrent: 0,
  ytdPrevious: 0,
  ytdDelta: 0,
};

/** Sum every non-subtotal (leaf) row into a single Totals. */
export function sumLeaves(rows: CategoryDelta[]): Totals {
  return rows
    .filter((r) => !r.is_parent)
    .reduce<Totals>(
      (acc, r) => ({
        monthCurrent: acc.monthCurrent + r.currentAmount,
        monthPrevious: acc.monthPrevious + r.previousAmount,
        monthDelta: acc.monthDelta + r.deltaAbs,
        ytdCurrent: acc.ytdCurrent + r.cumulativeCurrentAmount,
        ytdPrevious: acc.ytdPrevious + r.cumulativePreviousAmount,
        ytdDelta: acc.ytdDelta + r.cumulativeDeltaAbs,
      }),
      { ...ZERO },
    );
}

export function pct(delta: number, previous: number): number | null {
  return previous !== 0 ? (delta / Math.abs(previous)) * 100 : null;
}

/** Component-wise a + sign*b across the 6 figures. */
function combine(a: Totals, b: Totals, sign: 1 | -1): Totals {
  return {
    monthCurrent: a.monthCurrent + sign * b.monthCurrent,
    monthPrevious: a.monthPrevious + sign * b.monthPrevious,
    monthDelta: a.monthDelta + sign * b.monthDelta,
    ytdCurrent: a.ytdCurrent + sign * b.ytdCurrent,
    ytdPrevious: a.ytdPrevious + sign * b.ytdPrevious,
    ytdDelta: a.ytdDelta + sign * b.ytdDelta,
  };
}

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

/**
 * Income-statement roll-up for the real-vs-real compare (Issue #253).
 *
 * Sign convention flowing in from COMPARE_DELTA_SQL: expenses are positive
 * magnitudes (ABS of outflows), income is a signed SUM (positive), transfers a
 * signed net (~0 when balanced). So the operating result is a straight
 * `income − expense`, and the net adds the (usually ~0) transfer total. When a
 * transfer is unbalanced (a single leg categorised) its residual shows up as
 * the gap between resultBefore and resultNet.
 */
export function computeResults(rows: CategoryDelta[]): CompareResults {
  const byType = (type: SectionType): Totals =>
    sumLeaves(rows.filter((r) => (r.category_type ?? "expense") === type));
  const income = byType("income");
  const expense = byType("expense");
  const transfer = byType("transfer");
  const resultBefore = combine(income, expense, -1);
  const resultNet = combine(resultBefore, transfer, 1);
  const hasTransfers = rows.some(
    (r) => (r.category_type ?? "expense") === "transfer" && !r.is_parent,
  );
  return { income, expense, transfer, resultBefore, resultNet, hasTransfers };
}
