const DEFAULT_FORMATTING = {
  currencySymbol: '$',
  currencyPosition: 'before',
  currencySpacing: 'none',
  currencyDecimals: 2,
  numberDecimals: 2,
  percentDecimals: 1,
  thousandSeparator: 'comma',
  decimalSeparator: 'dot',
};

const SYSTEM_FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";

const DEFAULT_MEASUREMENT = {
  system: 'mixed',
};

const THOUSAND_MAP = {
  comma: ',',
  dot: '.',
  space: ' ',
  apostrophe: "'",
  none: '',
};

const DECIMAL_MAP = {
  dot: '.',
  comma: ',',
};

const nativeToLocaleString = Number.prototype.toLocaleString;

function ensureVareoncalcCfg() {
  if (typeof window === 'undefined') return;
  if (!window.VAREONCALC_CFG) {
    window.VAREONCALC_CFG = {};
  }
}

export function getFormattingConfig() {
  const cfg = (typeof window !== 'undefined' && window.VAREONCALC_CFG && window.VAREONCALC_CFG.formatting) || {};
  return {
    ...DEFAULT_FORMATTING,
    ...cfg,
  };
}

export function setFormattingConfig(patch = {}) {
  ensureVareoncalcCfg();
  const merged = {
    ...getFormattingConfig(),
    ...patch,
  };
  window.VAREONCALC_CFG.formatting = merged;
  return merged;
}

export function getMeasurementConfig() {
  const cfg = (typeof window !== 'undefined' && window.VAREONCALC_CFG && window.VAREONCALC_CFG.measurement) || {};
  return {
    ...DEFAULT_MEASUREMENT,
    ...cfg,
  };
}

export function setMeasurementConfig(patch = {}) {
  ensureVareoncalcCfg();
  const merged = {
    ...getMeasurementConfig(),
    ...patch,
  };
  window.VAREONCALC_CFG.measurement = merged;
  return merged;
}

function getDecimalSeparator(config) {
  return DECIMAL_MAP[config.decimalSeparator] ?? DECIMAL_MAP.dot;
}

/**
 * Get the current decimal separator character for use in placeholders.
 * @returns {string} The decimal separator ('.' or ',')
 */
export function getDecimalChar() {
  const config = getFormattingConfig();
  return getDecimalSeparator(config);
}

/**
 * Format a placeholder value using the configured decimal separator.
 * @param {string|number} value - The placeholder value (e.g., "3.50")
 * @returns {string} The formatted placeholder
 */
export function formatPlaceholder(value) {
  const decimalChar = getDecimalChar();
  return String(value).replace('.', decimalChar);
}

function getThousandSeparator(config) {
  return THOUSAND_MAP[config.thousandSeparator] ?? THOUSAND_MAP.comma;
}

export function escapeHtml(value) {
  if (value === null || value === undefined) return '';
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

export function getCurrencySymbol() {
  const { currencySymbol } = getFormattingConfig();
  return currencySymbol || '$';
}

export function getMeasurementSystem() {
  const { system } = getMeasurementConfig();
  return system || 'mixed';
}

/**
 * Format a numeric value using global formatting preferences.
 *
 * @param {number|string} value
 * @param {object} options
 * @param {boolean} [options.useGrouping] When true, always add thousands grouping (uses configured separator, or comma if unset). When false, never group. When omitted, follows plugin thousand-separator setting.
 * @returns {string}
 */
export function formatNumber(value, options = {}) {
  if (value === null || value === undefined || value === '') {
    return '0';
  }

  const num = Number(value);
  if (!isFinite(num)) {
    return '0';
  }

  const config = getFormattingConfig();
  const decimalsFromOption = typeof options.decimals === 'number'
    ? options.decimals
    : config.numberDecimals;

  const maxDecimals = typeof options.maxDecimals === 'number'
    ? options.maxDecimals
    : decimalsFromOption;

  const trim = options.trimTrailingZeros !== undefined ? options.trimTrailingZeros : true;
  const forceDecimals = options.forceDecimals || (!trim);

  let minDecimals;
  if (typeof options.minDecimals === 'number') {
    minDecimals = Math.min(options.minDecimals, maxDecimals);
  } else if (forceDecimals) {
    minDecimals = maxDecimals;
  } else {
    minDecimals = 0;
  }

  const absValue = Math.abs(num);
  const configuredThousand = getThousandSeparator(config);
  let useGrouping;
  if (options.useGrouping === true) {
    useGrouping = true;
  } else if (options.useGrouping === false) {
    useGrouping = false;
  } else {
    useGrouping = configuredThousand !== '';
  }
  const thousandChar = useGrouping
    ? (configuredThousand !== '' ? configuredThousand : THOUSAND_MAP.comma)
    : configuredThousand;

  let formatted = nativeToLocaleString.call(absValue, 'en-US', {
    minimumFractionDigits: minDecimals,
    maximumFractionDigits: maxDecimals,
    useGrouping,
  });

  // Replace decimal and thousand separators with placeholders first to avoid collisions.
  formatted = formatted.replace('.', '{decimal}');
  formatted = formatted.replace(/,/g, '{thousand}');

  const decimalChar = getDecimalSeparator(config);

  formatted = formatted.replace('{decimal}', decimalChar);
  formatted = formatted.replace(/{thousand}/g, thousandChar);

  if (trim && maxDecimals > minDecimals && formatted.includes(decimalChar)) {
    const parts = formatted.split(decimalChar);
    if (parts.length === 2) {
      let fractional = parts[1];
      while (fractional.length > minDecimals && fractional.endsWith('0')) {
        fractional = fractional.slice(0, -1);
      }
      if (fractional.length === 0) {
        formatted = parts[0];
      } else if (fractional !== parts[1]) {
        formatted = `${parts[0]}${decimalChar}${fractional}`;
      }
    }
  }

  return num < 0 ? `-${formatted}` : formatted;
}

export function formatCurrency(value, options = {}) {
  const config = getFormattingConfig();
  const symbol = options.symbol || getCurrencySymbol();
  const decimals = typeof options.decimals === 'number'
    ? options.decimals
    : config.currencyDecimals;

  const formattedNumber = formatNumber(value, {
    decimals,
    maxDecimals: options.maxDecimals ?? decimals,
    minDecimals: options.minDecimals ?? decimals,
    forceDecimals: true,
    trimTrailingZeros: options.trimTrailingZeros ?? false,
    useGrouping: options.useGrouping,
  });

  const isNegative = formattedNumber.startsWith('-');
  const absoluteNumber = isNegative ? formattedNumber.slice(1) : formattedNumber;
  const spacing = ((options.spacing ?? config.currencySpacing) === 'space') ? ' ' : '';
  const position = options.position || config.currencyPosition || 'before';

  const result = position === 'after'
    ? `${absoluteNumber}${spacing}${symbol}`
    : `${symbol}${spacing}${absoluteNumber}`;

  return isNegative ? `-${result}` : result;
}

export function formatCompactCurrency(value, options = {}) {
  if (value === null || value === undefined || value === '') {
    return formatCurrency(0, { ...options, decimals: 0, maxDecimals: 0, minDecimals: 0 });
  }

  const num = Number(value);
  if (!isFinite(num)) {
    return formatCurrency(0, { ...options, decimals: 0, maxDecimals: 0, minDecimals: 0 });
  }

  const config = getFormattingConfig();
  const symbol = options.symbol || getCurrencySymbol();
  const spacing = ((options.spacing ?? config.currencySpacing) === 'space') ? ' ' : '';
  const position = options.position || config.currencyPosition || 'before';

  const absValue = Math.abs(num);
  const magnitudes = [
    { value: 1e12, suffix: 'T' },
    { value: 1e9, suffix: 'B' },
    { value: 1e6, suffix: 'M' },
    { value: 1e3, suffix: 'k' },
  ];

  let scaled = num;
  let suffix = '';
  for (const magnitude of magnitudes) {
    if (absValue >= magnitude.value) {
      scaled = num / magnitude.value;
      suffix = magnitude.suffix;
      break;
    }
  }

  const scaledAbs = Math.abs(scaled);
  const decimals = suffix && scaledAbs < 10 ? 1 : 0;
  const formattedNumber = formatNumber(scaled, {
    decimals,
    maxDecimals: decimals,
    minDecimals: 0,
    trimTrailingZeros: true,
  });

  const isNegative = formattedNumber.startsWith('-');
  const absoluteNumber = isNegative ? formattedNumber.slice(1) : formattedNumber;
  const combinedNumber = `${absoluteNumber}${suffix}`;

  const result = position === 'after'
    ? `${combinedNumber}${spacing}${symbol}`
    : `${symbol}${spacing}${combinedNumber}`;

  return isNegative ? `-${result}` : result;
}

/**
 * Formats a value as currency and escapes HTML.
 * Use this when outputting currency values in innerHTML to prevent XSS.
 *
 * @param {number|string} value
 * @param {object} options
 * @returns {string}
 */
export function formatCurrencyHtml(value, options = {}) {
  return escapeHtml(formatCurrency(value, options));
}

/**
 * Currency for display: when |value| >= integerThreshold, show rounded whole units;
 * full formatted amount in native tooltip (title) and aria-label.
 * Below threshold, returns the same as formatCurrencyHtml (no wrapper).
 *
 * @param {number|string} value
 * @param {object} options
 * @param {number} [options.integerThreshold=100]
 * @returns {string} Safe HTML fragment
 */
export function formatCurrencyHtmlIntegerHover(value, options = {}) {
  const threshold = typeof options.integerThreshold === 'number' ? options.integerThreshold : 100;
  const num = Number(value);
  if (!isFinite(num)) {
    return escapeHtml(formatCurrency(0, options));
  }

  const full = formatCurrency(num, options);
  if (Math.abs(num) < threshold) {
    return escapeHtml(full);
  }

  const rounded = Math.round(num);
  const compact = formatCurrency(rounded, {
    ...options,
    decimals: 0,
    maxDecimals: 0,
    minDecimals: 0,
    trimTrailingZeros: true,
  });

  const fullEscaped = escapeHtml(full);
  const compactEscaped = escapeHtml(compact);
  return `<span class="fuel-currency-hover" title="${fullEscaped}" aria-label="${fullEscaped}"><span class="fuel-currency-hover__display">${compactEscaped}</span></span>`;
}

export function formatPercent(value, options = {}) {
  const config = getFormattingConfig();
  const decimals = typeof options.decimals === 'number'
    ? options.decimals
    : config.percentDecimals;
  const formattedNumber = formatNumber(value, {
    decimals,
    maxDecimals: options.maxDecimals ?? decimals,
    minDecimals: options.minDecimals ?? 0,
    forceDecimals: options.forceDecimals ?? false,
  });
  return `${formattedNumber}%`;
}

export function applyTypographyStyles(root) {
  if (!root) return;

  root.style.setProperty('--vareoncalc-font-text-family', SYSTEM_FONT_STACK);
  root.style.setProperty('--vareoncalc-font-text-weight', '400');
  root.style.setProperty('--vareoncalc-font-text-size', '1rem');

  root.style.setProperty('--vareoncalc-font-input-family', SYSTEM_FONT_STACK);
  root.style.setProperty('--vareoncalc-font-input-weight', '400');
  root.style.setProperty('--vareoncalc-font-input-size', '1rem');

  root.style.setProperty('--vareoncalc-font-button-family', SYSTEM_FONT_STACK);
  root.style.setProperty('--vareoncalc-font-button-weight', '600');
  root.style.setProperty('--vareoncalc-font-button-size', '1rem');

  root.style.setProperty('--vareoncalc-font-result-family', SYSTEM_FONT_STACK);
  root.style.setProperty('--vareoncalc-font-result-weight', '600');
  root.style.setProperty('--vareoncalc-font-result-size-main', '1.5rem');
  root.style.setProperty('--vareoncalc-font-result-size-detail', '1rem');

  root.style.setProperty('--vareoncalc-font-hint-family', SYSTEM_FONT_STACK);
  root.style.setProperty('--vareoncalc-font-hint-weight', '400');
  root.style.setProperty('--vareoncalc-font-hint-size', '0.95rem');
}

/**
 * Helper that returns both the formatted currency string and the raw number string
 * for UI components that need separate control.
 */
export function getCurrencyParts(value, options = {}) {
  const config = getFormattingConfig();
  const symbol = options.symbol || getCurrencySymbol();
  const formattedNumber = formatNumber(value, {
    decimals: options.decimals ?? config.currencyDecimals,
    maxDecimals: options.maxDecimals ?? (options.decimals ?? config.currencyDecimals),
    minDecimals: options.minDecimals ?? (options.forceDecimals ? (options.decimals ?? config.currencyDecimals) : 0),
    forceDecimals: options.forceDecimals ?? false,
    trimTrailingZeros: options.trimTrailingZeros ?? true,
  });
  return { symbol, number: formattedNumber };
}

export function formatList(values, formatter = formatNumber) {
  if (!Array.isArray(values)) return '';
  return values.map(v => formatter(v)).join(', ');
}

/**
 * Escape a string for use in a RegExp.
 */
function escapeRegExp(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

export function normalizeCurrencyInput(value) {
  if (typeof value === 'string') {
    const config = getFormattingConfig();
    const decimalChar = getDecimalSeparator(config);
    const thousandChar = getThousandSeparator(config);
    let normalized = value;
    if (thousandChar) {
      normalized = normalized.replace(new RegExp(escapeRegExp(thousandChar), 'g'), '');
    }
    if (decimalChar !== '.') {
      normalized = normalized.replace(new RegExp(escapeRegExp(decimalChar), 'g'), '.');
    }
    normalized = normalized.replace(/[^\d.-]/g, '');
    return parseFloat(normalized);
  }
  return Number(value);
}

const originalToLocaleString = nativeToLocaleString;

if (typeof Number !== 'undefined' && !Number.prototype.__vareoncalcEnhanced) {
  Object.defineProperty(Number.prototype, '__vareoncalcEnhanced', {
    value: true,
    enumerable: false,
    configurable: false,
    writable: false,
  });

  Number.prototype.toLocaleString = function (locale, options) {
    if (locale === undefined && options && typeof options === 'object') {
      const value = Number(this.valueOf());
      const config = getFormattingConfig();
      const maxDecimals = typeof options.maximumFractionDigits === 'number'
        ? options.maximumFractionDigits
        : (typeof options.minimumFractionDigits === 'number'
          ? options.minimumFractionDigits
          : config.numberDecimals);
      const minDecimals = typeof options.minimumFractionDigits === 'number'
        ? options.minimumFractionDigits
        : 0;
      const forceDecimals = options.minimumFractionDigits !== undefined
        && options.maximumFractionDigits !== undefined
        && options.minimumFractionDigits === options.maximumFractionDigits;

      return formatNumber(value, {
        decimals: maxDecimals,
        maxDecimals,
        minDecimals,
        forceDecimals,
        trimTrailingZeros: options.minimumFractionDigits === undefined,
      });
    }

    return originalToLocaleString.call(this, locale, options);
  };
}

export function formatCurrencyText(text) {
  if (typeof text !== 'string' || text.indexOf('$') === -1) return text;

  const config = getFormattingConfig();
  const symbolOnly = config.currencySymbol || '$';

  let updated = text.replace(/(^|[^A-Za-z0-9])([+\-]?)\$(\d[\d,.''\s]*)/g, (match, prefix, sign, numberPart) => {
    const decimalChar = getDecimalSeparator(config);
    const thousandChar = getThousandSeparator(config);

    // Preserve sentence spacing that may be captured by the currency match.
    const trailingWhitespaceMatch = numberPart.match(/(\s+)$/);
    const trailingWhitespace = trailingWhitespaceMatch ? trailingWhitespaceMatch[1] : '';
    const valuePortion = trailingWhitespace
      ? numberPart.slice(0, -trailingWhitespace.length)
      : numberPart;

    // Remove thousand separators only, then convert decimal separator to dot for parsing
    let normalized = valuePortion;
    if (thousandChar) {
      normalized = normalized.replace(new RegExp(escapeRegExp(thousandChar), 'g'), '');
    }
    if (decimalChar !== '.') {
      normalized = normalized.replace(new RegExp(escapeRegExp(decimalChar), 'g'), '.');
    }
    normalized = normalized.replace(/[^\d.-]/g, '');

    const numeric = parseFloat((sign === '-' ? '-' : '') + normalized);

    if (!Number.isFinite(numeric)) {
      return match;
    }

    let formatted = formatCurrency(numeric);

    if (sign === '+') {
      formatted = (numeric < 0 ? '-' : '+') + formatted.replace(/^[+-]/, '');
    }

    return `${prefix}${formatted}${trailingWhitespace}`;
  });

  if (!symbolOnly.includes('$')) {
    updated = updated.replace(/\$(?![\d])/g, symbolOnly);
  }
  return updated;
}

function applyCurrencyFormattingToNode(node) {
  if (typeof Node === 'undefined') return;
  if (node.nodeType === Node.TEXT_NODE) {
    const updated = formatCurrencyText(node.nodeValue);
    if (updated !== node.nodeValue) {
      node.nodeValue = updated;
    }
    return;
  }

  if (node.nodeType === Node.ELEMENT_NODE) {
    node.childNodes.forEach(applyCurrencyFormattingToNode);
  }
}

export function applyCurrencyFormatting(root) {
  if (typeof window === 'undefined' || !root) return;
  applyCurrencyFormattingToNode(root);
}

export function observeCurrencyFormatting(root) {
  if (typeof window === 'undefined' || !root || typeof MutationObserver === 'undefined') {
    return null;
  }

  applyCurrencyFormatting(root);

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      if (mutation.type === 'characterData') {
        const updated = formatCurrencyText(mutation.target.nodeValue);
        if (updated !== mutation.target.nodeValue) {
          mutation.target.nodeValue = updated;
        }
      } else if (mutation.type === 'childList') {
        mutation.addedNodes.forEach(node => applyCurrencyFormattingToNode(node));
      }
    });
  });

  observer.observe(root, { childList: true, characterData: true, subtree: true });
  return observer;
}
