// Plain-English date formatting for calendar dates (Postgres `date`, no time
// component, no timezone). Deliberately NOT `new Date(iso).toLocaleDateString()`
// — that parses "YYYY-MM-DD" as UTC midnight and then renders it in the
// browser's local timezone, which can silently roll the date back a day west
// of UTC. Splitting the string ourselves keeps the date exactly what the
// database said, matching CLAUDE.md's "wrong books are worse than slow
// progress" spirit applied to dates, not just money.

const MONTHS = [
  "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];

const MONTHS_LONG = [
  "January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December",
];

/** "2026-07-16" -> "Jul 16, 2026" */
export function fmtDate(iso: string): string {
  const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
  if (!match) return iso;
  const [, y, m, d] = match;
  const monthName = MONTHS[Number(m) - 1] ?? m;
  return `${monthName} ${Number(d)}, ${y}`;
}

/** "2026-07-16" -> "July 16, 2026" — the full-month spelling used on the
 * formal print statements (StatementPrint, Task 4), where fmtDate's
 * abbreviated months would look out of place on a letterhead-style
 * document. */
export function fmtDateLong(iso: string): string {
  const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
  if (!match) return iso;
  const [, y, m, d] = match;
  const monthName = MONTHS_LONG[Number(m) - 1] ?? m;
  return `${monthName} ${Number(d)}, ${y}`;
}

/** "2026-07-16" -> "July 16" — same-year range endpoints on a print
 * statement's period line don't repeat the year twice ("For the period
 * January 1 – December 31, 2025", not "January 1, 2025 – December 31,
 * 2025"); this is the year-less half of that pair. */
export function fmtMonthDay(iso: string): string {
  const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
  if (!match) return iso;
  const [, , m, d] = match;
  const monthName = MONTHS_LONG[Number(m) - 1] ?? m;
  return `${monthName} ${Number(d)}`;
}

/** Today as "YYYY-MM-DD", built from the browser's local clock via plain
 * padding — not a money value, so ordinary Date/Number use is fine here
 * (CLAUDE.md §2 is about monetary values, not calendar dates). Used to
 * default the balance-sheet/trial-balance "as of" inputs to today. */
export function todayIso(): string {
  const now = new Date();
  const y = now.getFullYear();
  const m = String(now.getMonth() + 1).padStart(2, "0");
  const d = String(now.getDate()).padStart(2, "0");
  return `${y}-${m}-${d}`;
}

/** Renders an hours-count (e.g. a backup's ageHours from GET /api/backup/status)
 * as plain English — "3 hours", "2 days" — for the health panel and Settings
 * screen (Task 12). Not a money value, so ordinary Number/Math use is fine
 * (CLAUDE.md §2 is about monetary values, not durations). */
export function fmtHoursAgo(hours: number): string {
  if (hours < 1) return "less than an hour";
  if (hours < 48) {
    const n = Math.round(hours);
    return `${n} hour${n === 1 ? "" : "s"}`;
  }
  const days = Math.round(hours / 24);
  return `${days} day${days === 1 ? "" : "s"}`;
}

/** "2026-07-16" + 30 -> "2026-08-15" — adds a whole number of days to a
 * calendar date, purely in UTC (Date.UTC/getUTC* only) so the local
 * timezone never enters the computation (same hazard this file's header
 * warns about for parsing). Used only for the invoice form's *live preview*
 * of the due date (invoice date + customer's terms) before the user edits it
 * by hand — the server (server/src/invoices.ts's resolveDueDate, plain SQL
 * date arithmetic) remains the authority on the actually-stored due date. */
export function addDaysIso(iso: string, days: number): string {
  const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
  if (!match) return iso;
  const [, y, m, d] = match;
  const utcMs = Date.UTC(Number(y), Number(m) - 1, Number(d)) + days * 86_400_000;
  const dt = new Date(utcMs);
  const yy = dt.getUTCFullYear();
  const mm = String(dt.getUTCMonth() + 1).padStart(2, "0");
  const dd = String(dt.getUTCDate()).padStart(2, "0");
  return `${yy}-${mm}-${dd}`;
}

/** Calendar-quarter boundaries containing an ISO date — backs the sales tax
 * report's "This quarter" default and "Last quarter" preset (sales tax is
 * typically remitted quarterly). Same constructed-not-parsed reasoning as
 * yearRange below: a quarter label is a calendar fact, not money. */
export function quarterRange(iso: string): { from: string; to: string } {
  const match = /^(\d{4})-(\d{2})/.exec(iso);
  if (!match) return { from: iso, to: iso };
  const year = Number(match[1]);
  const q = Math.floor((Number(match[2]) - 1) / 3); // 0..3
  const starts = ["01-01", "04-01", "07-01", "10-01"];
  const ends = ["03-31", "06-30", "09-30", "12-31"];
  return { from: `${year}-${starts[q]}`, to: `${year}-${ends[q]}` };
}

/** The quarter before the one containing an ISO date. */
export function prevQuarterRange(iso: string): { from: string; to: string } {
  const match = /^(\d{4})-(\d{2})/.exec(iso);
  if (!match) return { from: iso, to: iso };
  const year = Number(match[1]);
  const q = Math.floor((Number(match[2]) - 1) / 3); // 0..3
  const prevYear = q === 0 ? year - 1 : year;
  const prevQ = q === 0 ? 3 : q - 1;
  const starts = ["01-01", "04-01", "07-01", "10-01"];
  const ends = ["03-31", "06-30", "09-30", "12-31"];
  return { from: `${prevYear}-${starts[prevQ]}`, to: `${prevYear}-${ends[prevQ]}` };
}

/** Calendar-year boundaries (Jan 1..Dec 31) for a given year, as ISO date
 * strings — backs the P&L report's "This year" / "Last year" quick presets.
 * A year number is a calendar label, not money, so plain string building is
 * fine (no Date parsing hazard either, since these are constructed, not
 * parsed from an existing ISO string). */
export function yearRange(year: number): { from: string; to: string } {
  return { from: `${year}-01-01`, to: `${year}-12-31` };
}
