// Pure integer-cents -> English amount-in-words converter, for the AMOUNT IN
// WORDS field on a printed check (docs/superpowers/specs/2026-08-01-ap-design.md's
// check-printing section: "AMOUNT IN WORDS (integer-cents -> English words
// converter — pure, unit-tested, e.g. 'Twelve thousand three hundred
// forty-five and 67/100 dollars')"). No I/O, no dependency on server/src/db.js
// or any Fastify types — directly unit-testable (tests/server/amountWords.test.ts).
//
// Deliberately takes a bigint count of CENTS, never a decimal string or a
// parsed JS float (CLAUDE.md §2: money is never a raw JS number anywhere in
// the stack). server/src/checks.ts converts a NUMERIC(19,4) amount string
// from Postgres into cents via centsFromAmountString() below, using the same
// integer-arithmetic technique as bills.ts's toMicros/fromMicros — no money
// value passes through a JS float anywhere on this path.

const ONES = [
  "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
  "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
  "seventeen", "eighteen", "nineteen",
];

// Index 0/1 unused (0-9 and 10-19 are covered by ONES above); kept as ""
// placeholders so TENS[n] indexes directly by (value / 10).
const TENS = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];

// Short scale (US) — thousand, million, billion, etc. NUMERIC(19,4)'s
// integer part is capped at 15 digits everywhere else in this codebase
// (see bills.ts/invoices.ts's own comments), so "quadrillion" is already
// more headroom than any amount that can ever reach this function.
const SCALES = ["", "thousand", "million", "billion", "trillion", "quadrillion"];

/** Renders an integer in [0, 999] as English words, e.g. 345 -> "three
 *  hundred forty-five", 12 -> "twelve", 0 -> "" (the empty string — the
 *  caller is responsible for the "the whole amount is zero" case, since an
 *  empty three-digit GROUP within a larger number should contribute nothing,
 *  not the word "zero"). */
function threeDigitsToWords(n: number): string {
  const parts: string[] = [];
  const hundreds = Math.floor(n / 100);
  const rest = n % 100;
  if (hundreds > 0) parts.push(`${ONES[hundreds]} hundred`);
  if (rest > 0) {
    if (rest < 20) {
      parts.push(ONES[rest]!);
    } else {
      const tens = Math.floor(rest / 10);
      const ones = rest % 10;
      parts.push(ones > 0 ? `${TENS[tens]}-${ONES[ones]}` : TENS[tens]!);
    }
  }
  return parts.join(" ");
}

/** Renders a non-negative bigint whole-dollar amount as English words, e.g.
 *  1050n -> "one thousand fifty", 1000000n -> "one million", 0n -> "zero". */
function dollarsToWords(dollars: bigint): string {
  if (dollars === 0n) return "zero";

  // Split into base-1000 groups, least-significant first (345, 12 for 12345).
  const groups: number[] = [];
  let remaining = dollars;
  while (remaining > 0n) {
    groups.push(Number(remaining % 1000n)); // always 0-999: safe as a JS number
    remaining /= 1000n;
  }

  const words: string[] = [];
  for (let i = groups.length - 1; i >= 0; i--) {
    const value = groups[i]!;
    if (value === 0) continue; // an empty group (e.g. the "thousands" slot in 1,000,000) contributes nothing
    const groupWords = threeDigitsToWords(value);
    const scale = SCALES[i];
    words.push(scale ? `${groupWords} ${scale}` : groupWords);
  }
  return words.join(" ");
}

function capitalize(s: string): string {
  return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1);
}

/**
 * Converts a non-negative integer count of cents into the English
 * words-and-fraction form used on a printed check, e.g.
 *   105007n -> "One thousand fifty and 07/100 dollars"
 *   99n     -> "Zero and 99/100 dollars"
 */
export function amountToWords(cents: bigint): string {
  if (cents < 0n) {
    throw new Error("amountToWords: cents must be non-negative");
  }
  const dollars = cents / 100n;
  const remainderCents = cents % 100n;
  const dollarWords = capitalize(dollarsToWords(dollars));
  const centsStr = remainderCents.toString().padStart(2, "0");
  return `${dollarWords} and ${centsStr}/100 dollars`;
}

/**
 * Converts a NUMERIC(19,4) amount string as returned by Postgres (e.g.
 * "1234.5600") into a non-negative bigint count of cents, without ever
 * going through a JS float (CLAUDE.md §2) — mirrors bills.ts's
 * toMicros/fromMicros technique exactly. Every amount that reaches this
 * codebase is entered through a dollars-and-cents (2 decimal place) schema,
 * so any digits beyond the first two decimal places are always "00" in
 * practice; they are truncated (not rounded) here purely as a defensive
 * measure, never relied upon to change behavior.
 */
export function centsFromAmountString(amount: string): bigint {
  const negative = amount.startsWith("-");
  const unsigned = negative ? amount.slice(1) : amount;
  const [wholeRaw, fracRaw = ""] = unsigned.split(".");
  const centsDigits = (fracRaw + "00").slice(0, 2);
  const value = BigInt(wholeRaw || "0") * 100n + BigInt(centsDigits || "0");
  return negative ? -value : value;
}
