<?php
// save.php — receives POST from /odeon/x/ builder and writes schedule.json
// Updated 17:12 — 4 Sept 2026: prune days older than 7 days (file was 91% dead
// weight); write atomically via temp+rename.
// Merges new days into existing schedule — only days present in the POST are replaced.
// Days not included in the POST are left untouched.

header('Content-Type: application/json');

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['ok' => false, 'error' => 'POST only']);
    exit;
}

$raw = file_get_contents('php://input');
if (!$raw) {
    echo json_encode(['ok' => false, 'error' => 'Empty body']);
    exit;
}

// Size cap — mirrors the guard prefs.php already has. Without it this is an
// anonymous write primitive against the disk: an unbounded POST is written
// straight to schedule.json, which every page on the site fetches. A real
// payload is ~60KB; 256KB is generous headroom.
if (strlen($raw) > 262144) {
    echo json_encode(['ok' => false, 'error' => 'Too large']);
    exit;
}

$new = json_decode($raw, true);
if (!$new || !isset($new['days']) || !is_array($new['days'])) {
    echo json_encode(['ok' => false, 'error' => 'Invalid JSON']);
    exit;
}

$path = __DIR__ . '/../data/schedule.json';

// Load existing schedule (if any)
$existing = [];
if (file_exists($path)) {
    $existingRaw = file_get_contents($path);
    if ($existingRaw) {
        $decoded = json_decode($existingRaw, true);
        if ($decoded && isset($decoded['days'])) {
            // Index existing days by date for easy lookup
            foreach ($decoded['days'] as $day) {
                $existing[$day['date']] = $day;
            }
        }
    }
}

// Merge: replace each day from the new payload, keep everything else.
// A malformed entry (not an array, or missing 'date') is skipped rather than
// read blindly — that used to produce a PHP warning, and since headers are
// already sent by then, the warning text got prepended to the JSON response,
// which made publish-captures.py fail to parse it and report a failure it
// couldn't explain.
foreach ($new['days'] as $day) {
    if (!is_array($day) || !isset($day['date'])) continue;
    $existing[$day['date']] = $day;
}

// Drop dates older than a week. Without this the file grows forever: every
// page discards past dates on load anyway, so they were 91% of a 428KB file
// (135 past days / 2,550 shows) being re-downloaded on every visit, on every
// device, across five pages. A week of history costs almost nothing and keeps
// the file at roughly 60KB. Full history, if ever wanted, belongs in a
// separate archive file — not the one every page fetches.
$cutoff = date('Y-m-d', strtotime('-7 days'));
foreach (array_keys($existing) as $k) {
    if ($k < $cutoff) unset($existing[$k]);
}

// Sort by date and rebuild the days array
ksort($existing);

// 'published' read with a fallback rather than blindly: without isset, a
// missing key produced null here, which every page's new Date(data.published)
// turned into 1 Jan 1970.
$merged = [
    'published' => $new['published'] ?? date('Y-m-d H:i:s'),
    'days'      => array_values($existing)
];

// Write via temp-file + rename so a page fetching schedule.json mid-write can
// never read a half-written file. prefs.php already does this; this did not.
$output = json_encode($merged);
$tmp = $path . '.tmp';
if (file_put_contents($tmp, $output) === false || !rename($tmp, $path)) {
    @unlink($tmp);
    echo json_encode(['ok' => false, 'error' => 'Could not write file — check permissions']);
    exit;
}

echo json_encode(['ok' => true, 'saved' => date('H:i:s'), 'days' => count($merged['days'])]);
