<?php

declare(strict_types=1);

/**
 * STAR MERCHANT — the day-tick (SPEC §15.4). Included by the board's
 * daily cron with $doorCron (DoorCronContext). Idempotent per board-day;
 * everything rides one transaction.
 */

require_once __DIR__ . '/universe.php';

use Doors\StarMerchant\Universe;

$u = new Universe($doorCron->kv());
if ($doorCron->kv()->get('gen_done') !== '1') {
    return; // nobody has opened the door yet: no universe, no chores
}
$today = $doorCron->boardToday();
if ($doorCron->kv()->get('last_daily') === $today) {
    return;
}
$doorCron->kv()->set('last_daily', $today);

$doorCron->tx(function () use ($u, $doorCron): void {
    // Port stocks drift 15% toward full: sells replenish, gluts clear.
    foreach ($u->ports()->rows([]) as $port) {
        $u->ports()->update([
            'ore' => (int)$port['ore'] + (int)round((Universe::MAX_STOCK - (int)$port['ore']) * 0.15),
            'bio' => (int)$port['bio'] + (int)round((Universe::MAX_STOCK - (int)$port['bio']) * 0.15),
            'tech' => (int)$port['tech'] + (int)round((Universe::MAX_STOCK - (int)$port['tech']) * 0.15),
        ], ['id' => (int)$port['id']]);
    }

    // Planets produce by class, per 1,000 colonists (§15.3).
    foreach ($u->planets()->rows(['owner >' => 0]) as $pl) {
        $out = Universe::PLANET_CLASSES[(string)$pl['pclass']] ?? null;
        if ($out === null || (int)$pl['colonists'] < 1) {
            continue;
        }
        $k = (int)$pl['colonists'] / 1000;
        $u->planets()->update([
            'ore' => min(100000, (int)$pl['ore'] + (int)round($out[0] * $k)),
            'bio' => min(100000, (int)$pl['bio'] + (int)round($out[1] * $k)),
            'tech' => min(100000, (int)$pl['tech'] + (int)round($out[2] * $k)),
        ], ['id' => (int)$pl['id']]);
    }

    // Bank interest (0.5%/day) + fresh turns for everyone.
    $turns = $u->cfg('turns', 250);
    foreach ($u->players()->rows([]) as $p) {
        $u->players()->update([
            'bank' => (int)$p['bank'] + (int)floor((int)$p['bank'] * 0.005),
            'turns' => $turns,
        ], ['id' => (int)$p['id']]);
    }

    // Fresh drifter derelicts for the scavengers (2-5, wilds only).
    $u->drifters()->delete([]);
    $n = (int)$u->store->get('gen_sectors', '1000');
    for ($i = random_int(2, 5); $i > 0; $i--) {
        $u->drifters()->insert([
            'sector' => random_int(Universe::CHARTER_MAX + 1, max(Universe::CHARTER_MAX + 1, $n)),
            'loot' => random_int(500, 5000),
        ]);
    }

    // Marauders drift one warp overnight (never into Charter Space) and
    // fresh crews slip in until the galaxy is back at its cap.
    foreach ($u->marauders()->rows([]) as $mar) {
        $outs = array_values(array_filter(
            $u->warpsOut((int)$mar['sector']),
            fn(int $s) => $s > Universe::CHARTER_MAX
        ));
        if ($outs !== []) {
            $u->marauders()->update(
                ['sector' => $outs[random_int(0, count($outs) - 1)]],
                ['id' => (int)$mar['id']]
            );
        }
    }
    for ($i = $u->marauderCap() - $u->marauders()->count(); $i > 0; $i--) {
        $u->spawnMarauder();
    }
});

// Weekly hall-of-fame snapshot (top name + worth, kv-pinned by ISO week).
$week = date('o-\WW');
if ($doorCron->kv()->get('hall_latest') !== $week) {
    $best = null;
    foreach ($u->players()->rows([]) as $p) {
        $worth = $u->netWorth($p);
        if ($best === null || $worth > $best['worth']) {
            $best = ['handle' => (string)$p['handle'], 'worth' => $worth];
        }
    }
    if ($best !== null) {
        $doorCron->kv()->set('hall:' . $week, $best['handle'] . ' - ' . number_format($best['worth']) . ' cr');
        $doorCron->kv()->set('hall_latest', $week);
        $u->logEvent('Weekly registry closed: ' . $best['handle'] . ' leads at ' . number_format($best['worth']) . ' cr.');
    }
}

$doorCron->log('day-tick complete');
