import type { InvoiceState, PaymentStatus } from './shared';


// Display formatting for the Wallet panels.
//
// THE MILLISATOSHI RULE. A msat value arrives as a decimal string because 2.1e18 does not fit in a
// double. Every msat formatter below works on the digit string — no Number(), no parseInt, no
// BigInt round-trip through a float. Widening one to a number would silently corrupt large amounts,
// and the corruption would only show up on the one payment that mattered.

export type AmountUnit = 'sats' | 'btc';

const SATS_PER_BTC = 100_000_000;

/** Thousands separators on a bare digit string, left to right in groups of three from the end. */
function groupDigits(digits: string): string {
  return digits.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

/**
 * Split a non-negative digit string by 10^places without ever building a number.
 * `'1234', 3` → `{ whole: '1', frac: '234' }`.
 */
function splitByPowerOfTen(digits: string, places: number): { whole: string; frac: string } {
  const clean = digits.replace(/^0+(?=\d)/, '');
  if (places === 0) return { whole: clean, frac: '' };
  const padded = clean.padStart(places + 1, '0');
  return { whole: padded.slice(0, -places), frac: padded.slice(-places) };
}

const trimZeros = (frac: string) => frac.replace(/0+$/, '');

// ── satoshis (numbers) ────────────────────────────────────────────────────────────────────────────

/** Sats with thousands separators. Negative amounts keep their sign — a spend should read as one. */
export function formatSats(sats: number | null | undefined): string {
  if (sats == null || !Number.isFinite(sats)) return '—';
  const neg = sats < 0;
  return `${neg ? '-' : ''}${groupDigits(String(Math.abs(Math.trunc(sats))))}`;
}

/** BTC from sats, trailing zeros trimmed but never below two decimals, so amounts stay column-aligned. */
export function formatBtc(sats: number | null | undefined): string {
  if (sats == null || !Number.isFinite(sats)) return '—';
  const neg = sats < 0;
  const abs = Math.abs(Math.trunc(sats));
  const whole = Math.floor(abs / SATS_PER_BTC);
  const frac = String(abs % SATS_PER_BTC).padStart(8, '0');
  const shown = trimZeros(frac).padEnd(2, '0');
  return `${neg ? '-' : ''}${groupDigits(String(whole))}.${shown}`;
}

/** The one entry point the views call, so a unit toggle flips every amount on screen at once. */
export function formatAmount(sats: number | null | undefined, unit: AmountUnit): string {
  if (sats == null || !Number.isFinite(sats)) return '—';
  return unit === 'btc' ? `${formatBtc(sats)} BTC` : `${formatSats(sats)} sats`;
}

// ── millisatoshis (strings) ───────────────────────────────────────────────────────────────────────

/** True when the string is a decimal integer we can format; anything else is passed through verbatim. */
const isDecimal = (value: string) => /^\d+$/.test(value);

/**
 * A msat string as sats, keeping the sub-sat remainder when there is one. String arithmetic throughout —
 * see the rule at the top of this file.
 */
export function formatMsatAsSats(msat: string | null | undefined): string {
  if (msat == null) return '—';
  const neg = msat.startsWith('-');
  const digits = neg ? msat.slice(1) : msat;
  if (!isDecimal(digits)) return msat;
  const { whole, frac } = splitByPowerOfTen(digits, 3);
  const remainder = trimZeros(frac);
  return `${neg ? '-' : ''}${groupDigits(whole)}${remainder ? `.${remainder}` : ''}`;
}

/** A msat string as BTC — 10^11 msat to the bitcoin. */
export function formatMsatAsBtc(msat: string | null | undefined): string {
  if (msat == null) return '—';
  const neg = msat.startsWith('-');
  const digits = neg ? msat.slice(1) : msat;
  if (!isDecimal(digits)) return msat;
  const { whole, frac } = splitByPowerOfTen(digits, 11);
  const shown = trimZeros(frac).padEnd(2, '0');
  return `${neg ? '-' : ''}${groupDigits(whole)}.${shown}`;
}

export function formatMsat(msat: string | null | undefined, unit: AmountUnit): string {
  if (msat == null) return '—';
  return unit === 'btc' ? `${formatMsatAsBtc(msat)} BTC` : `${formatMsatAsSats(msat)} sats`;
}

/** Sats → msat, for request bodies. Multiplying a string by 1000 is three appended zeros. */
export function satsToMsat(sats: number): string {
  return `${Math.trunc(Math.abs(sats))}000`;
}

// ── everything else ───────────────────────────────────────────────────────────────────────────────

/** Unix seconds; 0 and null both mean "never" rather than 1970. */
export function formatTimestamp(unixSeconds: number | null | undefined): string {
  if (!unixSeconds || unixSeconds <= 0) return '—';
  const d = new Date(unixSeconds * 1000);
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

/** A countdown as mm:ss — the unlock window, where the seconds genuinely matter. */
export function formatCountdown(seconds: number | null | undefined): string {
  if (seconds == null || seconds <= 0) return '0:00';
  const m = Math.floor(seconds / 60);
  const s = Math.floor(seconds % 60);
  return `${m}:${String(s).padStart(2, '0')}`;
}

/** txids, addresses and pubkeys are unreadable in full and unmistakable at both ends. */
export function truncateMiddle(value: string | null | undefined, head = 10, tail = 8): string {
  if (!value) return '—';
  if (value.length <= head + tail + 1) return value;
  return `${value.slice(0, head)}…${value.slice(-tail)}`;
}

export function formatConfirmations(confirmations: number): string {
  if (confirmations <= 0) return 'unconfirmed';
  if (confirmations === 1) return '1 conf';
  if (confirmations >= 6) return '6+ confs';
  return `${confirmations} confs`;
}

export const INVOICE_TONES: Record<InvoiceState, string> = {
  open: 'bg-blue-500/10 text-blue-500',
  settled: 'bg-emerald-500/10 text-emerald-500',
  accepted: 'bg-amber-500/10 text-amber-500',
  canceled: 'bg-muted text-muted-foreground',
  expired: 'bg-muted text-muted-foreground',
};

export const PAYMENT_TONES: Record<PaymentStatus, string> = {
  pending: 'bg-amber-500/10 text-amber-500',
  succeeded: 'bg-emerald-500/10 text-emerald-500',
  failed: 'bg-destructive/10 text-destructive',
};

// `copyToClipboard` used to be re-exported from here, wrapping navigator.clipboard with a try/catch. The
// helper in `helpers/clipboard` now does that and more — it falls back to execCommand on an insecure
// origin, where navigator.clipboard does not exist at all — so callers import it from there directly.
//
// Removed rather than left as a pass-through: the wrapper had become `copyToClipboard` calling
// `copyToClipboard`, which is a stack overflow on any Wallet copy button. Every re-export is a chance for
// exactly that, and this file is about formatting.
