/**
 * Locale-aware formatters for Multideal.
 *
 * All functions accept a `Locale` and delegate to the Intl API - no raw
 * format strings live here, only token-level decisions.
 */

import type { Locale } from './i18n/index';
import { formatDateLocale, nowInIsrael, toIsraelDateString, TZ } from './datetime';
import { interpolate } from './i18n/interpolate';

// ─── Currency ────────────────────────────────────────────────────────────────

const currencyFormatters: Record<Locale, Intl.NumberFormat> = {
  he: new Intl.NumberFormat('he-IL', {
    style: 'currency',
    currency: 'ILS',
    maximumFractionDigits: 0,
  }),
  en: new Intl.NumberFormat('he-IL', {
    style: 'currency',
    currency: 'ILS',
    maximumFractionDigits: 0,
  }),
};

/**
 * Format an ILS amount: ₪45.
 * Both locales render in he-IL format (₪ prefix) per design spec.
 *
 * Null/undefined/NaN are coerced to 0 — renders as ₪0 rather than ₪NaN.
 */
export function formatCurrency(amount: number | null | undefined, locale: Locale = 'he'): string {
  const safe = amount == null || !Number.isFinite(amount) ? 0 : amount;
  return currencyFormatters[locale].format(safe);
}

export function formatNumber(amount: number | null | undefined, locale: Locale = 'he'): string {
  return new Intl.NumberFormat(locale === 'he' ? 'he-IL' : 'en-US').format(amount ?? 0);
}

// ─── Date / Time ──────────────────────────────────────────────────────────────

function toDate(date: Date | string): Date {
  return typeof date === 'string' ? new Date(date) : date;
}

/**
 * Short date: dd.mm.yyyy (he) or MMM d, yyyy (en).
 * Uses Asia/Jerusalem timezone so server (UTC) and client (IL) agree on the date.
 */
export function formatDate(date: Date | string, locale: Locale): string {
  const d = toDate(date);
  if (locale === 'he') {
    const parts = new Intl.DateTimeFormat('he-IL', {
      day: '2-digit',
      month: '2-digit',
      year: 'numeric',
      timeZone: TZ,
    }).formatToParts(d);
    const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '';
    return `${get('day')}.${get('month')}.${get('year')}`;
  }
  return new Intl.DateTimeFormat('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric',
    timeZone: TZ,
  }).format(d);
}

/**
 * Compact chart axis label: d.M (he) or M/d (en), no year.
 */
export function formatDateShort(date: Date | string, locale: Locale): string {
  const d = toDate(date);
  if (locale === 'he') {
    const parts = new Intl.DateTimeFormat('he-IL', {
      day: 'numeric',
      month: 'numeric',
      timeZone: TZ,
    }).formatToParts(d);
    const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '';
    return `${get('day')}.${get('month')}`;
  }
  return new Intl.DateTimeFormat('en-US', {
    month: 'numeric',
    day: 'numeric',
    timeZone: TZ,
  }).format(d);
}

/**
 * Date + time combined.
 */
export function formatDateTime(date: Date | string, locale: Locale): string {
  const d = toDate(date);
  return `${formatDate(d, locale)} ${formatTime(d, locale)}`;
}

/**
 * HH:mm (24-hour, both locales).
 * Uses Asia/Jerusalem timezone so server (UTC) and client (IL) agree on the time.
 */
export function formatTime(date: Date | string, locale: Locale): string {
  const d = toDate(date);
  const parts = new Intl.DateTimeFormat('he-IL', {
    hour: '2-digit',
    minute: '2-digit',
    hour12: false,
    timeZone: TZ,
  }).formatToParts(d);
  const get = (type: string) => parts.find((p) => p.type === type)?.value ?? '';
  // locale kept as parameter for future 12h support
  void locale;
  return `${get('hour')}:${get('minute')}`;
}

// ─── Relative time ────────────────────────────────────────────────────────────

const MINUTE = 60_000;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;

const rtfFormatters: Record<Locale, Intl.RelativeTimeFormat> = {
  he: new Intl.RelativeTimeFormat('he', { numeric: 'auto' }),
  en: new Intl.RelativeTimeFormat('en', { numeric: 'auto' }),
};

/**
 * Human-readable relative time: "לפני 5 דקות" / "5 minutes ago".
 */
export function formatRelative(date: Date | string, locale: Locale): string {
  const d = toDate(date);
  const diffMs = d.getTime() - Date.now();
  const absDiff = Math.abs(diffMs);
  const rtf = rtfFormatters[locale];

  if (absDiff < MINUTE) {
    return rtf.format(Math.round(diffMs / 1000), 'second');
  }
  if (absDiff < HOUR) {
    return rtf.format(Math.round(diffMs / MINUTE), 'minute');
  }
  if (absDiff < DAY) {
    return rtf.format(Math.round(diffMs / HOUR), 'hour');
  }
  return rtf.format(Math.round(diffMs / DAY), 'day');
}

/**
 * Feed timestamp: clock time for today, relative for this week, date+time for older.
 */
export function formatFeedTimestamp(date: Date | string, locale: Locale): string {
  const d = toDate(date);
  const todayStr = toIsraelDateString(nowInIsrael());
  const eventStr = toIsraelDateString(d);
  if (eventStr === todayStr) return formatTime(d, locale);
  const absDiff = Math.abs(d.getTime() - Date.now());
  if (absDiff < 7 * DAY) return formatRelative(d, locale);
  return formatDateTime(d, locale);
}

export interface DealWindowEndLabels {
  endsAtTime: string;
  endsOnDateTime: string;
  endsInDays: string;
}

/**
 * Human-readable deal window end: relative when far out, date+time when not today.
 */
export function formatDealWindowEnd(
  windowEnd: Date | string,
  locale: Locale,
  labels: DealWindowEndLabels,
): string {
  const d = toDate(windowEnd);
  const msRemaining = d.getTime() - Date.now();
  if (msRemaining > ONE_DAY) {
    const days = Math.ceil(msRemaining / ONE_DAY);
    return interpolate(labels.endsInDays, { n: days });
  }
  const time = formatTime(d, locale);
  const todayStr = toIsraelDateString(nowInIsrael());
  const endStr = toIsraelDateString(d);
  if (endStr === todayStr) {
    return interpolate(labels.endsAtTime, { time });
  }
  const date = formatDateLocale(d, locale, { day: 'numeric', month: 'short' });
  return interpolate(labels.endsOnDateTime, { date, time });
}

// ─── Countdown / Remaining ────────────────────────────────────────────────────

export interface RemainingResult {
  /** Formatted label: HH:mm:ss when under 24 h, otherwise "Xd Yh". */
  label: string;
  /** Visual urgency stage per FDS §4.2. */
  stage: 'neutral' | 'warm' | 'urgent';
}

const TEN_MIN = 10 * 60 * 1000;
const ONE_HOUR = 60 * 60 * 1000;
const ONE_DAY = 24 * ONE_HOUR;

/**
 * Compute countdown label + urgency stage from milliseconds remaining.
 * Used by CountdownTimer (domain) via this shared formatter.
 */
export function formatRemaining(ms: number): RemainingResult {
  const clamped = Math.max(0, ms);

  let stage: RemainingResult['stage'];
  if (clamped < TEN_MIN) {
    stage = 'urgent';
  } else if (clamped < ONE_HOUR) {
    stage = 'warm';
  } else {
    stage = 'neutral';
  }

  let label: string;
  if (clamped < ONE_HOUR) {
    // ≤ 1 hour: show mm:ss (seconds visible for urgency)
    const totalSeconds = Math.floor(clamped / 1000);
    const m = Math.floor(totalSeconds / 60);
    const s = totalSeconds % 60;
    label = [m, s].map((n) => String(n).padStart(2, '0')).join(':');
  } else if (clamped < ONE_DAY) {
    // > 1 hour but < 1 day: show HH:mm (no seconds - avoids visual noise)
    const totalSeconds = Math.floor(clamped / 1000);
    const h = Math.floor(totalSeconds / 3600);
    const m = Math.floor((totalSeconds % 3600) / 60);
    label = [h, m].map((n) => String(n).padStart(2, '0')).join(':');
  } else {
    const days = Math.floor(clamped / ONE_DAY);
    const hours = Math.floor((clamped % ONE_DAY) / ONE_HOUR);
    label = `${days}d ${hours}h`;
  }

  return { label, stage };
}

// ─── Duration ────────────────────────────────────────────────────────────────

/**
 * Format a millisecond duration to a human-readable string.
 * Used by admin support panels to display SLA/response times.
 *
 * Examples: 45000 → "45s", 90000 → "1m 30s", 7200000 → "2h", 90000000 → "1h 30m"
 */
export function fmtDuration(ms: number): string {
  if (ms <= 0) return '—';
  if (ms < 60_000) return `${Math.round(ms / 1000)}s`;
  if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;
  const h = Math.floor(ms / 3_600_000);
  const m = Math.round((ms % 3_600_000) / 60_000);
  return m > 0 ? `${h}h ${m}m` : `${h}h`;
}

// ─── Price / Discount ─────────────────────────────────────────────────────────

export interface PriceDiscountResult {
  /** Formatted original price label, e.g. "₪90". */
  originalLabel: string;
  /** Formatted discounted price label, e.g. "₪45". */
  discountedLabel: string;
  /**
   * Discount percent (0-100, rounded). Useful for a11y labels even though
   * no % badge is rendered per FDS (no % badge rule).
   */
  percent: number;
}

// ─── Image alt text ───────────────────────────────────────────────────────────

/**
 * Build a localised alt text for a deal/gallery image.
 *
 * @param title  - Deal title
 * @param index  - 0-based image index
 * @param imageLabel - Localised word for "image" (from `t('image_of_deal')` in the `image` namespace)
 * @returns e.g. "חמבורגר ענק - תמונה 1" / "Giant Burger - image 1"
 */
export function formatImageAlt(title: string, index: number, imageLabel: string): string {
  return `${title} - ${imageLabel} ${index + 1}`;
}

/**
 * Format a byte count as a human-readable string (B / KB / MB).
 * Returns '—' for missing or invalid values.
 */
export function formatBytes(bytes: number | undefined | null): string {
  if (bytes == null || bytes < 0) return '—';
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

/**
 * Compute formatted price pair + percent discount.
 */
export function formatPriceDiscount(
  original: number,
  discounted: number,
  locale: Locale = 'he',
): PriceDiscountResult {
  const percent = original > 0 ? Math.round(((original - discounted) / original) * 100) : 0;
  return {
    originalLabel: formatCurrency(original, locale),
    discountedLabel: formatCurrency(discounted, locale),
    percent,
  };
}

/**
 * Masked phone display from the last-3-digit hint. hint '555' -> '05*-***-*555'.
 * Returns null when no hint is available. Locale-independent. NOT for public surfaces —
 * only admin tables and the logged-in user's own navbar/profile badge (PII policy).
 */
export function maskPhoneFromHint(hint: string | null | undefined): string | null {
  if (!hint) return null;
  return `05*-***-*${hint}`;
}

/**
 * Long date+time for email templates.
 * Format: "9 ביוני 2026, 14:30" (he) / "June 9, 2026, 2:30 PM" (en)
 */
export function formatEmailDateTime(iso: string | Date, locale: Locale): string {
  const date = typeof iso === 'string' ? new Date(iso) : iso;
  return date.toLocaleString(locale === 'he' ? 'he-IL' : 'en-IL', {
    dateStyle: 'long',
    timeStyle: 'short',
    timeZone: TZ,
  });
}

/**
 * Canonical payment method display: "VISA •••• 4242"
 * Uppercase brand, 4 bullet dots (•), space-separated.
 */
export function formatPaymentMethodLabel(brand: string, last4: string): string {
  return `${brand.toUpperCase()} •••• ${last4}`;
}

/**
 * Format an integer with locale-appropriate digit grouping.
 * formatInteger(1234567, 'he') → "1,234,567"
 */
export function formatInteger(n: number, locale: Locale = 'he'): string {
  return n.toLocaleString(locale === 'he' ? 'he-IL' : 'en-US');
}

/**
 * Format opening-hours minutes-since-midnight as "HH:MM".
 * formatMinutesAsTime(90) → "01:30"
 */
export function formatMinutesAsTime(min: number): string {
  const h = Math.floor(min / 60);
  const m = min % 60;
  return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}
