// Currency formatting for the client, mirroring the server's utils/money.js.
//
// The API sends money in two units: bill / summary values are serialized as
// DOLLARS (the server calls fromCents before responding), while raw bank
// transaction amounts arrive as integer CENTS. So there are two entry points —
// formatUSD(dollars) and formatCentsUSD(cents) — matching the server's
// formatUSD / formatCentsUSD split. USD / en-US throughout. Inputs are coerced
// defensively so null, '', undefined, or NaN never render as "$NaN".
//
// The two units are *branded* below: a Dollars value can't be passed where Cents
// is expected (or vice-versa) without an explicit conversion, so the cents↔dollars
// mixups that have caused real money bugs (e.g. displaying cents as dollars →
// 100× wrong) become compile errors in typed code.

/** Integer cents, e.g. a raw bank transaction amount (1234 = $12.34). */
export type Cents = number & { readonly __unit: 'cents' };
/** Dollars, e.g. an API-serialized bill amount (12.34 = $12.34). */
export type Dollars = number & { readonly __unit: 'dollars' };

/** Brand a raw number as cents (the sanctioned way to enter the typed world). */
export const asCents = (n: number): Cents => n as Cents;
/** Brand a raw number as dollars. */
export const asDollars = (n: number): Dollars => n as Dollars;
/** Convert cents → dollars (the only way to cross the unit boundary). */
export const centsToDollars = (c: Cents): Dollars => (c / 100) as Dollars;
/** Convert dollars → cents, rounding to the nearest cent. */
export const dollarsToCents = (d: Dollars): Cents => Math.round(d * 100) as Cents;

// A money value as it may still arrive untyped from a form field or legacy code:
// the branded unit, or a numeric string, or blank. Note bare `number` is NOT
// included — that's deliberate, so typed callers must brand (asDollars/asCents)
// and can't accidentally hand cents to a dollars formatter.
type DollarsInput = Dollars | string | null | undefined;
type CentsInput = Cents | string | null | undefined;

const DASH = '—';

// ── Active display currency (a user preference; display-only, no conversion) ──
// Read synchronously from localStorage at module load so the very first paint is
// already in the right currency (the settings query resolves later; FormatSync
// reconciles server→local). Amounts are entered/stored in a single currency —
// this only changes the symbol/locale used to render them.
const CURRENCY_LOCALES: Record<string, string> = {
  USD: 'en-US', EUR: 'en-IE', GBP: 'en-GB', CAD: 'en-CA',
  AUD: 'en-AU', JPY: 'ja-JP', INR: 'en-IN', CHF: 'de-CH',
};
const CURRENCY_STORAGE_KEY = 'bt-currency';
export const SUPPORTED_CURRENCIES = Object.keys(CURRENCY_LOCALES);

function loadCurrency(): string {
  try {
    const c = localStorage.getItem(CURRENCY_STORAGE_KEY);
    if (c && CURRENCY_LOCALES[c]) return c;
  } catch { /* localStorage unavailable */ }
  return 'USD';
}

let activeCurrency = loadCurrency();
let activeLocale = CURRENCY_LOCALES[activeCurrency] ?? 'en-US';

/** Set the active display currency + persist it (called from Settings + FormatSync). */
export function setMoneyFormat(currency: string): void {
  if (!CURRENCY_LOCALES[currency]) return;
  activeCurrency = currency;
  activeLocale = CURRENCY_LOCALES[currency]!;
  try {
    localStorage.setItem(CURRENCY_STORAGE_KEY, currency);
  } catch { /* localStorage unavailable */ }
}

export function getActiveCurrency(): string {
  return activeCurrency;
}

function toNumber(value: unknown): number {
  const n = Number(value);
  if (!Number.isFinite(n)) return 0;
  return n === 0 ? 0 : n; // normalize -0 → +0 so it never renders as "-$0.00"
}

function isBlank(value: unknown): value is null | undefined | '' {
  return value === null || value === undefined || value === '';
}

/**
 * Format a DOLLAR amount → "$1,234.56".
 * `whole` drops the cents ("$1,235"); `dash` renders blank input as "—".
 */
export function formatUSD(
  dollars: DollarsInput,
  { whole = false, dash = false }: { whole?: boolean; dash?: boolean } = {},
): string {
  if (dash && isBlank(dollars)) return DASH;
  return toNumber(dollars).toLocaleString(activeLocale, {
    style: 'currency',
    currency: activeCurrency,
    minimumFractionDigits: whole ? 0 : 2,
    maximumFractionDigits: whole ? 0 : 2,
  });
}

/** Whole-dollar convenience → "$1,235". */
export function formatUSDWhole(
  dollars: DollarsInput,
  opts: { dash?: boolean } = {},
): string {
  return formatUSD(dollars, { ...opts, whole: true });
}

/**
 * Format an integer-CENTS amount (e.g. a bank transaction) → "$12.34".
 * `signed` prefixes "+"/"-" (income vs expense); `dash` renders blank → "—";
 * `currency` is an ISO code (defaults USD).
 */
export function formatCentsUSD(
  cents: CentsInput,
  { signed = false, dash = false, currency }: { signed?: boolean; dash?: boolean; currency?: string } = {},
): string {
  if (dash && isBlank(cents)) return DASH;
  const c = toNumber(cents);
  const body = (Math.abs(c) / 100).toLocaleString(activeLocale, {
    style: 'currency',
    currency: currency || activeCurrency,
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });
  if (signed) return (c < 0 ? '-' : '+') + body;
  return (c < 0 ? '-' : '') + body;
}

/**
 * Shared form validator for a non-negative money field (dollars). Blank is
 * allowed (returns ''); otherwise the value must parse to a number ≥ 0. Returns
 * '' when valid, or an error string labelled for the field. Zero is allowed —
 * these are non-negative, not strictly-positive, amounts.
 */
export function validateNonNegativeMoney(
  val: string | number | null | undefined,
  label = 'Amount',
): string {
  if (val === '' || val === null || val === undefined) return '';
  const num = parseFloat(String(val));
  if (isNaN(num) || num < 0) return `${label} must be a non-negative number`;
  return '';
}
