import { todayStr } from '@/lib/utils';
import { asDollars, type Dollars } from '@/lib/money';
import type { TrackerRow, TrackerStatus } from '@/types';

// Re-export the shared domain types so existing `@/lib/trackerUtils` importers
// keep resolving them (the canonical definitions live in `@/types`).
export type { TrackerRow, TrackerStatus } from '@/types';

export const MONTHS = [
  'January','February','March','April','May','June',
  'July','August','September','October','November','December',
];
export const FILTER_ALL = 'all';
// Sentinel for the "no method" select option — empty string crashes Radix Select
export const METHOD_NONE = 'none';

export const TRACKER_SORT_DEFAULT = 'manual';
export const TRACKER_SORT_ASC = 'asc';
export const TRACKER_SORT_DESC = 'desc';

export type TrackerSortDir = typeof TRACKER_SORT_ASC | typeof TRACKER_SORT_DESC;

export const TRACKER_SORT_OPTIONS = [
  { key: TRACKER_SORT_DEFAULT, label: 'Custom order', defaultDir: TRACKER_SORT_ASC },
  { key: 'name', label: 'Bill name', defaultDir: TRACKER_SORT_ASC },
  { key: 'due', label: 'Due date', defaultDir: TRACKER_SORT_ASC },
  { key: 'expected', label: 'Expected amount', defaultDir: TRACKER_SORT_DESC },
  { key: 'previous', label: 'Last month paid', defaultDir: TRACKER_SORT_DESC },
  { key: 'paid', label: 'Paid amount', defaultDir: TRACKER_SORT_DESC },
  { key: 'remaining', label: 'Remaining amount', defaultDir: TRACKER_SORT_DESC },
  { key: 'paid_date', label: 'Paid date', defaultDir: TRACKER_SORT_DESC },
  { key: 'status', label: 'Status', defaultDir: TRACKER_SORT_ASC },
];

export const TRACKER_SORT_LABELS = Object.fromEntries(
  TRACKER_SORT_OPTIONS.map(option => [option.key, option.label])
);

export const TRACKER_SORT_DEFAULT_DIRS = Object.fromEntries(
  TRACKER_SORT_OPTIONS.map(option => [option.key, option.defaultDir])
);

export const ROW_STATUS_CLS: Record<string, string> = {
  paid:      'bg-emerald-500/[0.04] dark:bg-emerald-400/[0.02]',
  autodraft: 'bg-sky-500/[0.04] dark:bg-sky-400/[0.018]',
  upcoming:  '',
  due_soon:  'bg-amber-400/[0.07] dark:bg-amber-300/[0.016]',
  late:      'border-l-4 border-l-orange-400 bg-orange-500/[0.16] ring-1 ring-inset ring-orange-400/25 dark:bg-orange-400/[0.11] dark:ring-orange-300/25',
  missed:    'border-l-4 border-l-rose-400 bg-rose-500/[0.18] ring-1 ring-inset ring-rose-400/30 dark:bg-rose-400/[0.13] dark:ring-rose-300/30',
};

export const STATUS_META = {
  paid:      { label: 'Paid',      cls: 'bg-emerald-500/15 text-emerald-500 border border-emerald-500/30 dark:bg-emerald-300/10 dark:text-emerald-200 dark:border-emerald-300/30' },
  upcoming:  { label: 'Upcoming',  cls: 'bg-secondary text-muted-foreground border border-border' },
  due_soon:  { label: 'Due Soon',  cls: 'bg-amber-400/15 text-amber-500 border border-amber-400/30 dark:bg-amber-300/10 dark:text-amber-200 dark:border-amber-300/28' },
  late:      { label: 'Late',      cls: 'bg-orange-500/30 text-orange-800 border border-orange-500/60 shadow-sm shadow-orange-950/10 dark:bg-orange-400/25 dark:text-orange-100 dark:border-orange-300/60' },
  missed:    { label: 'Missed',    cls: 'bg-rose-500/30 text-rose-800 border border-rose-500/70 shadow-sm shadow-rose-950/10 dark:bg-rose-400/25 dark:text-rose-100 dark:border-rose-300/60' },
  autodraft: { label: 'Autodraft', cls: 'bg-sky-400/15 text-sky-500 border border-sky-400/30 dark:bg-sky-300/10 dark:text-sky-200 dark:border-sky-300/28' },
  skipped:   { label: 'Skipped',   cls: 'bg-muted text-muted-foreground border border-border' },
};

export function paymentDateForTrackerMonth(
  year: number,
  month: number,
  dueDay: number | string | null | undefined,
): string {
  const now = new Date();
  if (year === now.getFullYear() && month === now.getMonth() + 1) {
    return todayStr();
  }

  const daysInMonth = new Date(year, month, 0).getDate();
  const day = Number.isInteger(Number(dueDay))
    ? Math.min(Math.max(Number(dueDay), 1), daysInMonth)
    : 1;

  return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
}

export function amountSearchText(...values: unknown[]): string {
  return values
    .filter(value => value !== null && value !== undefined && Number.isFinite(Number(value)))
    .flatMap(value => {
      const num = Number(value);
      return [String(num), num.toFixed(2), `$${num.toFixed(2)}`];
    })
    .join(' ');
}

export function rowThreshold(row: TrackerRow): Dollars {
  return row.actual_amount != null ? row.actual_amount : row.expected_amount;
}

export function rowEffectiveStatus(row: TrackerRow): TrackerStatus {
  if (row.is_skipped) return 'skipped';
  const threshold = rowThreshold(row);
  const isPaidByThreshold = row.total_paid > 0 && row.total_paid >= threshold;
  return (isPaidByThreshold && row.status !== 'paid' && row.status !== 'autodraft') ? 'paid' : row.status;
}

// A bill's month is "settled" when its status is paid or autodraft. Mirrors the
// server's statusService.isPaidStatus — the single low-level check the various
// row/badge/calendar components share.
export function isPaidStatus(status: string): boolean {
  return status === 'paid' || status === 'autodraft';
}

export function rowIsPaid(row: TrackerRow): boolean {
  const status = rowEffectiveStatus(row);
  if (row.autopay_suggestion && status === 'autodraft') return false;
  return isPaidStatus(status);
}

export function rowIsDebt(row: TrackerRow): boolean {
  const category = String(row.category_name || '').toLowerCase();
  return Number(row.current_balance) > 0
    || row.minimum_payment != null
    || ['credit card', 'credit cards', 'loan', 'loans', 'debt', 'mortgage'].some(token => category.includes(token));
}

const STATUS_SORT_ORDER: Record<TrackerStatus, number> = {
  missed: 0,
  late: 1,
  due_soon: 2,
  upcoming: 3,
  autodraft: 4,
  paid: 5,
  skipped: 6,
};

function parseDateSortValue(value: string | null | undefined): number | null {
  if (!value) return null;
  const parsed = Date.parse(`${value}T00:00:00`);
  return Number.isFinite(parsed) ? parsed : null;
}

function numericSortValue(value: unknown): number {
  const number = Number(value);
  return Number.isFinite(number) ? number : 0;
}

type SortValue = string | number | null | undefined;

function trackerSortValue(row: TrackerRow, key: string): SortValue {
  switch (key) {
    case 'name':
      return String(row.name || '').toLowerCase();
    case 'due':
      return parseDateSortValue(row.due_date) ?? numericSortValue(row.due_day);
    case 'expected':
      return numericSortValue(rowThreshold(row));
    case 'previous':
      return numericSortValue(row.previous_month_paid);
    case 'paid':
      return numericSortValue(row.total_paid);
    case 'remaining':
      return paymentSummary(row, rowThreshold(row)).remaining;
    case 'paid_date':
      return parseDateSortValue(row.last_paid_date);
    case 'status':
      return STATUS_SORT_ORDER[rowEffectiveStatus(row)] ?? 99;
    default:
      return null;
  }
}

function compareSortValues(a: SortValue, b: SortValue, dir: string): number {
  const aMissing = a === null || a === undefined || a === '';
  const bMissing = b === null || b === undefined || b === '';
  if (aMissing && bMissing) return 0;
  if (aMissing) return 1;
  if (bMissing) return -1;

  let result = 0;
  if (typeof a === 'string' || typeof b === 'string') {
    result = String(a).localeCompare(String(b), undefined, { sensitivity: 'base', numeric: true });
  } else {
    result = a === b ? 0 : (a > b ? 1 : -1);
  }
  return dir === TRACKER_SORT_DESC ? -result : result;
}

export function normalizeTrackerSortKey(key: string): string {
  return TRACKER_SORT_LABELS[key] ? key : TRACKER_SORT_DEFAULT;
}

export function normalizeTrackerSortDir(dir: string): TrackerSortDir {
  return dir === TRACKER_SORT_DESC ? TRACKER_SORT_DESC : TRACKER_SORT_ASC;
}

export function sortTrackerRows(rows: TrackerRow[], sortKey: string, sortDir: string): TrackerRow[] {
  const key = normalizeTrackerSortKey(sortKey);
  if (key === TRACKER_SORT_DEFAULT) return rows;

  const dir = normalizeTrackerSortDir(sortDir);
  return rows
    .map((row, index) => ({ row, index }))
    .sort((a, b) => {
      const primary = compareSortValues(
        trackerSortValue(a.row, key),
        trackerSortValue(b.row, key),
        dir
      );
      if (primary !== 0) return primary;

      const due = compareSortValues(trackerSortValue(a.row, 'due'), trackerSortValue(b.row, 'due'), TRACKER_SORT_ASC);
      if (due !== 0) return due;

      const name = compareSortValues(trackerSortValue(a.row, 'name'), trackerSortValue(b.row, 'name'), TRACKER_SORT_ASC);
      if (name !== 0) return name;

      return a.index - b.index;
    })
    .map(item => item.row);
}

export function moveInArray<T>(items: T[], fromIndex: number, toIndex: number): T[] {
  const next = [...items];
  const [moved] = next.splice(fromIndex, 1);
  if (moved === undefined) return next;
  next.splice(toIndex, 0, moved);
  return next;
}

export function paymentSummary(row: TrackerRow, threshold: Dollars) {
  const target = Number(threshold) || 0;
  const paid = Number(row.total_paid) || 0;
  const paidTowardDue = Number.isFinite(Number(row.paid_toward_due))
    ? Number(row.paid_toward_due)
    : Math.min(paid, target);
  const overpaid = Number.isFinite(Number(row.overpaid_amount))
    ? Number(row.overpaid_amount)
    : Math.max(paid - target, 0);
  const remaining = Math.max(target - paidTowardDue, 0);
  const percent = target > 0 ? Math.min(100, Math.round((paidTowardDue / target) * 100)) : 0;
  // Re-brand the computed amounts as dollars (arithmetic on branded numbers
  // yields a plain number) so callers can pass them straight to fmt/formatUSD.
  return {
    target: asDollars(target),
    paid: asDollars(paid),
    paidTowardDue: asDollars(paidTowardDue),
    overpaid: asDollars(overpaid),
    remaining: asDollars(remaining),
    percent,
    count: Array.isArray(row.payments) ? row.payments.length : 0,
    partial: paid > 0 && remaining > 0,
  };
}
