<?php
/* ============================================================
 * Larspeenghar — server-side inbreeding batch (for 3M-scale herds)
 * Computes & stores each animal's inbreeding coefficient (inbreeding_f)
 * directly in the database, so the herd report stays instant without the
 * browser computing anything.
 *
 * Wright's path coefficient via the shortest path to each common ancestor:
 *   F(X) = Σ_A ( ½ )^( gSire(A) + gDam(A) + 1 )
 * computed from the two parents' ancestor sets (recursive CTE).
 *
 * RUN:
 *   CLI (recommended):   php recompute.php
 *   Cron every 5 min:    *_/5 * * * * php /path/recompute.php >> /path/recompute.log
 *   Browser (one chunk): recompute.php?key=SECRET&chunk=5000
 *
 * It is RESUMABLE: each run processes animals whose inbreeding_f is still
 * NULL, so you can stop/restart freely. DELETE this file when finished.
 * Requires MySQL 8+ / MariaDB 10.2.2+ (recursive CTE).
 * ============================================================ */

@set_time_limit(0);
@ini_set('memory_limit', '512M');
require __DIR__ . '/db.php';
$cli = (php_sapi_name() === 'cli');
$cfg = file_exists(__DIR__ . '/config.php') ? include __DIR__ . '/config.php' : [];

if (!$cli) {
    header('Content-Type: text/plain; charset=utf-8');
    $secret = $cfg['migrate_key'] ?? '';
    if ($secret === '' || ($_GET['key'] ?? '') !== $secret) {
        http_response_code(403);
        exit("Forbidden. Set 'migrate_key' in config.php and pass ?key=...\n");
    }
}

$pdo = lp_db();
$DEPTH = (int)($cfg['recompute_depth'] ?? 8);   // pedigree depth for F (8 ≈ good balance)
$CHUNK = (int)($_GET['chunk'] ?? ($cli ? 0 : 5000)); // 0 = run to completion (CLI)

function say($s) { echo $s . "\n"; @ob_flush(); @flush(); }

/* Fast path: animals missing a parent can never be inbred → F = 0 in bulk. */
$zeroed = $pdo->exec(
    "UPDATE animals SET inbreeding_f = 0
     WHERE inbreeding_f IS NULL AND (sire_id IS NULL OR dam_id IS NULL)"
);
say("Set F=0 for $zeroed animals with a missing parent.");

/* Recursive ancestor set (root included at gen 0), shortest generation each. */
$ancSql =
    "WITH RECURSIVE anc(id, gen) AS (
        SELECT :root, 0
        UNION ALL
        SELECT p.pid, anc.gen + 1
        FROM (
            SELECT a.id AS cid, a.sire_id AS pid FROM animals a WHERE a.sire_id IS NOT NULL
            UNION ALL
            SELECT a.id AS cid, a.dam_id AS pid FROM animals a WHERE a.dam_id IS NOT NULL
        ) p
        JOIN anc ON p.cid = anc.id
        WHERE anc.gen < :depth
     )
     SELECT id, MIN(gen) AS g FROM anc GROUP BY id";
$ancStmt = $pdo->prepare($ancSql);

function ancestors($stmt, $rootId, $depth) {
    $stmt->bindValue(':root', $rootId, PDO::PARAM_INT);
    $stmt->bindValue(':depth', $depth, PDO::PARAM_INT);
    $stmt->execute();
    $out = [];
    foreach ($stmt->fetchAll(PDO::FETCH_NUM) as $r) $out[(int)$r[0]] = (int)$r[1];
    return $out;
}

$upd = $pdo->prepare("UPDATE animals SET inbreeding_f = ? WHERE id = ?");
$t0 = microtime(true);
$done = 0;

while (true) {
    $batch = $pdo->query(
        "SELECT id, sire_id, dam_id FROM animals
         WHERE inbreeding_f IS NULL AND sire_id IS NOT NULL AND dam_id IS NOT NULL
         ORDER BY id LIMIT 2000"
    )->fetchAll(PDO::FETCH_ASSOC);
    if (!$batch) break;

    $pdo->beginTransaction();
    foreach ($batch as $a) {
        $ancS = ancestors($ancStmt, (int)$a['sire_id'], $DEPTH);
        $ancD = ancestors($ancStmt, (int)$a['dam_id'], $DEPTH);
        $F = 0.0;
        // iterate the smaller set
        if (count($ancD) < count($ancS)) { $tmp = $ancS; $ancS = $ancD; $ancD = $tmp; }
        foreach ($ancS as $id => $gs) {
            if (isset($ancD[$id])) $F += pow(0.5, $gs + $ancD[$id] + 1);
        }
        $upd->execute([round($F * 100, 2), (int)$a['id']]);
        $done++;
    }
    $pdo->commit();

    if ($done % 10000 === 0) {
        $rate = $done / max(0.001, microtime(true) - $t0);
        say(sprintf("  computed F for %s animals  (%.0f/sec)", number_format($done), $rate));
    }
    if ($CHUNK > 0 && $done >= $CHUNK) {
        say("Chunk done ($done). Re-run / let cron continue for the rest.");
        exit;
    }
}
say(sprintf("✅ All done. Computed F for %s animals in %.1fs (depth %d).", number_format($done), microtime(true) - $t0, $DEPTH));
say("DELETE recompute.php (and seed.php) when finished.");
