// Arma las comandas de un pedido: parte los renglones por puesto (arepera,
// parrilla, barra) y produce las lineas listas para imprimir o previsualizar.
import { ANCHO_GRANDE, ANCHO_NORMAL } from './escpos.js';
import type { ComandaPuesto, ItemPedido, LineaComanda, PedidoNube, Puesto } from './tipos.js';

// Copia fiel de packages/shared/src/opciones.ts (el exe del cerebrito no
// importa fuera de su src/). NO editar solo aqui: el test
// test/test_copia_opciones.mjs revienta si las copias divergen.
export function parsearOpciones(nota: string | null | undefined): {
  grupos: { label: string; val: string }[];
  libre: string[];
} {
  const grupos: { label: string; val: string }[] = [];
  const libre: string[] = [];
  if (!nota) return { grupos, libre };
  for (const raw of nota.split('·')) {
    const p = raw.trim();
    if (!p) continue;
    const i = p.indexOf(':');
    if (i > 0) {
      let label = p
        .slice(0, i)
        .replace(/^elige\s+(tus?|tu)\s+/i, '')
        .replace(/\s*\([^)]*\)\s*$/, '')
        .trim();
      label = label.charAt(0).toUpperCase() + label.slice(1);
      grupos.push({ label, val: p.slice(i + 1).trim() });
    } else {
      libre.push(p);
    }
  }
  return { grupos, libre };
}

/** Corta un texto en lineas de maximo `ancho`, con sangria opcional. */
function envolver(texto: string, ancho: number, sangria = 0): string[] {
  const pref = ' '.repeat(sangria);
  const util = Math.max(1, ancho - sangria);
  const lineas: string[] = [];
  let actual = '';
  for (const palabra of texto.split(/\s+/).filter(Boolean)) {
    // Palabra mas larga que la linea: partirla a lo bruto.
    if (palabra.length > util) {
      if (actual) {
        lineas.push(actual);
        actual = '';
      }
      for (let i = 0; i < palabra.length; i += util) lineas.push(palabra.slice(i, i + util));
      continue;
    }
    const cand = actual ? `${actual} ${palabra}` : palabra;
    if (cand.length <= util) actual = cand;
    else {
      lineas.push(actual);
      actual = palabra;
    }
  }
  if (actual) lineas.push(actual);
  if (!lineas.length) lineas.push('');
  return lineas.map((l) => pref + l);
}

function tipoTexto(p: PedidoNube): string {
  // Venta directa de la caja: sin "PARA RETIRAR" (a Gilberto no le gusta);
  // la cocina solo necesita saber que el papel vuelve a la caja.
  if (p.origen === 'caja') return 'CAJA';
  if (p.tipo_entrega === 'mesa') return `MESA ${p.mesa ?? ''}`.trim();
  if (p.tipo_entrega === 'delivery') return 'DELIVERY';
  if (p.tipo_entrega === 'reserva') return `RESERVA${p.mesa ? ` MESA ${p.mesa}` : ''}`;
  if (p.tipo_entrega === 'mostrador') return 'MOSTRADOR';
  return 'PARA RETIRAR';
}

/** Fecha y hora como se leen aqui: 22/07/2026 5:42 pm (nada de hora militar). */
function fechaHora(iso: string): string {
  const d = new Date(iso);
  const dd = String(d.getDate()).padStart(2, '0');
  const mm = String(d.getMonth() + 1).padStart(2, '0');
  const min = String(d.getMinutes()).padStart(2, '0');
  const ampm = d.getHours() >= 12 ? 'pm' : 'am';
  const h = d.getHours() % 12 || 12;
  return `${dd}/${mm}/${d.getFullYear()} ${h}:${min} ${ampm}`;
}

function fmtUsd(n: number): string {
  return `$${n.toFixed(2)}`;
}

function fmtBs(n: number): string {
  return `Bs ${n.toLocaleString('es-VE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}

function nombreMetodo(m: string | null | undefined): string {
  const mapa: Record<string, string> = {
    efectivo: 'EFECTIVO',
    pago_movil: 'PAGO MOVIL',
    transferencia: 'TRANSFERENCIA',
    zelle: 'ZELLE',
    tarjeta: 'TARJETA',
  };
  return mapa[m ?? ''] ?? (m ?? 'SIN METODO').toUpperCase().replace(/_/g, ' ');
}

function puestoDefecto(puestos: Puesto[], modo: 'defecto' | 'ignorar'): Puesto | null {
  if (modo === 'ignorar') return null;
  const activos = puestos.filter((p) => p.activo);
  return activos.find((p) => p.es_defecto) ?? activos.sort((a, b) => a.orden - b.orden)[0] ?? null;
}

/** Puesto de un renglon: el del producto pisa al de su categoria; sin nada, el defecto. */
export function resolverPuesto(
  item: ItemPedido,
  puestos: Puesto[],
  modo: 'defecto' | 'ignorar',
): Puesto | null {
  const directo = item.producto?.puesto_id ?? item.producto?.categoria?.puesto_id ?? null;
  if (directo) {
    const p = puestos.find((x) => x.id === directo && x.activo);
    if (p) return p;
  }
  return puestoDefecto(puestos, modo);
}

const SEPARADOR = '-'.repeat(ANCHO_NORMAL);

/**
 * UNA MESA = UNA ORDEN: fusiona los pedidos confirmados de una cuenta (los 7
 * celulares del QR y/o los del mesero) en un solo pedido para imprimir UNA
 * comanda por puesto. Suma renglones identicos (mismo plato, misma nota).
 */
export function consolidarPedidosMesa(pedidos: PedidoNube[], secuencia: number): PedidoNube {
  const base = pedidos[0];
  const fusion = new Map<string, ItemPedido>();
  for (const p of pedidos) {
    for (const it of p.pedido_items ?? []) {
      const nota = p.para_llevar ? (it.nota ? `${it.nota} · Para llevar` : 'Para llevar') : it.nota;
      const clave = `${it.nombre}|${nota ?? ''}`;
      const ya = fusion.get(clave);
      if (ya) ya.cantidad += it.cantidad;
      else fusion.set(clave, { ...it, nota });
    }
  }
  const notas = pedidos.map((p) => p.nota).filter((n): n is string => !!n);
  return {
    ...base,
    id: base.id,
    codigo: `M${base.mesa ?? '?'}-${secuencia}`,
    para_llevar: pedidos.every((p) => p.para_llevar),
    mesero_nombre: pedidos.find((p) => p.mesero_nombre)?.mesero_nombre ?? null,
    nota: [pedidos.length > 1 ? `${pedidos.length} personas pidieron` : null, ...notas]
      .filter(Boolean)
      .join(' · ') || null,
    pedido_items: [...fusion.values()],
  };
}

/** Parte el pedido por puesto y arma la comanda de cada uno. */
export function armarComandas(
  p: PedidoNube,
  puestos: Puesto[],
  modo: 'defecto' | 'ignorar' = 'defecto',
): ComandaPuesto[] {
  const porPuesto = new Map<string, { puesto: Puesto; items: ItemPedido[] }>();
  for (const it of p.pedido_items ?? []) {
    // Mismo contrato que la pantalla de Cocina: lo que no se cocina (bebidas
    // con a_cocina=false) NO genera comanda. El panel igual muestra el pedido
    // completo (la encargada avisa) y el ticket de despacho SI las lista.
    if (it.a_cocina === false) continue;
    const puesto = resolverPuesto(it, puestos, modo);
    if (!puesto) continue;
    const bolsa = porPuesto.get(puesto.id) ?? { puesto, items: [] };
    bolsa.items.push(it);
    porPuesto.set(puesto.id, bolsa);
  }

  const hora = fechaHora(p.recibido_en);

  return [...porPuesto.values()]
    .sort((a, b) => a.puesto.orden - b.puesto.orden)
    .map(({ puesto, items }) => {
      const lineas: LineaComanda[] = [];
      lineas.push({ texto: puesto.nombre.toUpperCase(), grande: true, negrita: true, centrada: true });
      lineas.push({ texto: `#${p.codigo}`, grande: true, centrada: true });
      lineas.push({ texto: tipoTexto(p), grande: true, negrita: true, centrada: true });
      if (p.para_llevar) lineas.push({ texto: '* PARA LLEVAR *', grande: true, negrita: true, centrada: true });
      const sub = [hora];
      if (p.mesero_nombre) sub.push(`Atiende: ${p.mesero_nombre}`);
      lineas.push({ texto: sub.join('   '), centrada: true });
      lineas.push({ texto: SEPARADOR });

      items.forEach((it, idx) => {
        if (idx > 0) lineas.push({ texto: '' });
        for (const t of envolver(`${it.cantidad}x ${it.nombre}`, ANCHO_GRANDE)) {
          lineas.push({ texto: t, grande: true });
        }
        const { grupos, libre } = parsearOpciones(it.nota);
        for (const g of grupos) {
          for (const t of envolver(`${g.label}: ${g.val}`, ANCHO_NORMAL, 3)) lineas.push({ texto: t });
        }
        for (const l of libre) {
          for (const t of envolver(`>> ${l}`, ANCHO_NORMAL, 3)) lineas.push({ texto: t, negrita: true });
        }
      });

      if (p.nota) {
        lineas.push({ texto: SEPARADOR });
        for (const t of envolver(`NOTA: ${p.nota}`, ANCHO_NORMAL)) lineas.push({ texto: t, negrita: true });
      }
      lineas.push({ texto: SEPARADOR });
      lineas.push({ texto: 'Gustito Xpress', centrada: true });
      return { puesto, lineas };
    });
}

/** Linea con la plata a la derecha (48 columnas). */
function alinear(izq: string, der: string): string {
  const hueco = Math.max(1, ANCHO_NORMAL - izq.length - der.length);
  return izq.length + der.length >= ANCHO_NORMAL ? `${izq} ${der}` : izq + ' '.repeat(hueco) + der;
}

/**
 * PRE-CUENTA de una mesa (para llevarla al cliente ANTES de cobrar).
 * NO es factura y lo dice: es el mismo blindaje del ticket de despacho.
 */
export function armarTicketCuenta(datos: {
  negocio: string;
  mesa: string;
  renglones: { nombre: string; cantidad: number; precio_usd: number }[];
  total_usd: number;
  tasa_bs: number | null;
}): LineaComanda[] {
  const lineas: LineaComanda[] = [];
  lineas.push({ texto: datos.negocio, grande: true, negrita: true, centrada: true });
  lineas.push({ texto: `CUENTA · MESA ${datos.mesa}`, grande: true, centrada: true });
  lineas.push({ texto: fechaHora(new Date().toISOString()), centrada: true });
  lineas.push({ texto: SEPARADOR });
  for (const r of datos.renglones) {
    for (const t of envolver(alinear(`${r.cantidad}x ${r.nombre}`, fmtUsd(r.precio_usd * r.cantidad)), ANCHO_NORMAL)) {
      lineas.push({ texto: t });
    }
  }
  lineas.push({ texto: SEPARADOR });
  const totalTxt = datos.tasa_bs && datos.tasa_bs > 0 ? `${fmtUsd(datos.total_usd)}  (${fmtBs(datos.total_usd * datos.tasa_bs)})` : fmtUsd(datos.total_usd);
  for (const t of envolver(alinear('TOTAL', totalTxt), ANCHO_NORMAL)) lineas.push({ texto: t, grande: false, negrita: true });
  lineas.push({ texto: SEPARADOR });
  lineas.push({ texto: 'Gustito Xpress', centrada: true });
  lineas.push({ texto: 'ESTE TICKET NO ES FACTURA', negrita: true, centrada: true });
  lineas.push({ texto: 'Documento no fiscal, solo control de despacho', centrada: true });
  return lineas;
}

/**
 * COPIA-NEGOCIO de una MESA: sale SOLO cuando el cliente cancela (la mesera
 * cobra y libera), con el cobro REAL (metodo, referencia, propina). Nada de
 * inventar "cobra en efectivo" antes de tiempo. Sirve para la mesa de la nube
 * (panel) y para la del POS local.
 */
export function armarTicketCopiaMesa(datos: {
  mesa: string;
  mesero?: string | null;
  items: { nombre: string; cantidad: number; nota?: string | null }[];
  total_usd: number;
  propina_usd?: number;
  tasa_bs: number | null;
  pagos: { metodo: string | null; monto_usd?: number | null; referencia?: string | null }[];
}): LineaComanda[] {
  const lineas: LineaComanda[] = [];
  lineas.push({ texto: 'COPIA-NEGOCIO', grande: true, negrita: true, centrada: true });
  lineas.push({ texto: `MESA ${datos.mesa}`, grande: true, negrita: true, centrada: true });
  const sub = [fechaHora(new Date().toISOString())];
  if (datos.mesero) sub.push(`Atiende: ${datos.mesero}`);
  lineas.push({ texto: sub.join('   '), centrada: true });
  lineas.push({ texto: SEPARADOR });
  for (const it of datos.items) {
    for (const t of envolver(`${it.cantidad}x ${it.nombre}`, ANCHO_NORMAL)) lineas.push({ texto: t });
    const { grupos, libre } = parsearOpciones(it.nota);
    for (const g of grupos) {
      for (const t of envolver(`${g.label}: ${g.val}`, ANCHO_NORMAL, 3)) lineas.push({ texto: t });
    }
    for (const l of libre) {
      for (const t of envolver(`>> ${l}`, ANCHO_NORMAL, 3)) lineas.push({ texto: t, negrita: true });
    }
  }
  lineas.push({ texto: SEPARADOR });
  const totalTxt =
    datos.tasa_bs && datos.tasa_bs > 0
      ? `${fmtUsd(datos.total_usd)}  (${fmtBs(datos.total_usd * datos.tasa_bs)})`
      : fmtUsd(datos.total_usd);
  lineas.push({ texto: `Total: ${totalTxt}`, negrita: true });
  for (const pg of datos.pagos) {
    const monto = pg.monto_usd != null ? ` ${fmtUsd(pg.monto_usd)}` : '';
    const ref = pg.referencia ? ` ref ${pg.referencia}` : '';
    lineas.push({ texto: `Pagado: ${nombreMetodo(pg.metodo)}${monto}${ref}` });
  }
  if ((datos.propina_usd ?? 0) > 0) lineas.push({ texto: `Propina: ${fmtUsd(datos.propina_usd ?? 0)}` });
  lineas.push({ texto: SEPARADOR });
  lineas.push({ texto: 'Gustito Xpress', centrada: true });
  lineas.push({ texto: 'ESTE TICKET NO ES FACTURA', negrita: true, centrada: true });
  lineas.push({ texto: 'Documento no fiscal, solo control de despacho', centrada: true });
  return lineas;
}

/**
 * Ticket de DESPACHO: papel adicional para la bolsa y el repartidor, con los
 * datos del cliente, la direccion y el cobro. Trae TODOS los renglones (lista
 * de chequeo para armar la bolsa completa). La cocina nunca ve estos datos.
 */
export function armarTicketDespacho(p: PedidoNube, titulo = 'DESPACHO', cobrado = false): LineaComanda[] {
  const lineas: LineaComanda[] = [];
  lineas.push({ texto: titulo, grande: true, negrita: true, centrada: true });
  lineas.push({ texto: `#${p.codigo}`, grande: true, centrada: true });
  lineas.push({ texto: tipoTexto(p), grande: true, negrita: true, centrada: true });
  lineas.push({ texto: fechaHora(p.recibido_en), centrada: true });
  lineas.push({ texto: SEPARADOR });

  // Datos del cliente (a donde va la bolsa). En una copia de mesa (dine-in) no
  // hay cliente: en ese caso NO metemos el separador para no dejar doble raya.
  const antesCliente = lineas.length;
  if (p.cliente_nombre) {
    for (const t of envolver(`Cliente: ${p.cliente_nombre}`, ANCHO_NORMAL)) lineas.push({ texto: t });
  }
  if (p.cliente_telefono) lineas.push({ texto: `Telf: ${p.cliente_telefono}` });
  if (p.tipo_entrega === 'delivery' && p.direccion) {
    for (const t of envolver(`Direccion: ${p.direccion}`, ANCHO_NORMAL)) lineas.push({ texto: t, negrita: true });
  }
  if (lineas.length > antesCliente) lineas.push({ texto: SEPARADOR });

  // Lista de chequeo: todo lo que lleva la bolsa (tamano normal).
  for (const it of p.pedido_items ?? []) {
    for (const t of envolver(`${it.cantidad}x ${it.nombre}`, ANCHO_NORMAL)) lineas.push({ texto: t });
    const { grupos, libre } = parsearOpciones(it.nota);
    for (const g of grupos) {
      for (const t of envolver(`${g.label}: ${g.val}`, ANCHO_NORMAL, 3)) lineas.push({ texto: t });
    }
    for (const l of libre) {
      for (const t of envolver(`>> ${l}`, ANCHO_NORMAL, 3)) lineas.push({ texto: t, negrita: true });
    }
  }
  lineas.push({ texto: SEPARADOR });

  // Cobro: lo que el repartidor (o la caja) tiene que saber si o si.
  const total = p.total_usd ?? 0;
  const tasa = p.tasa_bs ?? 0;
  const totalTxt = tasa > 0 ? `${fmtUsd(total)}  (${fmtBs(total * tasa)})` : fmtUsd(total);
  lineas.push({ texto: `Total: ${totalTxt}`, negrita: true });
  if ((p.costo_delivery_usd ?? 0) > 0) {
    lineas.push({ texto: `(incluye delivery ${fmtUsd(p.costo_delivery_usd ?? 0)})` });
  }
  const metodo = nombreMetodo(p.metodo_pago);
  if (cobrado) {
    // Venta de caja: ya se cobro y verifico en el momento; nada que gritar.
    const pago = (p.pagos ?? [])[0];
    const ref = pago?.referencia ? ` ref ${pago.referencia}` : '';
    lineas.push({ texto: `Pagado: ${metodo}${ref}`, negrita: true });
  } else if ((p.metodo_pago ?? 'efectivo') === 'efectivo') {
    lineas.push({ texto: 'COBRA EN EFECTIVO', grande: true, negrita: true });
  } else {
    const pago = (p.pagos ?? []).find((x) => x.estado === 'confirmado') ?? (p.pagos ?? [])[0];
    const ref = pago?.referencia ? ` ref ${pago.referencia}` : '';
    if (pago?.estado === 'confirmado') {
      lineas.push({ texto: `Pago verificado: ${metodo}${ref}` });
    } else {
      for (const t of envolver(`>> VERIFICAR PAGO ANTES DE SALIR: ${metodo}${ref}`, ANCHO_NORMAL)) {
        lineas.push({ texto: t, negrita: true });
      }
    }
  }

  if (p.nota) {
    lineas.push({ texto: SEPARADOR });
    for (const t of envolver(`NOTA: ${p.nota}`, ANCHO_NORMAL)) lineas.push({ texto: t, negrita: true });
  }
  lineas.push({ texto: SEPARADOR });
  lineas.push({ texto: 'Gustito Xpress', centrada: true });
  // Leyenda fiscal, de ultimo (debajo de la marca, como pidio Gilberto): este
  // papel lleva totales y sin esto se puede confundir con factura (SENIAT).
  lineas.push({ texto: 'ESTE TICKET NO ES FACTURA', negrita: true, centrada: true });
  lineas.push({ texto: 'Documento no fiscal, solo control de despacho', centrada: true });
  return lineas;
}
