<?php
// Cache disque pour resultats lourds et immuables (debrief, userhistory, get session).
// Chaque session a son propre sous-dossier cache/sess_<id>/ : invalider une session
// se reduit a supprimer le dossier, sans risque de match partiel sur d'autres ids.
// Toutes les fonctions sont silencieuses en cas d'echec (best-effort, jamais bloquant).

if (!defined('PROSERVE_CACHE_DIR'))
    define('PROSERVE_CACHE_DIR', __DIR__ . '/../cache');

function cache_session_dir($sessionId)
{
    return PROSERVE_CACHE_DIR . '/sess_' . intval($sessionId);
}

function cache_path($sessionId, $key)
{
    $safe = preg_replace('/[^a-zA-Z0-9_-]/', '_', $key);
    return cache_session_dir($sessionId) . '/' . $safe . '.json';
}

function cache_get($sessionId, $key)
{
    if (intval($sessionId) <= 0) return null;
    $path = cache_path($sessionId, $key);
    if (!file_exists($path)) return null;
    $data = @file_get_contents($path);
    if ($data === false || $data === '') return null;
    return $data;
}

function cache_put($sessionId, $key, $jsonString)
{
    if (intval($sessionId) <= 0) return;
    $dir = cache_session_dir($sessionId);
    if (!is_dir($dir)) @mkdir($dir, 0755, true);
    @file_put_contents(cache_path($sessionId, $key), $jsonString, LOCK_EX);
}

function cache_invalidate_session($sessionId)
{
    $sid = intval($sessionId);
    if ($sid <= 0) return;
    $dir = cache_session_dir($sid);
    if (!is_dir($dir)) return;
    $files = glob($dir . '/*');
    if (is_array($files)) {
        foreach ($files as $f) @unlink($f);
    }
    @rmdir($dir);
}

// =============================================================================
// DB-level cache for the materialized sessiondebriefs view (table session_debriefs_cache)
// Populated by stored procedure rebuild_debrief_cache(p_session_id) created in migration 1.7.0.
// =============================================================================

// Marque le cache DB d'une session comme stale (delete des lignes).
// Appele depuis les endpoints d'evenements (storetriggerevent, storereactevent)
// pour amortir le cout : la reconstruction se fait paresseusement au prochain
// read via ensure_session_debriefs_cached_db, ou explicitement via rebuild...
function invalidate_session_debrief_cache_db($db, $sessionId)
{
    $sid = intval($sessionId);
    if ($sid <= 0 || $db === null) return;
    try {
        $stmt = $db->prepare("DELETE FROM session_debriefs_cache WHERE SessionId = ?");
        $stmt->execute([$sid]);
    } catch (Exception $e) {
        // best-effort, ne jamais bloquer la requete metier
    }
}

// Reconstruit immediatement le cache DB d'une session (DELETE + INSERT via la proc).
// Appele depuis session/stop, session/userleave, session/updateobjectives.
function rebuild_session_debrief_cache_db($db, $sessionId)
{
    $sid = intval($sessionId);
    if ($sid <= 0 || $db === null) return;
    try {
        $stmt = $db->prepare("CALL rebuild_debrief_cache(?)");
        $stmt->execute([$sid]);
        $stmt->closeCursor();
    } catch (Exception $e) {
        // best-effort
    }
}

// Garantit que le cache DB contient au moins une ligne pour la session.
// Appele avant tout SELECT sur sessiondebriefs cote stats_object.php.
// Si le cache a ete invalide par un evenement et n'a pas encore ete reconstruit,
// cette fonction le reconstruit a la demande.
function ensure_session_debriefs_cached_db($db, $sessionId)
{
    $sid = intval($sessionId);
    if ($sid <= 0 || $db === null) return;
    try {
        $stmt = $db->prepare("SELECT 1 FROM session_debriefs_cache WHERE SessionId = ? LIMIT 1");
        $stmt->execute([$sid]);
        $exists = $stmt->fetchColumn();
        $stmt->closeCursor();
        if ($exists === false) {
            rebuild_session_debrief_cache_db($db, $sid);
        }
    } catch (Exception $e) {
        // si la table n'existe pas (migration 1.7.0 pas encore appliquee),
        // on ne fait rien : le code legacy continue de marcher via la vue
    }
}
?>
