import type { MotivoNoResuelto } from 'src/catalogo/diagnostico/diagnosticar-documento';
import { CATEGORIAS, CIERRES, Cierre, MOTIVOS } from './tipos';

/**
 * El modelo declara cómo terminó el turno con una marca al FINAL del texto:
 *
 *   [[cierre:autoservicio|documento_sifen|ninguno]]
 *
 * No es una herramienta a propósito: en Claude Opus 5.5 el texto que el modelo
 * escribe ANTES de una llamada a herramienta vuelve como nota de progreso
 * oculta, no como texto. Con una herramienta de cierre al final, la respuesta
 * al cliente quedaba escondida.
 *
 * FiltroCierre recorta la marca del stream sin demorarlo: retiene solo los
 * caracteres que podrían ser el comienzo de "[[cierre".
 */
const MARCA = '[[cierre';
const PATRON = /\[\[cierre:\s*([a-z_]+)\s*\|\s*([a-z_]+)\s*\|\s*([a-z_]+)\s*\]\]/;

export class FiltroCierre {
  private texto = '';
  private emitido = 0;
  private cortado = false;

  /** Recibe un delta del modelo y devuelve lo que ya se puede mandar al cliente. */
  agregar(delta: string): string {
    this.texto += delta;
    if (this.cortado) return '';

    const inicioMarca = this.texto.indexOf(MARCA, Math.max(0, this.emitido - MARCA.length));
    if (inicioMarca >= 0) {
      this.cortado = true;
      return this.tomarHasta(inicioMarca);
    }
    return this.tomarHasta(this.texto.length - this.retenidos());
  }

  /** Al terminar el stream: el texto visible completo y la declaración, si vino. */
  finalizar(): { pendiente: string; visible: string; cierre: Cierre | null } {
    const pendiente = this.cortado ? '' : this.tomarHasta(this.texto.length);
    const inicioMarca = this.texto.indexOf(MARCA);
    const visible = (inicioMarca >= 0 ? this.texto.slice(0, inicioMarca) : this.texto).trimEnd();
    return { pendiente, visible, cierre: leerMarca(this.texto) };
  }

  private tomarHasta(fin: number): string {
    if (fin <= this.emitido) return '';
    const parte = this.texto.slice(this.emitido, fin);
    this.emitido = fin;
    return parte;
  }

  /** Cuántos caracteres del final podrían ser el comienzo de la marca. */
  private retenidos(): number {
    for (let n = Math.min(MARCA.length - 1, this.texto.length); n > 0; n--) {
      if (MARCA.startsWith(this.texto.slice(-n))) return n;
    }
    return 0;
  }
}

export function leerMarca(texto: string): Cierre | null {
  const m = texto.match(PATRON);
  if (!m) return null;
  const [, resultado, categoria, motivo] = m;
  if (!(CIERRES as readonly string[]).includes(resultado)) return null;
  return {
    resultado: resultado as Cierre['resultado'],
    categoria: (CATEGORIAS as readonly string[]).includes(categoria) ? (categoria as Cierre['categoria']) : 'otro',
    motivo: (MOTIVOS as readonly string[]).includes(motivo) ? (motivo as MotivoNoResuelto) : null,
  };
}
