import type Big from 'big.js';

import { safeBig } from '../../math/decimal';

const FORMATTER_CACHE = new Map<string, Intl.NumberFormat>();
const PARTS_CACHE = new Map<string, { decimal: string; group: string }>();

function escapeRegExp(input: string): string {
  return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function resolveParts(locale: string): { decimal: string; group: string } {
  const cached = PARTS_CACHE.get(locale);
  if (cached) {
    return cached;
  }

  const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
  const decimal = parts.find((part) => part.type === 'decimal')?.value ?? '.';
  const group = parts.find((part) => part.type === 'group')?.value ?? ',';
  const resolved = { decimal, group };
  PARTS_CACHE.set(locale, resolved);
  return resolved;
}

function normalizeAmountInput(raw: string, locale: string): string {
  const { decimal, group } = resolveParts(locale);

  let normalized = raw.trim();
  if (normalized === '') {
    return '';
  }

  normalized = normalized.replace(/[\s\u00A0\u202F\u2009\u2007]/g, '');

  if (group) {
    const groupRegex = new RegExp(escapeRegExp(group), 'g');
    normalized = normalized.replace(groupRegex, '');
  }

  if (decimal && decimal !== '.') {
    const decimalRegex = new RegExp(escapeRegExp(decimal), 'g');
    normalized = normalized.replace(decimalRegex, '.');
  }

  normalized = normalized.replace(/[^\d+\-.,]/g, '');

  const sign = normalized.startsWith('-') ? '-' : (normalized.startsWith('+') ? '+' : '');
  if (sign !== '') {
    normalized = normalized.slice(1);
  }
  normalized = normalized.replace(/[+\-]/g, '');

  const lastDot = normalized.lastIndexOf('.');
  const lastComma = normalized.lastIndexOf(',');
  const decimalIndex = Math.max(lastDot, lastComma);

  if (decimalIndex >= 0) {
    const intPart = normalized.slice(0, decimalIndex).replace(/[.,]/g, '');
    const fracPart = normalized.slice(decimalIndex + 1).replace(/[.,]/g, '');
    normalized = `${intPart}.${fracPart}`;
  } else {
    normalized = normalized.replace(/[.,]/g, '');
  }

  if (normalized.startsWith('.')) {
    normalized = `0${normalized}`;
  }

  return `${sign}${normalized}`;
}

export function parseAmount(raw: string, locale: string): Big | null {
  const cleaned = normalizeAmountInput(raw, locale);
  if (cleaned === '' || cleaned === '.' || cleaned === '+' || cleaned === '-') return null;

  try {
    const parsed = safeBig(cleaned);
    if (parsed.lt('0')) {
      return null;
    }

    return parsed;
  } catch {
    return null;
  }
}

function resolveFormatter(locale: string, decimals: number): Intl.NumberFormat {
  const key = `${locale}:${decimals}`;
  const cached = FORMATTER_CACHE.get(key);
  if (cached) {
    return cached;
  }

  const formatter = new Intl.NumberFormat(locale, {
    minimumFractionDigits: decimals,
    maximumFractionDigits: decimals,
    useGrouping: true,
  });
  FORMATTER_CACHE.set(key, formatter);
  return formatter;
}

export function formatAmount(value: string | Big, decimals: number, locale: string): string {
  const fixed = safeBig(value).toFixed(decimals);
  const numeric = Number.parseFloat(fixed);
  if (!Number.isFinite(numeric)) {
    return fixed;
  }

  return resolveFormatter(locale, decimals).format(numeric);
}

export function formatRawAmount(value: string | Big, decimals: number): string {
  const fixed = safeBig(value).toFixed(decimals);
  return fixed.replace(/(\.\d*?[1-9])0+$/u, '$1').replace(/\.0+$/u, '');
}
