// Renderizado simple de plantillas para archivos de acreditación bancaria.
// Sintaxis:
//   {{campo}}                   -> valor del campo (string)
//   {{campo|filtro1|filtro2:arg}} -> aplica filtros en orden
// Filtros soportados:
//   upper, lower, trim
//   trunc:N            corta a N caracteres
//   padl:N             rellena con espacios a la izquierda hasta N chars
//   padr:N             rellena con espacios a la derecha hasta N chars
//   pad0:N             rellena con ceros a la izquierda hasta N chars
//   num:N              formatea número con N decimales sin separadores
//   replace:from:to    reemplaza todas las ocurrencias
//   default:VALUE      si el valor es vacío/null, usa VALUE
//
// Si un campo no existe en el contexto se reemplaza por "" (con warning silenciado).

type Ctx = Record<string, unknown>;

const stripDiacritics = (s: string) => s.normalize('NFD').replace(/[̀-ͯ]/g, '');
const asString = (v: unknown): string => {
  if (v === null || v === undefined) return '';
  if (typeof v === 'number') return String(v);
  if (typeof v === 'boolean') return v ? 'true' : 'false';
  if (v instanceof Date) return v.toISOString().slice(0, 10);
  return String(v);
};

const filters: Record<string, (val: string, ...args: string[]) => string> = {
  upper: (v) => v.toUpperCase(),
  lower: (v) => v.toLowerCase(),
  trim: (v) => v.trim(),
  // Quita acentos (útil para bancos que sólo aceptan ASCII)
  noaccent: (v) => stripDiacritics(v),
  trunc: (v, n) => v.slice(0, Math.max(0, parseInt(n, 10) || 0)),
  padl: (v, n) => v.padStart(Math.max(0, parseInt(n, 10) || 0), ' '),
  padr: (v, n) => v.padEnd(Math.max(0, parseInt(n, 10) || 0), ' '),
  pad0: (v, n) => v.padStart(Math.max(0, parseInt(n, 10) || 0), '0'),
  num: (v, n) => {
    const num = Number(v);
    if (!Number.isFinite(num)) return v;
    const dec = Math.max(0, parseInt(n, 10) || 0);
    return num.toFixed(dec);
  },
  replace: (v, from, to) => v.split(from ?? '').join(to ?? ''),
  default: (v, val) => (v === '' ? (val ?? '') : v),
};

const applyFilter = (value: string, expr: string): string => {
  const parts = expr.split(':');
  const name = parts[0].trim();
  const args = parts.slice(1);
  const fn = filters[name];
  if (!fn) return value;
  return fn(value, ...args);
};

export function renderTemplate(template: string, ctx: Ctx): string {
  if (!template) return '';
  return template.replace(/\{\{([^}]+)\}\}/g, (_, expr) => {
    const segments = String(expr).split('|').map((s) => s.trim());
    const key = segments[0];
    let value = asString(ctx[key]);
    for (let i = 1; i < segments.length; i++) {
      value = applyFilter(value, segments[i]);
    }
    return value;
  });
}

/**
 * Interpreta los separadores escapados en el formato (`\n`, `\r`, `\t`).
 */
export function unescapeSeparator(sep: string | null | undefined): string {
  if (!sep) return '\n';
  return sep
    .replace(/\\r\\n/g, '\r\n')
    .replace(/\\n/g, '\n')
    .replace(/\\r/g, '\r')
    .replace(/\\t/g, '\t');
}
