<?php
/**
 * Demo Oracle — assemblage de la charge utile du SQL Monitor.
 *
 * Portage de modules/oracle/lib/OracleSqlMonPayload.php, reduit a la seule
 * branche sans licence. Les embranchements du module complet — plan monitore,
 * activite ASH, totaux de GV$SQL_MONITOR — ne sont pas desactives ici : ils
 * sont absents, comme les methodes de repository qu'ils appelaient.
 *
 * Ce fichier n'emet AUCUN appel OCI : il orchestre le repository.
 *
 * Safe to include multiple times (guard sur class_exists).
 */
/*
 * Les dependances sont chargees AVANT la garde, et c'est indispensable.
 *
 * PHP lie les declarations de classe a la COMPILATION du fichier, donc avant
 * d'en executer la premiere instruction. Quand la garde s'evalue, la classe
 * definie plus bas existe deja : la garde `return` systematiquement, et tout
 * ce qui la suit est du code mort. Un require_once place apres elle ne
 * s'execute jamais.
 */
require_once __DIR__ . '/DemoOracleSqlMonSource.php';
require_once __DIR__ . '/DemoOracleSqlMonitorRepository.php';
require_once __DIR__ . '/DemoOracleSqlMonAccumulator.php';

if (class_exists('DemoOracleSqlMonPayload')) return;

class DemoOracleSqlMonPayload
{
    /**
     * @param resource $conn     connexion OCI8 active
     * @param array    $src      retour de DemoOracleSqlMonSource::resolve()
     * @param string   $dbKey    identifie la base pour l'accumulateur
     * @param string   $phv      plan_hash_value connu du client ('' si inconnu)
     * @param int      $now      horodatage du releve, injecte
     * @param int      $maxLines troncature du plan (plafond de la demo)
     */
    public static function build($conn, array $src, string $dbKey,
                                 string $sqlId, int $execId, string $execStart,
                                 string $phv, int $now, int $maxLines): array
    {
        // ── L'en-tete de l'execution ─────────────────────────────────────
        $exec = DemoOracleSqlMonitorRepository::getExecutionSession(
            $conn, $sqlId, $execId, $execStart);

        /* Sans pack, une execution terminee ne laisse aucune trace : plus de
           session a interroger. La fin est DEDUITE, et la vue le dit. */
        $done = empty($exec);

        if ($phv === '' && !empty($exec['phv'])) $phv = (string)$exec['phv'];

        // ── Le plan, estime ──────────────────────────────────────────────
        $plan = ($phv !== '')
            ? DemoOracleSqlMonitorRepository::getEstimatedPlan($conn, $sqlId, $phv)
            : [];

        $truncated = false;
        if ($maxLines > 0 && count($plan) > $maxLines) {
            $plan      = array_slice($plan, 0, $maxLines);
            $truncated = true;
        }

        // ── L'activite : un poll, un echantillon ─────────────────────────
        $key      = DemoOracleSqlMonAccumulator::key($dbKey, $sqlId, $execId, $execStart);
        $statsNow = [];

        if ($done) {
            /* Plus rien a relever : on se contente de rendre ce qui a ete
               accumule tant que l'execution vivait. */
            $state = DemoOracleSqlMonAccumulator::load($key);
        } else {
            $rows     = DemoOracleSqlMonitorRepository::sampleSessionPlanLines(
                $conn, $sqlId, $execId, $execStart, (bool)$src['plan_line']);
            $statsNow = DemoOracleSqlMonitorRepository::getSessionStatDelta(
                $conn, $sqlId, $execId, $execStart);
            $state    = DemoOracleSqlMonAccumulator::add($key, $rows, $statsNow, $now);
        }
        $activity = $state['lines'];

        $samples = 0;
        foreach ($activity as $byClass) {
            foreach ($byClass as $n) $samples += (int)$n;
        }

        /* Une colonne SQL_PLAN_LINE_ID presente mais jamais alimentee ne se
           detecte qu'ici : tous les echantillons tombent sous la cle ''. */
        $attributed = 0;
        foreach ($activity as $line => $byClass) {
            if ((string)$line === '') continue;
            foreach ($byClass as $n) $attributed += (int)$n;
        }

        // ── Zones de travail : memoire, temp et passes par ligne ──────────
        $wa = DemoOracleSqlMonitorRepository::getWorkareaActive(
            $conn, $sqlId, $execId, $execStart);
        $waByLine = [];
        foreach ($wa as $w) {
            $l = (int)$w['line'];
            if (!isset($waByLine[$l])) {
                $waByLine[$l] = ['mem' => 0, 'temp' => 0, 'passes' => 0];
            }
            $waByLine[$l]['mem']    = max($waByLine[$l]['mem'],    (int)$w['max_mem']);
            $waByLine[$l]['temp']   = max($waByLine[$l]['temp'],   (int)$w['temp']);
            $waByLine[$l]['passes'] = max($waByLine[$l]['passes'], (int)$w['passes']);
        }

        // ── Fusion dans les lignes de plan ───────────────────────────────
        $maxSamples = 0;
        foreach ($plan as &$row) {
            $act = $activity[(string)$row['id']] ?? [];
            $sum = 0;
            foreach ($act as $n) $sum += (int)$n;

            $row['act']     = $act;
            $row['act_sum'] = $sum;
            $row['act_pct'] = ($samples > 0) ? round($sum * 100 / $samples, 1) : 0.0;
            if ($sum > $maxSamples) $maxSamples = $sum;

            if (isset($waByLine[(int)$row['id']])) {
                $w = $waByLine[(int)$row['id']];
                if ($w['mem']  > 0) $row['mem']  = $w['mem'];
                if ($w['temp'] > 0) $row['temp'] = $w['temp'];
                $row['passes'] = $w['passes'];
                $row['active'] = true;
            }
        }
        unset($row);

        $longops = DemoOracleSqlMonitorRepository::getLongOpsForExec(
            $conn, $sqlId, $execId, $execStart);

        /* Totaux : le delta des statistiques de session depuis le premier
           releve. Une session dediee a une execution ne fait rien d'autre
           pendant qu'elle tourne, donc son delta EST sa consommation. */
        $d = DemoOracleSqlMonAccumulator::delta($state, $statsNow);

        return [
            'updated'        => date('H:i:s', $now),
            'source'         => $src['source'],
            'plan_source'    => $src['plan_source'],
            'activity_scope' => $src['activity'],
            'plan_line'      => !empty($src['plan_line']) && $attributed > 0,
            'licensed'       => !empty($src['licensed']),
            'status'         => $done ? 'DONE' : 'EXECUTING',
            'done'           => $done,
            'status_deduced' => $done,
            'exec'           => $exec,
            'phv'            => $phv,
            'plan'           => $plan,
            'plan_truncated' => $truncated,
            'unattributed'   => $samples - $attributed,
            'activity_total' => $samples,
            'activity_max'   => $maxSamples,
            'sample_count'   => (int)$state['n'],
            'sample_span'    => max(0, (int)$state['last'] - (int)$state['first']),
            'longops'        => $longops,
            'workareas'      => $wa,
            'totals'         => [
                'scope'       => 'delta',
                'elapsed_s'   => $exec['elapsed_s'] ?? 0,
                /* Centisecondes dans V$SESSTAT, secondes partout ailleurs. */
                'cpu_s'       => round(($d['CPU used by this session'] ?? 0) / 100, 2),
                'user_io_s'   => round(($d['user I/O wait time'] ?? 0) / 100, 2),
                'lio'         => $d['session logical reads'] ?? 0,
                'pio'         => $d['physical reads'] ?? 0,
                'read_bytes'  => $d['physical read total bytes'] ?? 0,
                'write_bytes' => $d['physical write total bytes'] ?? 0,
            ],
        ];
    }
}
