<?php
/**
 * RUSH Newstream — server-rendered HTML site (DEV)
 * ------------------------------------------------
 * Every page is a full HTML document rendered by PHP. Content comes from the
 * existing JSON API (consumed server-side), so crawlers and Google News get the
 * complete page on first byte, real URLs work with the back button, and nothing
 * touches the live Svelte app. This file holds shared config + helpers.
 *
 * Data note: field names below (ai_summary, bias_distribution, hero_image_url,
 * etc.) are inferred from the current app. Load any page with ?debug=1 to dump
 * the real API shape and adjust the accessors if a name differs.
 */

// ── Config ──────────────────────────────────────────────────────────────────
const API_BASE   = 'https://ingest.rushnewstream.com/api';
const SITE_NAME  = 'RUSH Newstream';
const SITE_TAG   = 'News Without Spin';
// Path this dev site is served under. If hosted at rushnewstream.com/dev, keep
// '/dev'. If you later move it to the domain root, set to ''.
// This site runs at the ROOT of its own subdomain (e.g. html.rushnewstream.com),
// so BASE_PATH is empty. (It would be '/dev' only if served from a sub-path.)
const BASE_PATH  = '';
// Public origin (for absolute canonical / Open Graph URLs).
// The subdomain THIS html site is served from — used for canonical / OG urls.
// Set this to whatever subdomain you create in step 1 of SETUP.md.
const SITE_ORIGIN = 'https://html.rushnewstream.com';

// PHP 8.0 fallback for array_is_list (Hostinger is usually 8.1+, this is safety).
if (!function_exists('array_is_list')) {
    function array_is_list(array $a): bool {
        $i = 0; foreach ($a as $k => $_) { if ($k !== $i++) return false; } return true;
    }
}

// ── HTML / URL helpers ────────────────────────────────────────────────────────
/** Escape any dynamic value before printing into markup. */
function h($s): string { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }

/** Build a path under BASE_PATH (for internal links + assets). */
function url(string $path = ''): string { return BASE_PATH . '/' . ltrim($path, '/'); }

/** Absolute URL under the public origin (for canonical / og:url). */
function abs_url(string $path = ''): string { return SITE_ORIGIN . url($path); }

/** Collapse whitespace, strip tags, truncate on a word boundary. */
function excerpt($s, int $len = 160): string {
    $s = trim(preg_replace('/\s+/', ' ', strip_tags((string)$s)));
    if (mb_strlen($s) <= $len) return $s;
    $cut = mb_substr($s, 0, $len);
    $sp  = mb_strrpos($cut, ' ');
    if ($sp !== false) $cut = mb_substr($cut, 0, $sp);
    return rtrim($cut, " ,;:.") . '…';
}

/** First non-empty value across a list of possible keys on a row. */
function field(array $row, array $keys, $default = '') {
    foreach ($keys as $k) {
        if (isset($row[$k]) && $row[$k] !== '' && $row[$k] !== null) return $row[$k];
    }
    return $default;
}

// ── API ───────────────────────────────────────────────────────────────────────
/** GET a JSON endpoint; returns decoded array or null on failure. */
function api_get(string $endpoint, array $params = []): ?array {
    $qs = $params ? ('?' . http_build_query($params)) : '';
    $u  = API_BASE . '/' . ltrim($endpoint, '/') . $qs;
    $ch = curl_init($u);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 8,
        CURLOPT_CONNECTTIMEOUT => 4,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_USERAGENT      => 'RUSH-SSR/1.0',
    ]);
    $body = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($body === false || $code < 200 || $code >= 300) return null;
    $data = json_decode($body, true);
    return is_array($data) ? $data : null;
}

/** Normalize a list response ({events:[…]}, {data:[…]}, or bare […]) to rows. */
function api_list($data, array $keys = ['events', 'articles', 'data', 'results', 'items']): array {
    if (!is_array($data)) return [];
    if (array_is_list($data)) return $data;
    foreach ($keys as $k) if (isset($data[$k]) && is_array($data[$k])) return $data[$k];
    return [];
}

// ── Presentation helpers ───────────────────────────────────────────────────────
/** Render an L / C / R bias bar from a bias_distribution value (defensive about
 *  key names: left/center/right or l/c/r). Returns '' if no usable data. */
function bias_bar($bias): string {
    if (!is_array($bias)) return '';
    $L = (float) field($bias, ['left', 'l', 'L']);
    $C = (float) field($bias, ['center', 'c', 'C']);
    $R = (float) field($bias, ['right', 'r', 'R']);
    $sum = $L + $C + $R;
    if ($sum <= 0) return '';
    $pl = round($L / $sum * 100); $pc = round($C / $sum * 100); $pr = 100 - $pl - $pc;
    return '<div class="bias" role="img" aria-label="Coverage bias left ' . $pl . '% center ' . $pc . '% right ' . $pr . '%">'
         . '<span class="bias-l" style="width:' . $pl . '%">' . ($pl >= 12 ? 'L&nbsp;' . $pl . '%' : '') . '</span>'
         . '<span class="bias-c" style="width:' . $pc . '%">' . ($pc >= 12 ? 'C&nbsp;' . $pc . '%' : '') . '</span>'
         . '<span class="bias-r" style="width:' . $pr . '%">' . ($pr >= 12 ? 'R&nbsp;' . $pr . '%' : '') . '</span>'
         . '</div>';
}

/** Reusable hero detail card, ported from App.svelte (.dh-wrap / .dh-plain):
 *  hero image + gradient overlay headline + RUSH watermark + subcategory pill,
 *  or a plain text card when there's no image. */
function hero_card(array $o): string {
    $img = $o['image'] ?? ''; $title = $o['title'] ?? ''; $credit = $o['credit'] ?? '';
    $sub = $o['subcategory'] ?? ''; $href = $o['href'] ?? '';
    $hl = $href
        ? '<a class="dh-hl-link" href="' . h($href) . '">' . h($title) . '</a>'
        : '<div class="dh-hl">' . h($title) . '</div>';
    $pill = $sub ? '<span class="pill">' . h(ucwords(str_replace('_', ' ', $sub))) . '</span>' : '';
    if ($img) {
        return '<div class="dh-wrap">'
            . '<img class="dh-img" src="' . h($img) . '" alt="" loading="lazy">'
            . ($credit ? '<div class="dh-credit">Image: ' . h($credit) . '</div>' : '')
            . '<div class="dh-brand"><span class="dh-brand-rush">RUSH</span><span class="dh-brand-sub">NEWSTREAM</span></div>'
            . ($pill ? '<div class="dh-sub">' . $pill . '</div>' : '')
            . ($title !== '' ? '<div class="dh-ov">' . $hl . '</div>' : '')
            . '</div>';
    }
    return '<div class="dh-plain">' . $pill . $hl . '</div>';
}

/** Hero image + its credit (the source that supplied it) for an event. */
function event_hero(array $ev): array {
    foreach (($ev['articles'] ?? []) as $a) {
        if (!empty($a['hero_image_url'])) return ['image' => $a['hero_image_url'], 'credit' => $a['source_name'] ?? ''];
    }
    return ['image' => '', 'credit' => ''];
}

/** Events carry no image of their own; the hero comes from the first article
 *  that has a hero_image_url. Returns '' if none. */
function event_image(array $ev): string {
    foreach (($ev['articles'] ?? []) as $a) {
        if (!empty($a['hero_image_url'])) return $a['hero_image_url'];
    }
    return '';
}

/** Dump the shape of the first row when ?debug=1 — quick way to confirm field
 *  names against the real API without guessing. */
function maybe_debug(array $rows): void {
    if (!isset($_GET['debug'])) return;
    echo '<pre style="background:#111;color:#0f0;padding:16px;overflow:auto;font-size:12px;border-radius:8px">';
    echo "First row keys:\n";
    if ($rows) { echo h(implode("\n", array_keys($rows[0]))); }
    else { echo "(no rows returned)"; }
    echo "\n\nFull first row:\n" . h(json_encode($rows[0] ?? null, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    echo '</pre>';
}