<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Media server session helpers. Discovers the Emby / Jellyfin / Plex instances declared in
//   the host confs and returns who is currently watching what, for the monitor page's
//   now-playing panel.
//
// DESIGN PRINCIPLES
//   Conf declares the servers; nothing is probed or auto-detected.
//       A server appears only because a URL and key were configured for it. Three server
//       types are supported side by side — this is not an either/or.
//
//   Placeholder credentials count as absent.
//       A key still containing "your-" is a template default that was never filled in, so the
//       server is skipped rather than queried. Half-configured is treated as unconfigured.
//
//   Unknown host reads both host slots.
//       When vv_detect_host() cannot identify the machine (development, or a hostname that
//       does not match any HOST<n>), both host confs are tried so the page still shows
//       something useful instead of nothing.
//
//   Session shape is normalised across server types.
//       Emby, Jellyfin and Plex return quite different payloads; callers get one consistent
//       structure and do not branch on server type.
//
// OPERATIONAL SAFEGUARDS
//   Every request is time-boxed at 3 seconds.
//       Session lookups run inside a page render, so a hung media server must not hold the
//       request open. The stream context timeout is the only thing standing between a
//       wedged Emby and a page that never returns.
//
//   Any failure yields an empty list, never an exception.
//       Unreachable server, non-JSON body, or an unexpected shape all return [] — the panel
//       renders empty and the rest of the page is unaffected.
//
//   Read-only. Sessions are observed; nothing is stopped, transcoded, or messaged.
//
// EXPORTS
//   vv_discover_media_servers()   configured Emby / Jellyfin / Plex instances for this host
//   vv_media_sessions()           normalised active sessions across all discovered servers
//   vv_fetch_jf_sessions()        Jellyfin-specific fetch
//   vv_fetch_plex_sessions()      Plex-specific fetch
//   vv_media_conf_scalar()        conf scalar reader used by the above
//
// CONFIGURATION
//   HOST*_EMBY_URL / _EMBY_API_KEY / _EMBY_CONTAINER
//   HOST*_JELLYFIN_URL / _JELLYFIN_API_KEY / _JELLYFIN_CONTAINER
//   HOST*_PLEX_URL / _PLEX_TOKEN
// ═══════════════════════════════════════════════════════════════════════════════════════════════

require_once __DIR__ . '/config.php';

// ── Conf reader ───────────────────────────────────────────────────────────────

function vv_media_conf_scalar(string $raw, string $key): string {
    return vv_parse_conf_scalar($raw, $key);
}

// ── Server list from host conf ────────────────────────────────────────────────

function vv_discover_media_servers(): array {
    $host = vv_detect_host(); // 'host1', 'host2', 'unknown'

    // For unknown (dev), try both host confs; otherwise read only the current host's file.
    $hostSlots = $host !== 'unknown' ? [$host] : ['host1', 'host2'];

    $servers = [];
    foreach ($hostSlots as $h) {
        $raw    = vv_read_conf_raw($h . '.conf');
        $prefix = strtoupper($h) . '_';             // HOST1_ or HOST2_

        $get = fn(string $k) => vv_media_conf_scalar($raw, $prefix . $k);

        // ── Emby ──────────────────────────────────────────────────────────────
        $embyUrl = $get('EMBY_URL');
        $embyKey = $get('EMBY_API_KEY');
        if ($embyUrl && $embyKey && !str_contains($embyKey, 'your-')) {
            $servers[] = [
                'type' => 'emby',
                'name' => $get('EMBY_CONTAINER') ?: 'Emby',
                'url'  => $embyUrl,
                'key'  => $embyKey,
            ];
        }

        // ── Jellyfin ──────────────────────────────────────────────────────────
        $jfUrl = $get('JELLYFIN_URL');
        $jfKey = $get('JELLYFIN_API_KEY');
        if ($jfUrl && $jfKey && !str_contains($jfKey, 'your-')) {
            $servers[] = [
                'type' => 'jellyfin',
                'name' => $get('JELLYFIN_CONTAINER') ?: 'Jellyfin',
                'url'  => $jfUrl,
                'key'  => $jfKey,
            ];
        }

        // ── Plex (optional) ───────────────────────────────────────────────────
        $plexToken = $get('PLEX_TOKEN');
        $plexUrl   = $get('PLEX_URL') ?: 'http://localhost:32400';
        if ($plexToken && !str_contains($plexToken, 'your-')) {
            $servers[] = [
                'type'  => 'plex',
                'name'  => 'Plex',
                'url'   => $plexUrl,
                'token' => $plexToken,
            ];
        }
    }

    // Deduplicate — same server can appear from multiple conf sources (unknown host, shared keys)
    $seen   = [];
    $unique = [];
    foreach ($servers as $s) {
        $k = $s['type'] . '|' . ($s['url'] ?? $s['token'] ?? '');
        if (!isset($seen[$k])) { $seen[$k] = true; $unique[] = $s; }
    }
    return $unique;
}

// ── Session fetchers ──────────────────────────────────────────────────────────

function vv_fetch_jf_sessions(array $srv): array {
    $url = rtrim($srv['url'], '/') . '/Sessions?api_key=' . urlencode($srv['key']) . '&activeWithinSeconds=60';
    $ctx = stream_context_create(['http' => ['timeout' => 3]]);
    $raw = @file_get_contents($url, false, $ctx);
    if (!$raw) return [];
    $sessions = json_decode($raw, true);
    if (!is_array($sessions)) return [];

    $result = [];
    foreach ($sessions as $s) {
        if (empty($s['NowPlayingItem'])) continue;
        $item = $s['NowPlayingItem'];
        $ps   = $s['PlayState'] ?? [];
        $tc   = $s['TranscodingInfo'] ?? null;

        $type  = $item['Type'] ?? '';
        $title = $item['Name'] ?? 'Unknown';
        if ($type === 'Episode' && !empty($item['SeriesName'])) {
            $ep    = sprintf('S%02dE%02d', $item['ParentIndexNumber'] ?? 0, $item['IndexNumber'] ?? 0);
            $title = $item['SeriesName'] . ' ' . $ep;
        }

        $pos = (int)($ps['PositionTicks'] ?? 0);
        $dur = (int)($item['RunTimeTicks'] ?? 0);
        $pct = $dur > 0 ? min(100, (int)round($pos / $dur * 100)) : 0;

        if ($tc) {
            $vc     = strtoupper($tc['VideoCodec'] ?? '');
            $hw     = !empty($tc['IsHardwareAcceleratedVideoDecoding']) ? ' HW' : '';
            $method = 'Transcode' . ($vc ? " ($vc$hw)" : '');
        } else {
            $pm     = $ps['PlayMethod'] ?? '';
            $method = $pm === 'DirectStream' ? 'Direct Stream' : 'Direct Play';
        }

        // Source video stream — resolution and codec of the file being played
        $videoStream = null;
        foreach ($item['MediaStreams'] ?? [] as $ms) {
            if (($ms['Type'] ?? '') === 'Video') { $videoStream = $ms; break; }
        }

        $result[] = [
            'server'      => $srv['name'],
            'server_type' => $srv['type'],
            'user'        => $s['UserName'] ?? '?',
            'title'       => $title,
            'type'        => $type,
            'client'      => trim(($s['Client'] ?? '') . ' / ' . ($s['DeviceName'] ?? ''), ' /'),
            'method'      => $method,
            'paused'      => !empty($ps['IsPaused']),
            'pct'         => $pct,
            'pos_sec'     => (int)($pos / 10000000),
            'dur_sec'     => (int)($dur / 10000000),
            'is_tc'       => $tc !== null,
            'height'      => (int)($videoStream['Height'] ?? 0),
            'codec'       => strtolower($videoStream['Codec'] ?? ''),
        ];
    }
    return $result;
}

function vv_fetch_plex_sessions(array $srv): array {
    $url = rtrim($srv['url'], '/') . '/status/sessions?X-Plex-Token=' . urlencode($srv['token']);
    $ctx = stream_context_create(['http' => ['timeout' => 3, 'header' => "Accept: application/json\r\n"]]);
    $raw = @file_get_contents($url, false, $ctx);
    if (!$raw) return [];
    $data  = json_decode($raw, true);
    $items = $data['MediaContainer']['Metadata'] ?? [];
    if (!is_array($items)) return [];

    $result = [];
    foreach ($items as $m) {
        $type   = strtolower($m['type'] ?? '');
        $title  = $m['title'] ?? 'Unknown';
        if ($type === 'episode') {
            $title = ($m['grandparentTitle'] ?? '') . ' S' . str_pad($m['parentIndex'] ?? 0, 2, '0', STR_PAD_LEFT)
                   . 'E' . str_pad($m['index'] ?? 0, 2, '0', STR_PAD_LEFT);
        }

        $dur    = (int)($m['duration'] ?? 0);
        $offset = (int)($m['viewOffset'] ?? 0);
        $pct    = $dur > 0 ? min(100, (int)round($offset / $dur * 100)) : 0;

        $tcInfo = $m['TranscodeSession'] ?? null;
        $isTc   = $tcInfo !== null;
        if ($isTc) {
            $vc     = strtoupper($tcInfo['videoCodec'] ?? '');
            $hw     = !empty($tcInfo['transcodeHwEncoding']) ? ' HW' : '';
            $method = 'Transcode' . ($vc ? " ($vc$hw)" : '');
        } else {
            $method = 'Direct Play';
        }

        $player = $m['Player'] ?? [];
        $media0 = $m['Media'][0] ?? [];
        $plexH  = (int)($media0['height'] ?? 0);
        if (!$plexH && !empty($media0['videoResolution'])) {
            $vr    = strtolower($media0['videoResolution']);
            $plexH = $vr === '4k' ? 2160 : (int)$vr;
        }
        $result[] = [
            'server'      => $srv['name'],
            'server_type' => $srv['type'],
            'user'        => ($m['User']['title'] ?? '?'),
            'title'       => $title,
            'type'        => ucfirst($type),
            'client'      => trim(($player['product'] ?? '') . ' / ' . ($player['title'] ?? ''), ' /'),
            'method'      => $method,
            'paused'      => ($player['state'] ?? '') === 'paused',
            'pct'         => $pct,
            'pos_sec'     => (int)($offset / 1000),
            'dur_sec'     => (int)($dur / 1000),
            'is_tc'       => $isTc,
            'height'      => $plexH,
            'codec'       => strtolower($media0['videoCodec'] ?? ''),
        ];
    }
    return $result;
}

// ── Public entry point ────────────────────────────────────────────────────────

function vv_media_sessions(): array {
    $servers  = vv_discover_media_servers();
    $sessions = [];
    foreach ($servers as $srv) {
        $found = $srv['type'] === 'plex'
            ? vv_fetch_plex_sessions($srv)
            : vv_fetch_jf_sessions($srv);
        foreach ($found as $s) $sessions[] = $s;
    }
    return [
        'sessions'     => $sessions,
        'server_names' => array_column($servers, 'name'),
        'server_count' => count($servers),
    ];
}
