<?php
// Unraid GraphQL API — single-request master fetch + per-function fallback tracking.
// All API-first functions call vv_api_data() then fall back to local reads on null.
//
// Confirmed schema (introspected 2026-05-29):
//   InfoOs:     hostname, uptime (String), release — no version/uptime-as-int
//   InfoCpu:    brand, threads, cores — no physicalCores/currentLoad
//   InfoMemory: layout only — NO usage fields; memory usage stays as local read
//   ArrayDisk:  single list for all types (DATA/PARITY/CACHE); fields: name, device,
//               type (ArrayDiskType enum), status (ArrayDiskStatus enum), size (BigInt),
//               fsSize, fsFree, fsUsed (BigInt), temp, transport (String), rotational,
//               isSpinning — no free/mounted
//   VmDomain:   name, state (VmState enum) — no memory/vcpus

require_once __DIR__ . '/config.php';

// ── Fallback tracking ─────────────────────────────────────────────────────────

function &_vv_api_fallbacks(): array { static $f = []; return $f; }

function vv_api_record_fallback(string $fn): void {
    $f  = &_vv_api_fallbacks();
    $f[] = $fn;
}

function vv_api_get_status(): array {
    $fallbacks = array_unique(_vv_api_fallbacks());
    return [
        'available' => empty($fallbacks),
        'fallbacks' => $fallbacks,
    ];
}

// ── Master fetch ──────────────────────────────────────────────────────────────

// Single combined query — one HTTP round-trip, cached for the request lifetime.
// Returns null if API is unreachable, key missing, or any query error occurs.
function vv_api_data(): ?array {
    static $cache = null, $fetched = false;
    if ($fetched) return $cache;
    $fetched = true;

    $hostId = vv_detect_host();
    $vars   = vv_conf_vars();
    $key    = $vars[strtoupper($hostId) . '_UNRAID_API_KEY'] ?? '';
    if (!$key) { $cache = null; return null; }

    // Fields verified against live schema introspection (2026-05-29).
    // array.parities / .disks / .caches are SEPARATE lists — .disks is DATA only.
    // metrics.cpu.percentTotal and metrics.memory.* provide real-time utilisation.
    $gql = <<<'GQL'
{
  info {
    os  { hostname uptime release }
    cpu { brand threads cores }
  }
  metrics {
    cpu    { percentTotal }
    memory { percentTotal total used available swapTotal swapUsed }
  }
  array {
    state
    parities { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
    disks    { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
    caches   { name device type status size fsSize fsFree fsUsed temp transport rotational isSpinning }
  }
  vms {
    domains { name state }
  }
}
GQL;

    $cache = vv_unraid_api_query($hostId, $gql, 5, $key);
    return $cache;
}

// ── Disk data helpers ─────────────────────────────────────────────────────────

// API size fields (BigInt) are in bytes on this schema.
// Heuristic: if raw > 100 billion → bytes; else → KB (covers both possible encodings).
function _vv_api_bytes_to_gb(float $raw): float {
    return $raw > 100_000_000_000
        ? round($raw / (1024 ** 3), 1)
        : round($raw / (1024 ** 2), 1);
}

// Map ArrayDiskType enum → role string used by the rest of the plugin.
// Unraid 6.9 used CACHE; 6.10+ renamed pools to POOL. FLASH is the USB boot drive (skip).
// Anything unrecognised (not DATA/PARITY*/FLASH) is treated as a pool.
function _vv_api_disk_role(string $type): string {
    $t = strtoupper($type);
    if (str_contains($t, 'PARITY')) return 'parity';
    if ($t === 'DATA')               return 'data';
    if ($t === 'FLASH')              return 'flash';   // USB boot — excluded from both views
    return 'cache';                                     // CACHE, POOL, or future variants
}

// Build a normalised disk entry from API data, matching the shape vv_disk_entry() produces.
// $role may be overridden; if empty it is derived from the disk's type field.
function vv_api_disk_entry(array $d, string $role = ''): ?array {
    if (!$role) $role = _vv_api_disk_role((string)($d['type'] ?? 'DATA'));

    if ($role === 'parity') {
        // Parity disks have no filesystem — use raw size only.
        $sizeRaw = (float)($d['size'] ?? 0);
        if ($sizeRaw <= 0) return null;
        $sizeGb  = _vv_api_bytes_to_gb($sizeRaw);
        $usedGb  = 0.0;
        $pct     = null;
    } else {
        // Data/cache disks: prefer fsSize/fsUsed; fall back to size if unmounted.
        $sizeRaw = (float)($d['fsSize'] ?? $d['size'] ?? 0);
        if ($sizeRaw <= 0) return null;
        $usedRaw = (float)($d['fsUsed'] ?? 0);
        $sizeGb  = _vv_api_bytes_to_gb($sizeRaw);
        $usedGb  = _vv_api_bytes_to_gb($usedRaw);
        $pct     = $sizeGb > 0 ? round($usedGb / $sizeGb * 100, 1) : null;
    }

    $temp      = isset($d['temp']) && is_numeric($d['temp']) ? (int)$d['temp'] : null;
    $spinning  = (bool)($d['isSpinning'] ?? true);
    $transport = $d['transport'] ?? (str_contains(strtolower($d['device'] ?? ''), 'nvme') ? 'nvme' : 'ata');

    return [
        'name'      => $d['name']   ?? '',
        'device'    => $d['device'] ?? '',
        'role'      => $role,
        'size_gb'   => $sizeGb,
        'used_gb'   => $usedGb,
        'pct'       => $pct,
        'temp'      => $temp,
        'transport' => strtolower($transport),
        'mounted'   => $spinning,
        'status'    => $d['status'] ?? 'DISK_OK',
    ];
}

// ── Confirmed schema (Unraid 7.2.5, introspected 2026-05-29) ─────────────────
// Adding a new host: add HOSTn="hostname" to master.conf and HOSTn_UNRAID_API_KEY
// to hostn.conf, then run Deployment/deploy.sh. No schema work needed.
//
// If a future Unraid version renames a field, the affected function falls back
// to local reads and the api banner lists the fallback — fix by updating the GQL.
