<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Orchestrator membership toggle. Comments or uncomments one script's entry inside the
//   *_SCRIPTS arrays in master.conf, so the scheduler page can take a script out of an
//   orchestrator's run list without deleting it.
//
// OPERATIONAL MODEL
//   This is not the same switch as the scheduler's enable/disable. Scheduler state controls
//   whether a *cron entry* fires; this controls whether an orchestrator *calls a child
//   script* during its own run. A script can be disabled here and still run, if another
//   orchestrator lists it — unraid_api_key_renew.sh is deliberately in two arrays.
//
//   The edit is a comment marker, not a deletion. The line stays in master.conf with its
//   arguments and its position intact, so re-enabling restores exactly what was there before
//   and a diff shows an intent change rather than a removal.
//
// DESIGN PRINCIPLES
//   Only the first match is toggled.
//       vv_conf_toggle_script() breaks after the first array entry it matches. A script
//       listed in two orchestrators is not silently changed in both by one click.
//
//   No match is success, not failure.
//       A script that is not in any array has nothing to toggle and the conf is already in
//       the requested state. Returning an error there would make the UI report a problem
//       where none exists.
//
// OPERATIONAL SAFEGUARDS
//   POST only, checked before any parameter is read.
//
//   The id is pattern-matched and traversal-checked separately.
//       ^[a-zA-Z0-9_./\-]+\.sh$ permits the Category/script.sh form the arrays actually use,
//       so the slash cannot simply be banned — str_contains($id, '..') is therefore a second,
//       explicit check rather than something folded into the pattern.
//
//   The id is never used as a path.
//       It reaches vv_conf_toggle_script() only as a preg_quote()d needle matched against
//       existing lines in master.conf. Nothing is opened, executed, or created from it, so a
//       value that slipped past validation still has no file to reach.
//
//   The scope of the edit is bounded to array bodies.
//       The library tracks whether it is inside a `*_SCRIPTS=(` block and skips every line
//       outside one, so a matching string in a comment or an unrelated variable cannot be
//       rewritten.
//
//   The write is atomic.
//       vv_conf_toggle_script() writes through vv_write_conf_raw() (tmp + rename). Every
//       script sources master.conf; a truncated write here would be a system-wide outage
//       rather than a lost toggle.
//
// REQUEST
//   POST id=<Category/script.sh>  enabled=0|1
//
// RESPONSE
//   {"ok":true,"error":null}
//   {"ok":false,"error":"POST only"|"Invalid id"|"Failed to write master.conf"}
//
// DEPENDS ON
//   include/scheduler.php   vv_conf_toggle_script() → vv_write_conf_raw()
//   Configurations/master.conf   the *_SCRIPTS arrays
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/scheduler.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['ok' => false, 'error' => 'POST only']);
    exit;
}

$id      = trim($_POST['id']      ?? '');
$enabled = ($_POST['enabled'] ?? '0') === '1';

if (!$id || !preg_match('/^[a-zA-Z0-9_.\/\-]+\.sh$/', $id) || str_contains($id, '..')) {
    echo json_encode(['ok' => false, 'error' => 'Invalid id']);
    exit;
}

$ok = vv_conf_toggle_script($id, $enabled);
echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write master.conf']);
