<?php
// ============================================================
//  lib/auditor_frecuencia.php
//
//  Motor de cumplimiento de frecuencia de intervenciones.
//  Recibe las notas ya parseadas por ParserIntervencion y las
//  cruza contra el catálogo de mapeo (actividad -> terapia) y
//  las reglas de contrato_frecuencias, para producir hallazgos
//  de incumplimiento.
//
//  Diseño acordado:
//   - El periodo auditable son SOLO los días en que el paciente
//     estuvo hospitalizado (no la semana calendario completa).
//   - Ingreso/Egreso se calculan como primera/última nota del
//     historial (no hay fecha explícita de ingreso/egreso en el
//     texto de las notas).
// ============================================================

class AuditorFrecuencia {

    // Códigos de día usados en dias_aplicables (SET de MySQL) -> ISO weekday (1=lunes..7=domingo)
    private const DIA_A_ISO = ['L' => 1, 'M' => 2, 'MI' => 3, 'J' => 4, 'V' => 5, 'S' => 6, 'D' => 7];
    private const ISO_A_NOMBRE = [1=>'Lunes',2=>'Martes',3=>'Miércoles',4=>'Jueves',5=>'Viernes',6=>'Sábado',7=>'Domingo'];

    /**
     * @param array $notas            Salida de ParserIntervencion::parsearTexto()['notas']
     * @param array $mapeoTitulos     ['TITULO NORMALIZADO' => terapia_id, ...] (o array de candidatos para títulos ambiguos, ver $ambiguos)
     * @param array $ambiguos         ['TITULO NORMALIZADO' => ['ESPECIALIDAD' => terapia_id, ...], ...] desempate por especialidad del profesional
     * @param array $reglasFrecuencia Filas de contrato_frecuencias relevantes (para el contrato del paciente), indexadas por terapia_id
     * @param array $nombreTerapiaPorId ['terapia_id' => 'Nombre legible'] solo para mensajes de hallazgos
     */
    public static function auditar(
        array $notas,
        array $mapeoTitulos,
        array $ambiguos,
        array $reglasFrecuencia,
        array $nombreTerapiaPorId = []
    ): array {

        // --- 1. Determinar periodo de hospitalización (con detección de anomalías) ---
        [$fechaIngreso, $fechaEgreso, $advertenciasFecha] = self::determinarPeriodo($notas);

        // --- 2. Resolver terapia_id de cada nota ---
        $notasResueltas = [];
        $sinMapeo = [];
        foreach ($notas as $nota) {
            $titulo = self::normalizar($nota['titulo_nota'] ?? '');
            $terapiaId = null;

            if ($titulo === '') {
                continue; // nota sin título, no auditable (ya se reportó aparte en el parseo)
            }

            if (isset($ambiguos[$titulo])) {
                $esp = self::normalizar($nota['especialidad'] ?? '');
                $terapiaId = $ambiguos[$titulo][$esp] ?? null;
                if ($terapiaId === null) {
                    $sinMapeo[$titulo . ' (especialidad: ' . ($nota['especialidad'] ?? '?') . ')'] = true;
                    continue;
                }
            } elseif (isset($mapeoTitulos[$titulo])) {
                $terapiaId = $mapeoTitulos[$titulo];
            } else {
                $sinMapeo[$nota['titulo_nota']] = true;
                continue;
            }

            $notaConTerapia = $nota;
            $notaConTerapia['terapia_id'] = $terapiaId;
            $notasResueltas[] = $notaConTerapia;
        }

        // --- 3. Agrupar notas resueltas por terapia_id ---
        $notasPorTerapia = [];
        foreach ($notasResueltas as $n) {
            $notasPorTerapia[$n['terapia_id']][] = $n;
        }

        // --- 4. Evaluar cada regla de frecuencia contra las notas correspondientes ---
        $hallazgos = [];
        foreach ($reglasFrecuencia as $regla) {
            $terapiaId = $regla['terapia_id'];
            $notasDeEstaTerapia = $notasPorTerapia[$terapiaId] ?? [];
            $nombreTerapia = $nombreTerapiaPorId[$terapiaId] ?? "terapia_id $terapiaId";

            $hallazgos = array_merge(
                $hallazgos,
                self::evaluarRegla($regla, $notasDeEstaTerapia, $fechaIngreso, $fechaEgreso, $nombreTerapia)
            );
        }

        return [
            'fecha_ingreso'        => $fechaIngreso,
            'fecha_egreso'         => $fechaEgreso,
            'advertencias_fecha'   => $advertenciasFecha,
            'hallazgos'            => $hallazgos,
            'actividades_sin_mapeo'=> array_keys($sinMapeo),
            'notas_resueltas'      => $notasResueltas, // cada nota + su terapia_id, para persistencia
        ];
    }

    public static function normalizar(string $s): string {
        $s = mb_strtoupper(trim($s), 'UTF-8');
        $s = strtr($s, ['Á'=>'A','É'=>'E','Í'=>'I','Ó'=>'O','Ú'=>'U','Ñ'=>'N']);
        $s = preg_replace('/\s+/', ' ', $s);
        return $s;
    }

    /**
     * Determina fecha de ingreso/egreso como primera/última nota,
     * pero detecta saltos de fecha anómalos (ej. typos de mes) y
     * los excluye del cálculo de egreso, reportándolos como
     * advertencia en vez de dejarlos distorsionar el periodo.
     */
    private static function determinarPeriodo(array $notas): array {
        $fechas = array_values(array_unique(array_filter(array_map(
            fn($n) => $n['fecha_aten'] ?? null, $notas
        ))));
        sort($fechas);

        if (empty($fechas)) {
            throw new RuntimeException('No hay fechas de atención válidas para determinar el periodo.');
        }

        $advertencias = [];
        $primeraValida = 0;
        $ultimaValida = count($fechas) - 1;

        // Detectar fechas anómalamente alejadas del resto en la COLA
        // (ej. un salto de mes por error de captura en el PDF, como "Ago" en vez de "Jul").
        while ($ultimaValida > $primeraValida) {
            $dias = (strtotime($fechas[$ultimaValida]) - strtotime($fechas[$ultimaValida - 1])) / 86400;
            if ($dias > 10) {
                $advertencias[] = "Fecha '{$fechas[$ultimaValida]}' se descartó del cálculo de EGRESO por ".
                    "estar a {$dias} días de la nota anterior ({$fechas[$ultimaValida-1]}) — posible error ".
                    "de captura (mes/typo) en el PDF. Verificar manualmente.";
                $ultimaValida--;
            } else {
                break;
            }
        }

        // Detectar fechas anómalamente alejadas del resto en el INICIO
        // (ej. "Jun" en vez de "Jul" — el mismo tipo de error pero al otro extremo,
        // que de no filtrarse se colaría como fecha de INGRESO y distorsionaría
        // todo el periodo auditado hacia atrás).
        while ($primeraValida < $ultimaValida) {
            $dias = (strtotime($fechas[$primeraValida + 1]) - strtotime($fechas[$primeraValida])) / 86400;
            if ($dias > 10) {
                $advertencias[] = "Fecha '{$fechas[$primeraValida]}' se descartó del cálculo de INGRESO por ".
                    "estar a {$dias} días de la siguiente nota ({$fechas[$primeraValida+1]}) — posible error ".
                    "de captura (mes/typo) en el PDF. Verificar manualmente.";
                $primeraValida++;
            } else {
                break;
            }
        }

        return [$fechas[$primeraValida], $fechas[$ultimaValida], $advertencias];
    }

    private static function evaluarRegla(
        array $regla, array $notasDeEstaTerapia, string $fechaIngreso, string $fechaEgreso, string $nombreTerapia
    ): array {
        $hallazgos = [];

        switch ($regla['tipo_frecuencia']) {

            case 'dias_semana':
                $diasCodigos = array_filter(array_map('trim', explode(',', $regla['dias_aplicables'] ?? '')));
                $fechasConNota = array_flip(array_map(fn($n) => $n['fecha_aten'], $notasDeEstaTerapia));

                $cursor = strtotime($fechaIngreso);
                $fin = strtotime($fechaEgreso);
                while ($cursor <= $fin) {
                    $fechaStr = date('Y-m-d', $cursor);
                    $isoDia = (int)date('N', $cursor);
                    foreach ($diasCodigos as $codigo) {
                        if ((self::DIA_A_ISO[$codigo] ?? null) === $isoDia) {
                            if (!isset($fechasConNota[$fechaStr])) {
                                $hallazgos[] = [
                                    'tipo'        => 'frecuencia_dias_semana',
                                    'terapia'     => $nombreTerapia,
                                    'fecha'       => $fechaStr,
                                    'descripcion' => "Falta sesión de \"$nombreTerapia\" el $fechaStr (" .
                                                     self::ISO_A_NOMBRE[$isoDia] . "), día contractualmente exigido.",
                                ];
                            }
                        }
                    }
                    $cursor = strtotime('+1 day', $cursor);
                }
                break;

            case 'evento':
                $tieneEnIngreso = false;
                $tieneEnEgreso = false;
                foreach ($notasDeEstaTerapia as $n) {
                    if ($n['fecha_aten'] === $fechaIngreso) $tieneEnIngreso = true;
                    if ($n['fecha_aten'] === $fechaEgreso) $tieneEnEgreso = true;
                }
                $evento = $regla['evento_tipo'] ?? null;
                if (in_array($evento, ['ingreso', 'ingreso_egreso']) && !$tieneEnIngreso) {
                    $hallazgos[] = [
                        'tipo' => 'frecuencia_evento', 'terapia' => $nombreTerapia, 'fecha' => $fechaIngreso,
                        'descripcion' => "Falta \"$nombreTerapia\" en el ingreso ($fechaIngreso).",
                    ];
                }
                if (in_array($evento, ['egreso', 'ingreso_egreso']) && !$tieneEnEgreso) {
                    $hallazgos[] = [
                        'tipo' => 'frecuencia_evento', 'terapia' => $nombreTerapia, 'fecha' => $fechaEgreso,
                        'descripcion' => "Falta \"$nombreTerapia\" en el egreso ($fechaEgreso).",
                    ];
                }
                break;

            case 'veces_semana':
            case 'veces_periodo':
                // Conteo por periodo anclado a la fecha de ingreso (no semana calendario),
                // consistente con "solo días hospitalizado".
                $periodoDias = $regla['tipo_frecuencia'] === 'veces_semana' ? 7 : (int)($regla['periodo_dias'] ?? 7);
                $objetivo = $regla['tipo_frecuencia'] === 'veces_semana'
                    ? (int)($regla['veces_por_semana'] ?? 0)
                    : (int)($regla['veces_por_periodo'] ?? 0);

                $cursor = strtotime($fechaIngreso);
                $fin = strtotime($fechaEgreso);
                $numPeriodo = 1;
                while ($cursor <= $fin) {
                    $finPeriodo = min($fin, strtotime("+" . ($periodoDias - 1) . " day", $cursor));
                    $conteo = 0;
                    foreach ($notasDeEstaTerapia as $n) {
                        $t = strtotime($n['fecha_aten']);
                        if ($t >= $cursor && $t <= $finPeriodo) $conteo++;
                    }
                    if ($conteo < $objetivo) {
                        $hallazgos[] = [
                            'tipo' => 'frecuencia_conteo', 'terapia' => $nombreTerapia,
                            'fecha' => date('Y-m-d', $cursor) . ' a ' . date('Y-m-d', $finPeriodo),
                            'descripcion' => "\"$nombreTerapia\": se esperaban $objetivo sesión(es) en el periodo " .
                                             date('Y-m-d', $cursor) . " a " . date('Y-m-d', $finPeriodo) .
                                             " y solo se registraron $conteo.",
                        ];
                    }
                    $cursor = strtotime("+" . $periodoDias . " day", $cursor);
                    $numPeriodo++;
                }
                break;
        }

        return $hallazgos;
    }
}
