// Genera bytes ESC/POS, el idioma de las impresoras termicas de 80mm.
// Sin librerias: son comandos de bytes simples y asi no dependemos de nadie.
import type { LineaComanda } from './tipos.js';

export const ANCHO_NORMAL = 48; // caracteres por linea en 80mm (fuente A)
export const ANCHO_GRANDE = 48; // "grande" es doble ALTO solamente: mismo ancho

// SOLO ASCII (bytes < 128), sin acentos. Leccion del piloto: las termicas
// chinas (POS-80 genericas) arrancan en modo chino GB y un byte alto (los
// acentos de CP850) se traga la letra siguiente y pinta un caracter chino
// ("Guayanes" salia "Guayan俿"). Con ASCII puro es IMPOSIBLE en cualquier
// impresora, este en el modo que este.
const SIN_ACENTO: Record<string, string> = {
  'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ú': 'u', 'ñ': 'n', 'Ñ': 'N',
  'Á': 'A', 'É': 'E', 'Í': 'I', 'Ó': 'O', 'Ú': 'U', 'ü': 'u', 'Ü': 'U',
  '¿': '?', '¡': '!', '°': 'o', '·': '-',
};

function codificar(texto: string): number[] {
  const bytes: number[] = [];
  for (const ch of texto) {
    const code = ch.codePointAt(0) ?? 63;
    if (code < 128) {
      bytes.push(code);
      continue;
    }
    const simple = SIN_ACENTO[ch];
    if (simple) {
      bytes.push(simple.charCodeAt(0));
      continue;
    }
    // Sin mapa: soltar el acento (a partir de la forma descompuesta) o '?'.
    const plano = ch.normalize('NFD').replace(/[̀-ͯ]/g, '');
    const cp = plano.codePointAt(0) ?? 63;
    bytes.push(cp > 0 && cp < 128 ? cp : 63);
  }
  return bytes;
}

/** Convierte las lineas de una comanda en bytes listos para la impresora. */
export function comandaABytes(lineas: LineaComanda[]): Buffer {
  const b: number[] = [];
  b.push(0x1b, 0x40); // reset
  b.push(0x1c, 0x2e); // FS . : cancelar modo chino (las POS-80 genericas arrancan en el)
  for (const l of lineas) {
    b.push(0x1b, 0x61, l.centrada ? 0x01 : 0x00); // alineacion
    b.push(0x1d, 0x21, l.grande ? 0x01 : 0x00); // "grande" = doble ALTO, mismo ancho (parejo)
    b.push(0x1b, 0x45, l.negrita ? 0x01 : 0x00); // negrita
    b.push(...codificar(l.texto), 0x0a);
  }
  b.push(0x1b, 0x45, 0x00, 0x1d, 0x21, 0x00);
  b.push(0x0a, 0x0a, 0x0a, 0x0a); // margen para que el corte no coma texto
  b.push(0x1d, 0x56, 0x42, 0x00); // corte parcial
  return Buffer.from(b);
}

/** La misma comanda como texto plano (consola, pruebas y previos). */
export function comandaATexto(lineas: LineaComanda[]): string {
  return lineas
    .map((l) => {
      if (!l.centrada) return l.texto;
      const ancho = l.grande ? ANCHO_GRANDE : ANCHO_NORMAL;
      const sobra = Math.max(0, ancho - l.texto.length);
      return ' '.repeat(Math.floor(sobra / 2)) + l.texto;
    })
    .join('\n');
}
