/* ──────────────────────────────────────────────────────────────
 *  Shared formatters
 *  Centralizes time/size/runtime/locale formatting so display code
 *  stays declarative.
 * ──────────────────────────────────────────────────────────── */

export const TICKS_PER_SECOND = 10_000_000

/** Convert Jellyfin ticks to seconds. */
export function ticksToSeconds(ticks?: number | bigint | null): number {
  if (!ticks) return 0
  return Number(ticks) / TICKS_PER_SECOND
}

/** "1h 34m" / "47m" / "12s" - humanized runtime from ticks. */
export function formatRuntime(ticks?: number | bigint | null): string {
  const seconds = ticksToSeconds(ticks)
  if (!seconds) return ''
  const h = Math.floor(seconds / 3600)
  const m = Math.floor((seconds % 3600) / 60)
  if (h > 0) return m > 0 ? `${h}h ${m}m` : `${h}h`
  if (m > 0) return `${m}m`
  return `${Math.round(seconds)}s`
}

/** "1:24:05" / "47:12" / "0:30" - timecode from ticks. */
export function formatTimecode(ticks?: number | bigint | null): string {
  const total = Math.floor(ticksToSeconds(ticks))
  const h = Math.floor(total / 3600)
  const m = Math.floor((total % 3600) / 60)
  const s = total % 60
  if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
  return `${m}:${s.toString().padStart(2, '0')}`
}

/** Same but accepts seconds directly (used by player which has currentTime). */
export function formatSeconds(seconds: number): string {
  if (!seconds || !isFinite(seconds)) return '0:00'
  const h = Math.floor(seconds / 3600)
  const m = Math.floor((seconds % 3600) / 60)
  const s = Math.floor(seconds % 60)
  if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
  return `${m}:${s.toString().padStart(2, '0')}`
}

/** "6.4 Mbps" / "320 kbps" */
export function formatBitrate(bps?: number | null): string {
  if (!bps) return ''
  if (bps >= 1_000_000) return `${(bps / 1_000_000).toFixed(1)} Mbps`
  if (bps >= 1_000) return `${Math.round(bps / 1_000)} kbps`
  return `${bps} bps`
}

/** "24.7 GB" / "812 MB" */
export function formatBytes(bytes?: number | null): string {
  if (!bytes) return ''
  const KB = 1024
  const MB = KB * 1024
  const GB = MB * 1024
  const TB = GB * 1024
  if (bytes >= TB) return `${(bytes / TB).toFixed(2)} TB`
  if (bytes >= GB) return `${(bytes / GB).toFixed(2)} GB`
  if (bytes >= MB) return `${(bytes / MB).toFixed(0)} MB`
  if (bytes >= KB) return `${(bytes / KB).toFixed(0)} KB`
  return `${bytes} B`
}

/** "Mar 12, 2024" */
export function formatDate(iso?: string | null): string {
  if (!iso) return ''
  try {
    const d = new Date(iso)
    return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
  } catch {
    return ''
  }
}

/** "Mar 2024" */
export function formatMonthYear(iso?: string | null): string {
  if (!iso) return ''
  try {
    const d = new Date(iso)
    return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short' })
  } catch {
    return ''
  }
}

/** "in 4 days" / "5 hours ago" */
export function formatRelative(iso?: string | null): string {
  if (!iso) return ''
  try {
    const d = new Date(iso).getTime()
    if (isNaN(d)) return ''
    const diff = d - Date.now()
    const abs = Math.abs(diff)
    const seconds = Math.round(diff / 1000)
    const minutes = Math.round(seconds / 60)
    const hours = Math.round(minutes / 60)
    const days = Math.round(hours / 24)
    const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })
    if (abs < 60_000) return rtf.format(seconds, 'second')
    if (abs < 3_600_000) return rtf.format(minutes, 'minute')
    if (abs < 86_400_000) return rtf.format(hours, 'hour')
    if (abs < 30 * 86_400_000) return rtf.format(days, 'day')
    if (abs < 365 * 86_400_000) return rtf.format(Math.round(days / 30), 'month')
    return rtf.format(Math.round(days / 365), 'year')
  } catch {
    return ''
  }
}

/** ISO 639 → display name. "eng" → "English" */
const langNamer = (() => {
  try {
    return new Intl.DisplayNames(undefined, { type: 'language' })
  } catch {
    return null
  }
})()

export function languageLabel(code?: string | null): string {
  if (!code) return ''
  // Jellyfin returns 3-letter codes; Intl.DisplayNames accepts both
  try {
    return langNamer?.of(code) || code.toUpperCase()
  } catch {
    return code.toUpperCase()
  }
}

/** ISO 3166 → country name. "US" → "United States" */
const countryNamer = (() => {
  try {
    return new Intl.DisplayNames(undefined, { type: 'region' })
  } catch {
    return null
  }
})()

export function countryLabel(code?: string | null): string {
  if (!code) return ''
  try {
    return countryNamer?.of(code.toUpperCase()) || code.toUpperCase()
  } catch {
    return code.toUpperCase()
  }
}

/** Render a 2-letter ISO 3166 code as a flag emoji. "US" → 🇺🇸 */
export function flagEmoji(code?: string | null): string {
  if (!code || code.length !== 2) return ''
  const upper = code.toUpperCase()
  if (!/^[A-Z]{2}$/.test(upper)) return ''
  const base = 0x1f1e6
  return String.fromCodePoint(
    base + upper.charCodeAt(0) - 65,
    base + upper.charCodeAt(1) - 65,
  )
}

/** Compact USD currency format. 145603415 → "$145,603,415" */
export function formatUsd(n?: number | null): string {
  if (!n || !Number.isFinite(n)) return ''
  try {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
      maximumFractionDigits: 0,
    }).format(n)
  } catch {
    return `$${n.toLocaleString()}`
  }
}

/** Long-form date "January 31, 2025" from an ISO date string. */
export function formatLongDate(iso?: string | null): string {
  if (!iso) return ''
  try {
    const d = new Date(iso)
    if (Number.isNaN(d.getTime())) return ''
    return d.toLocaleDateString(undefined, {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
    })
  } catch {
    return ''
  }
}

/** Default region for the current user, browser-derived. */
export function regionForUser(): string {
  try {
    const lang = navigator.language || 'en-US'
    const parts = lang.split('-')
    if (parts.length > 1 && parts[1].length === 2) return parts[1].toUpperCase()
    return 'US'
  } catch {
    return 'US'
  }
}
