<?php

/**
 * Admin "Redirect Decision" control.
 *
 * Sets the timed-filter cycle used by redirect/index.php:
 *   filter window (default 2 min) → normal window (default 3 min) → repeat.
 * Writes runtime override files into the shared {tmp}/srp_bb/ cache dir that the
 * redirect engine reads (filter_timing.json + filter_url.txt). No deploy needed.
 */

declare(strict_types=1);

error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');

const RD_CSRF_NS  = 'rd_panel';
const RD_CACHE    = 'srp_bb';
const RD_DEF_FILTER = 120;
const RD_DEF_CYCLE  = 300;
const RD_SESSION  = 'srp_rd_panel';
const RD_MAX_FAILS = 8;
const RD_LIVE_STATS_CACHE_TTL = 15;
const RD_TABLE_STATS_CACHE_TTL = 60;

require_once dirname(__DIR__) . '/env.php';
load_env_file(dirname(__DIR__) . '/.env');
require_once dirname(__DIR__) . '/login_throttle.php';

/** Redirect Decision password hash must be configured explicitly in .env. */
$rdPasswordHash = app_env('RD_PASSWORD_HASH', '') ?? '';

/** Lazy PDO — stats panels degrade gracefully if the DB is unreachable. */
function rdPdo(): ?PDO
{
    static $pdo = false;
    if ($pdo === false) {
        try {
            $candidate = require dirname(__DIR__) . '/connection_pdo.php';
            $pdo = $candidate instanceof PDO ? $candidate : null;
        } catch (Throwable $e) {
            $pdo = null;
        }
    }

    return $pdo;
}

function rdCacheDir(): string
{
    return rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . RD_CACHE;
}

function rdStatsCacheFile(string $name): string
{
    return rdCacheDir() . DIRECTORY_SEPARATOR . $name . '_' . gmdate('Ymd') . '.json';
}

/**
 * @return array<int|string,mixed>|null
 */
function rdReadStatsCache(string $file, int $ttlSeconds): ?array
{
    if (!is_file($file)) {
        return null;
    }

    $mtime = filemtime($file);
    if ($mtime === false || (time() - $mtime) > $ttlSeconds) {
        return null;
    }

    $raw = file_get_contents($file, false, null, 0, 65536);
    if (!is_string($raw) || $raw === '') {
        return null;
    }

    try {
        $decoded = json_decode($raw, true, 16, JSON_THROW_ON_ERROR);
    } catch (JsonException) {
        return null;
    }

    if (!is_array($decoded) || ($decoded['_date'] ?? '') !== gmdate('Y-m-d')) {
        return null;
    }

    $payload = $decoded['payload'] ?? null;

    return is_array($payload) ? $payload : null;
}

/**
 * @param array<int|string,mixed> $payload
 */
function rdWriteStatsCache(string $file, array $payload): void
{
    $dir = dirname($file);
    if (!is_dir($dir)) {
        @mkdir($dir, 0700, true);
    }

    $json = json_encode(
        ['_date' => gmdate('Y-m-d'), 'payload' => $payload],
        JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES,
    );
    @file_put_contents($file, $json, LOCK_EX);
}

/**
 * @param array<int|string,mixed> $row
 * @return array{clicks_today:int,leads_today:int,postbacks_today:int,payout_today:float,ts:string}
 */
function rdNormalizeLiveStats(array $row): array
{
    return [
        'clicks_today' => rdNumberToInt($row['clicks_today'] ?? null),
        'leads_today' => rdNumberToInt($row['leads_today'] ?? null),
        'postbacks_today' => rdNumberToInt($row['postbacks_today'] ?? null),
        'payout_today' => rdNumberToFloat($row['payout_today'] ?? null),
        'ts' => is_string($row['ts'] ?? null) ? $row['ts'] : gmdate('H:i:s') . ' UTC',
    ];
}

function rdNumberToInt(mixed $value): int
{
    if (is_int($value)) {
        return $value;
    }
    if (is_float($value)) {
        return (int) $value;
    }
    if (is_string($value) && is_numeric($value)) {
        return (int) $value;
    }

    return 0;
}

function rdNumberToFloat(mixed $value): float
{
    if (is_float($value) || is_int($value)) {
        return (float) $value;
    }
    if (is_string($value) && is_numeric($value)) {
        return (float) $value;
    }

    return 0.0;
}

/**
 * @return list<array<string,mixed>>|null
 */
function rdReadRowsCache(string $file, int $ttlSeconds): ?array
{
    $payload = rdReadStatsCache($file, $ttlSeconds);
    if ($payload === null) {
        return null;
    }

    $rows = [];
    foreach ($payload as $row) {
        if (is_array($row)) {
            /** @var array<string,mixed> $row */
            $rows[] = $row;
        }
    }

    return $rows;
}

/**
 * Live counters for today (clicks + leads from clickrecord, postbacks from
 * leadreport). Used by both the initial render and the ?stats=1 poll.
 *
 * @return array{clicks_today:int,leads_today:int,postbacks_today:int,payout_today:float,ts:string}
 */
function rdLiveStats(): array
{
    $cacheFile = rdStatsCacheFile('redirect_decision_live');
    $cached = rdReadStatsCache($cacheFile, RD_LIVE_STATS_CACHE_TTL);
    if ($cached !== null) {
        return rdNormalizeLiveStats($cached);
    }

    $pdo = rdPdo();
    $out = ['clicks_today' => 0, 'leads_today' => 0, 'postbacks_today' => 0, 'payout_today' => 0.0, 'ts' => gmdate('H:i:s') . ' UTC'];
    if (!$pdo instanceof PDO) {
        return $out;
    }

    try {
        $stmt = $pdo->query('SELECT COALESCE(SUM(clicks),0) c, COALESCE(SUM(CAST(leads AS UNSIGNED)),0) l FROM clickrecord WHERE click_date = CURDATE()');
        $r = $stmt !== false ? $stmt->fetch(PDO::FETCH_ASSOC) : false;
        $out['clicks_today'] = (int) (is_array($r) ? ($r['c'] ?? 0) : 0);
        $out['leads_today']  = (int) (is_array($r) ? ($r['l'] ?? 0) : 0);

        $stmt2 = $pdo->query('SELECT COUNT(*) n, COALESCE(SUM(CAST(payout AS DECIMAL(18,2))),0) p FROM leadreport WHERE conversion_date = CURDATE()');
        $p = $stmt2 !== false ? $stmt2->fetch(PDO::FETCH_ASSOC) : false;
        $out['postbacks_today'] = (int) (is_array($p) ? ($p['n'] ?? 0) : 0);
        $out['payout_today']    = (float) (is_array($p) ? ($p['p'] ?? 0) : 0);
    } catch (Throwable $e) {
        // leave zeros
    }

    rdWriteStatsCache($cacheFile, $out);

    return $out;
}

/**
 * Per-tracker totals (clickrecord grouped by click_id), highest clicks first.
 *
 * @return list<array<string,mixed>>
 */
function rdTrackers(int $limit = 20): array
{
    $limit = max(1, min($limit, 100));
    $cacheFile = rdStatsCacheFile('redirect_decision_trackers_' . $limit);
    $cached = rdReadRowsCache($cacheFile, RD_TABLE_STATS_CACHE_TTL);
    if ($cached !== null) {
        return $cached;
    }

    $pdo = rdPdo();
    if (!$pdo instanceof PDO) {
        return [];
    }

    try {
        $sql = 'SELECT click_id,
                       SUM(clicks)                        AS clicks,
                       SUM(CAST(leads AS UNSIGNED))       AS leads,
                       SUM(CAST(payout AS DECIMAL(18,2))) AS payout,
                       MAX(click_date)                    AS last_date
                FROM clickrecord
                GROUP BY click_id
                ORDER BY clicks DESC, last_date DESC
                LIMIT ' . $limit;
        $stmt = $pdo->query($sql);

        $rows = $stmt !== false ? ($stmt->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
        rdWriteStatsCache($cacheFile, $rows);

        return $rows;
    } catch (Throwable $e) {
        return [];
    }
}

/**
 * Incoming postbacks per day from leadreport (most recent first).
 *
 * @return list<array<string,mixed>>
 */
function rdDailyPostbacks(int $days = 14): array
{
    $days = max(1, min($days, 90));
    $cacheFile = rdStatsCacheFile('redirect_decision_postbacks_' . $days);
    $cached = rdReadRowsCache($cacheFile, RD_TABLE_STATS_CACHE_TTL);
    if ($cached !== null) {
        return $cached;
    }

    $pdo = rdPdo();
    if (!$pdo instanceof PDO) {
        return [];
    }

    try {
        $sql = 'SELECT conversion_date,
                       COUNT(*)                           AS postbacks,
                       SUM(CAST(payout AS DECIMAL(18,2))) AS payout
                FROM leadreport
                GROUP BY conversion_date
                ORDER BY conversion_date DESC
                LIMIT ' . $days;
        $stmt = $pdo->query($sql);

        $rows = $stmt !== false ? ($stmt->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
        rdWriteStatsCache($cacheFile, $rows);

        return $rows;
    } catch (Throwable $e) {
        return [];
    }
}

function rdHttps(): bool
{
    if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
        return true;
    }

    // Honour proxy/edge TLS-termination signals (Cloudflare, reverse proxy) so the
    // session cookie's Secure flag is set correctly when HTTPS ends at the edge.
    $proto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
    if (is_string($proto) && strtolower($proto) === 'https') {
        return true;
    }

    $cfVisitor = $_SERVER['HTTP_CF_VISITOR'] ?? '';
    if (is_string($cfVisitor) && str_contains(strtolower($cfVisitor), '"scheme":"https"')) {
        return true;
    }

    return (int) ($_SERVER['SERVER_PORT'] ?? 0) === 443;
}

function rdStartSession(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }
    session_name(RD_SESSION);
    session_set_cookie_params([
        'lifetime' => 0, 'path' => '/', 'secure' => rdHttps(),
        'httponly' => true, 'samesite' => 'Strict',
    ]);
    session_start();
}

function rdIsAuthed(): bool
{
    return !empty($_SESSION['rd_authed']);
}

function rdPasswordHashUsable(string $hash): bool
{
    if ($hash === '') {
        return false;
    }

    $info = password_get_info($hash);

    return isset($info['algo']) && $info['algo'] !== 0;
}

function rdPasswordNotConfigured(): never
{
    error_log('[redirect-decision] RD_PASSWORD_HASH missing or invalid');
    http_response_code(503);
    header('Content-Type: text/plain; charset=UTF-8');
    header('X-Content-Type-Options: nosniff');
    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
    echo 'Service unavailable';
    exit;
}

/** Render the standalone login screen (themed like the panel) and exit. */
function rdRenderLogin(string $error = ''): never
{
    $csrf  = rdCsrfToken();
    $nonce = base64_encode(random_bytes(16));
    header("Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; style-src 'nonce-$nonce'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'");
    header('Content-Type: text/html; charset=utf-8');
    ?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Redirect Decision — Login</title>
<style nonce="<?= rdEsc($nonce) ?>">
:root{
    --bg:#f7f7f7;
    --fg:#444746;
    --surface:rgba(255,255,255,.86);
    --surface-solid:#ffffff;
    --surface-soft:rgba(255,255,255,.66);
    --border:#e3e5e6;
    --line:rgba(227,229,230,.92);
    --muted:#6b6f72;
    --dark:#fc801a;
    --dark-soft:rgba(252, 128, 26,.13);
    --red:#a86b6b;
    --shadow:0 1px 0 rgba(41,43,44,.04),0 10px 30px rgba(41,43,44,.045);
    --font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;
    --radius:.3rem;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{width:100%;height:100dvh;margin:0;}
html{color-scheme:light;-webkit-text-size-adjust:100%}
::selection{background:var(--border);color:var(--fg)}
body{
    padding:clamp(46px,10vh,120px) 14px 28px;
    color:var(--fg);
    font:13px/1.5 var(--font);
    background:
        radial-gradient(circle at 50% 0,rgba(68, 71, 70,.055),transparent 34rem),
        linear-gradient(180deg,#ffffff 0,#f7f7f7 44%,#f3f4f4 100%);
    overflow-x:hidden;
    position:relative;
    text-rendering:optimizeLegibility;
    -webkit-font-smoothing:antialiased;
    -moz-osx-font-smoothing:grayscale;
}
body::before{
    content:"";
    position:fixed;
    inset:-24vmax;
    background:
        radial-gradient(ellipse at 16% 42%,rgba(227,229,230,.34) 0,rgba(227,229,230,.22) 18%,rgba(227,229,230,0) 44%),
        radial-gradient(ellipse at 72% 32%,rgba(255,255,255,.72) 0,rgba(227,229,230,.26) 20%,rgba(227,229,230,0) 48%),
        radial-gradient(ellipse at 48% 74%,rgba(227,229,230,.24) 0,rgba(247,247,247,.18) 24%,rgba(247,247,247,0) 54%),
        conic-gradient(from 90deg at 50% 50%,rgba(227,229,230,0),rgba(227,229,230,.15),rgba(255,255,255,.36),rgba(227,229,230,.12),rgba(227,229,230,0));
    background-size:86vmax 58vmax,78vmax 52vmax,92vmax 62vmax,128vmax 128vmax;
    background-position:-18vmax 18%,62vw 20%,34vw 76%,center;
    opacity:.86;
    filter:blur(34px) saturate(1.04);
    transform:translate3d(0,0,0);
    animation:rdFogDrift 34s ease-in-out infinite alternate;
    pointer-events:none;
    z-index:0;
}
body::after{
    content:"";
    position:fixed;
    inset:-18vmax;
    background:
        linear-gradient(90deg,rgba(255,255,255,.72),rgba(255,255,255,.34),rgba(255,255,255,.72)),
        radial-gradient(ellipse at 22% 24%,rgba(255,255,255,.54) 0,rgba(227,229,230,.20) 28%,rgba(227,229,230,0) 58%),
        radial-gradient(ellipse at 80% 78%,rgba(227,229,230,.28) 0,rgba(247,247,247,.22) 30%,rgba(247,247,247,0) 62%),
        radial-gradient(circle at center,rgba(227,229,230,.05) 0,rgba(247,247,247,.22) 35%,rgba(247,247,247,.82) 100%);
    background-size:100% 100%,76vmax 48vmax,82vmax 54vmax,100% 100%;
    background-position:center,left 10% top 12%,right 4% bottom 0,center;
    opacity:.94;
    filter:blur(18px);
    transform:translate3d(0,0,0);
    animation:rdFogVeil 46s ease-in-out infinite alternate;
    pointer-events:none;
    z-index:0;
}
@keyframes rdFogDrift{
    0%{transform:translate3d(-2.5%,-1.2%,0) scale(1)}
    50%{transform:translate3d(2.2%,1.6%,0) scale(1.04)}
    100%{transform:translate3d(-1.2%,2.4%,0) scale(1.02)}
}
@keyframes rdFogVeil{
    0%{transform:translate3d(1.6%,0,0) scale(1);opacity:.88}
    50%{transform:translate3d(-1.8%,1.2%,0) scale(1.03);opacity:.98}
    100%{transform:translate3d(.8%,-1.6%,0) scale(1.01);opacity:.92}
}
@media(prefers-reduced-motion:reduce){
    body::before,
    body::after{
        animation:none;
    }
}
button,input{font:inherit}
button{appearance:none}
.rd-watermark{
    position:fixed;
    inset:0;
    z-index:1;
    display:flex;
    align-items:center;
    justify-content:center;
    gap:clamp(22px,4.4vw,58px);
    pointer-events:none;
    transform:none;
    white-space:nowrap;
    user-select:none;
}
.rd-watermark::before{
    content:"";
    position:fixed;
    inset:clamp(22px,3vw,42px);
    background:
        linear-gradient(var(--dark),var(--dark)) left top/28px 4px no-repeat,
        linear-gradient(var(--dark),var(--dark)) left top/4px 28px no-repeat,
        linear-gradient(var(--dark),var(--dark)) right top/28px 4px no-repeat,
        linear-gradient(var(--dark),var(--dark)) right bottom/28px 4px no-repeat,
        linear-gradient(var(--dark),var(--dark)) right bottom/4px 28px no-repeat,
        linear-gradient(var(--dark),var(--dark)) left bottom/28px 4px no-repeat;
    opacity:.82;
}
.rd-watermark span,
.rd-watermark strong{
    display:inline-block;
    font:500 clamp(42px,7.8vw,92px)/1 var(--mono);
    letter-spacing:.42em;
    text-transform:uppercase;
    text-shadow:0 1px 0 rgba(255,255,255,.72),0 16px 40px rgba(41,43,44,.15);
}
.rd-watermark span{
    color:#111315;
    opacity:.18;
}
.rd-watermark b{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    margin:0 clamp(4px,1vw,12px);
    color:var(--dark);
    font:900 clamp(34px,6.2vw,74px)/1 var(--mono);
    letter-spacing:0;
    opacity:.30;
}
.rd-watermark strong{
    color:var(--dark);
    opacity:.26;
}
.rd-card{
    position:relative;
    z-index:1;
    width:min(94vw,440px);
    margin:0 auto;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:var(--surface);
    box-shadow:var(--shadow);
    overflow:hidden;
    backdrop-filter:blur(12px);
    -webkit-backdrop-filter:blur(12px);
}
.rd-card-heading{
    min-height:50px;
    display:flex;
    align-items:center;
    padding:10px 11px;
    border-bottom:1px solid var(--line);
    background:rgba(247,247,247,.92);
    color:var(--fg);
    font-size:12px;
    font-weight:900;
    letter-spacing:.04em;
    text-transform:uppercase;
}
.rd-card-body{padding:14px}
label{
    display:block;
    margin:0 0 6px;
    color:var(--muted);
    font-size:10px;
    font-weight:800;
    letter-spacing:.12em;
    text-transform:uppercase;
}
.rd-control{
    width:100%;
    min-height:34px;
    border:1px solid rgba(227,229,230,.98);
    border-radius:var(--radius);
    background:rgba(255,255,255,.86);
    color:var(--fg);
    font:12px/1.2 var(--font);
    font-weight:700;
    outline:0;
    padding:8px 9px;
}
.rd-control:focus{
    border-color:rgba(68, 71, 70, 1);
    background:#fff;
}
.rd-btn{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    gap:7px;
    width:100%;
    min-height:34px;
    margin-top:12px;
    border:1px solid var(--fg);
    border-radius:var(--radius);
    background:var(--fg);
    color:var(--bg);
    padding:8px 11px;
    font:11px/1 var(--font);
    font-weight:900;
    text-transform:uppercase;
    letter-spacing:.04em;
    cursor:pointer;
    text-decoration:none;
    user-select:none;
}
.rd-btn:hover{filter:contrast(1.05)}
.rd-btn:focus-visible{outline:2px solid rgba(68, 71, 70, 1);outline-offset:2px}
.rd-error{
    margin:0 0 10px;
    padding:9px 10px;
    border:1px solid rgba(168,107,107,.42);
    border-radius:var(--radius);
    background:rgba(255,255,255,.84);
    color:var(--red);
    font-size:12px;
    overflow-wrap:anywhere;
}
@media(max-width:560px){
    body{padding:54px 9px 24px}
    .rd-card-body{padding:12px}
    .rd-card-heading{min-height:46px}
}
body::before{content:'NGIX\2022 XCTD';position:fixed;top:12px;left:14px;z-index:0;font:600 11px/1 Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;letter-spacing:.24em;text-transform:uppercase;color:rgba(68,71,70,.06);pointer-events:none}body::after{content:'';position:fixed;inset:0;z-index:0;pointer-events:none;background-image:url('/assets/favicon.svg'),url('/assets/bg.png');background-repeat:no-repeat,no-repeat;background-position:right 14px bottom 12px,center center;background-size:28px 28px,cover;opacity:.28;filter:saturate(.68) contrast(.84) brightness(1.03) blur(.32px);transform:translateZ(0)}
</style>
</head>
<body>
  <div class="rd-watermark" aria-hidden="true"><span>NGIX</span><b>•</b><strong>XCTD</strong></div>
  <form class="rd-card" method="post" autocomplete="off">
    <div class="rd-card-heading">Redirect Decision</div>
    <div class="rd-card-body">
      <?php if ($error !== '') : ?>
        <p class="rd-error"><?= rdEsc($error) ?></p>
      <?php endif; ?>
      <label for="pw">Password</label>
      <input class="rd-control" id="pw" name="password" type="password" autofocus required>
      <input type="hidden" name="action" value="login">
      <input type="hidden" name="csrf_token" value="<?= rdEsc($csrf) ?>">
      <button class="rd-btn" type="submit">Masuk</button>
    </div>
  </form>
<style nonce="<?= rdEsc($nonce) ?>">
/* NGIX patch: fullscreen background/backdrop override */
html,body{min-height:100svh!important}
body{background-color:var(--bg,#f8fafc)!important;background-attachment:fixed!important}
body::after{content:""!important;position:fixed!important;inset:0!important;width:100vw!important;min-width:100vw!important;height:100vh!important;height:100dvh!important;z-index:0!important;pointer-events:none!important;background-image:url('/assets/favicon.svg'),url('/assets/bg.png')!important;background-repeat:no-repeat,no-repeat!important;background-position:right 14px bottom 12px,center center!important;background-size:28px 28px,cover!important;opacity:.30!important;filter:saturate(.68) contrast(.84) brightness(1.03) blur(.32px)!important;transform:translateZ(0)!important}
body>.container,body>.app-shell,body>.navbar,body>.panel,body>.panel_m,main,.container,.navbar,.panel,.panel_m,.app-footer{position:relative;z-index:1}
.panel_m.panel>.ngix-image-backdrop{position:fixed!important;inset:0!important;width:100vw!important;min-width:100vw!important;height:100vh!important;height:100dvh!important;z-index:0!important;pointer-events:none!important;background-repeat:no-repeat,no-repeat!important;background-position:center center,center center!important;background-size:100% 100%,cover!important;opacity:.40!important;filter:saturate(.72) contrast(.86) blur(.22px)!important;transform:translateZ(0)!important}
.panel_m.panel>.panel-heading,.panel_m.panel>.panel-body{position:relative!important;z-index:2!important}
@media (prefers-color-scheme:dark){body::after{opacity:.24!important;filter:saturate(.62) contrast(.78) brightness(.88) blur(.34px)!important}.panel_m.panel>.ngix-image-backdrop{opacity:.34!important;filter:saturate(.66) contrast(.80) blur(.24px)!important}}
@media screen and (max-width:768px){body::after{background-position:right 10px bottom 10px,center center!important;background-size:24px 24px,cover!important;opacity:.24!important}.panel_m.panel>.ngix-image-backdrop{background-size:100% 100%,cover!important;opacity:.30!important}}

/* NGIX patch: viewport/navbar correction */
html{width:100%!important;min-width:100%!important;max-width:none!important;min-height:100%!important;margin:0!important;padding:0!important;overflow-x:hidden!important;background:var(--bg,#f8fafc)!important;zoom:1!important}
body{width:100%!important;min-width:100vw!important;max-width:none!important;min-height:100vh!important;min-height:100svh!important;min-height:100dvh!important;margin:0!important;overflow-x:hidden!important;background-color:var(--bg,#f8fafc)!important;background-attachment:fixed!important}
body::after{top:0!important;right:0!important;bottom:0!important;left:0!important;inset:0!important;width:100vw!important;min-width:100vw!important;max-width:none!important;height:100vh!important;height:100svh!important;height:100dvh!important;background-size:28px 28px,cover!important;background-position:right 14px bottom 12px,center center!important}
.navbar.navbar-fixed-top,.navbar-fixed-top{position:fixed!important;top:0!important;right:0!important;left:0!important;width:100vw!important;max-width:none!important;margin:0!important;border-radius:0!important;transform:none!important;z-index:1055!important}
body>.navbar.navbar-fixed-top{position:fixed!important;z-index:1055!important}.navbar.navbar-default.navbar-fixed-top{top:0!important}
.panel_m.panel{overflow:visible!important;isolation:auto!important}.panel_m.panel>.ngix-image-backdrop,.ngix-image-backdrop{position:fixed!important;top:0!important;right:0!important;bottom:0!important;left:0!important;width:100vw!important;min-width:100vw!important;max-width:none!important;height:100vh!important;height:100svh!important;height:100dvh!important;z-index:0!important;background-size:100% 100%,cover!important;background-position:center center,center center!important;pointer-events:none!important}.panel_m.panel>.panel-heading,.panel_m.panel>.panel-body,.panel_m.panel>.table-responsive{position:relative!important;z-index:2!important}
@supports (height:100lvh){body{min-height:100lvh!important}body::after,.panel_m.panel>.ngix-image-backdrop,.ngix-image-backdrop{height:100lvh!important;min-height:100lvh!important}}
@media screen and (max-width:768px){html{zoom:1!important}.navbar.navbar-fixed-top,.navbar-fixed-top{top:0!important;width:100vw!important}.panel_m.panel>.ngix-image-backdrop,.ngix-image-backdrop{width:100vw!important;height:100svh!important;height:100dvh!important;min-height:100svh!important;background-size:100% 100%,cover!important}}
</style>
<style>
/* NGIX patch: disable image/background right-click */
img,picture,svg,canvas,.ngix-image-backdrop,.image-protect,.app-banner,.brand-logo,.logo{
  -webkit-user-drag:none!important;
  user-drag:none!important;
  -webkit-touch-callout:none!important;
  -webkit-user-select:none!important;
  user-select:none!important;
}
</style>
<script>
/* NGIX patch: disable image/background right-click */
(function(){
    'use strict';

    var protectedSelector = 'img,picture,svg,canvas,.ngix-image-backdrop,.image-protect,.app-banner,.brand-logo,.logo';
    var editableSelector = 'input,textarea,select,[contenteditable="true"],[contenteditable=""]';

    function closest(node, selector) {
        return node && typeof node.closest === 'function' ? node.closest(selector) : null;
    }

    function hasProtectedBackground(node) {
        var current = node && node.nodeType === 1 ? node : null;

        while (current && current !== document.documentElement) {
            if (current.classList && (
                current.classList.contains('ngix-image-backdrop')
                || current.classList.contains('image-protect')
                || current.classList.contains('app-banner')
                || current.classList.contains('brand-logo')
            )) {
                return true;
            }

            var backgroundImage = window.getComputedStyle(current).backgroundImage;
            if (backgroundImage && backgroundImage !== 'none' && backgroundImage.indexOf('url(') !== -1) {
                return true;
            }

            if (current === document.body) {
                break;
            }

            current = current.parentElement;
        }

        return false;
    }

    function isProtectedTarget(node) {
        if (!node || closest(node, editableSelector)) {
            return false;
        }

        return !!closest(node, protectedSelector) || hasProtectedBackground(node);
    }

    function blockImageAction(event) {
        if (!isProtectedTarget(event.target)) {
            return;
        }

        event.preventDefault();
        event.stopPropagation();
    }

    function hardenImages() {
        document.querySelectorAll('img,picture,svg,canvas').forEach(function(node) {
            node.setAttribute('draggable', 'false');
        });
    }

    document.addEventListener('contextmenu', blockImageAction, true);
    document.addEventListener('dragstart', blockImageAction, true);

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', hardenImages, {once: true});
        return;
    }

    hardenImages();
}());
</script>
</body>
</html>
    <?php
    exit;
}

function rdEsc(string $v): string
{
    return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}

function rdCsrfToken(): string
{
    if (!isset($_SESSION['csrf'][RD_CSRF_NS]) || !is_string($_SESSION['csrf'][RD_CSRF_NS])) {
        $_SESSION['csrf'][RD_CSRF_NS] = bin2hex(random_bytes(32));
    }

    return $_SESSION['csrf'][RD_CSRF_NS];
}

function rdValidCsrf(?string $t): bool
{
    $s = $_SESSION['csrf'][RD_CSRF_NS] ?? null;

    return is_string($t) && is_string($s) && hash_equals($s, $t);
}

/** Current timing: [filterSeconds, cycleSeconds]. */
function rdCurrentTiming(): array
{
    $file = rdCacheDir() . DIRECTORY_SEPARATOR . 'filter_timing.json';
    if (is_file($file)) {
        $d = json_decode((string) file_get_contents($file, false, null, 0, 256), true);
        if (is_array($d)) {
            $f = (int) ($d['filter_seconds'] ?? -1);
            $c = (int) ($d['cycle_seconds'] ?? -1);
            if ($c >= 60 && $c <= 86400 && $f >= 0 && $f <= $c) {
                return [$f, $c];
            }
        }
    }

    $ef = (int) app_env('SRP_FILTER_SECONDS', '-1');
    $ec = (int) app_env('SRP_CYCLE_SECONDS', '-1');
    if ($ec >= 60 && $ec <= 86400 && $ef >= 0 && $ef <= $ec) {
        return [$ef, $ec];
    }

    return [RD_DEF_FILTER, RD_DEF_CYCLE];
}

function rdCurrentFilterUrl(): string
{
    $file = rdCacheDir() . DIRECTORY_SEPARATOR . 'filter_url.txt';
    if (is_file($file)) {
        $u = trim((string) file_get_contents($file, false, null, 0, 2048));
        if ($u !== '') {
            return $u;
        }
    }

    return (string) app_env('SRP_FILTER_URL', '');
}

/** Serialize a value for .env: integers plain, everything else double-quoted. */
function rdEnvValue(string $v): string
{
    if ($v === '') {
        return '""';
    }
    if (preg_match('/^-?\d+$/', $v) === 1) {
        return $v;
    }

    return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $v) . '"';
}

/**
 * Atomically update keys in the project .env — flock + timestamped backup.
 * Existing keys are replaced in place; new keys appended.
 *
 * @param array<string,string> $kv
 */
function rdWriteEnv(array $kv): bool
{
    $envFile  = dirname(__DIR__) . DIRECTORY_SEPARATOR . '.env';
    $lockFile = $envFile . '.lock';

    $fp = @fopen($lockFile, 'c');
    if ($fp === false || !flock($fp, LOCK_EX)) {
        if ($fp !== false) {
            fclose($fp);
        }

        return false;
    }

    $content = is_file($envFile) ? (string) file_get_contents($envFile) : '';

    $backupDir = dirname($envFile) . DIRECTORY_SEPARATOR . '.env-backups';
    if (is_dir($backupDir) || @mkdir($backupDir, 0700, true) || is_dir($backupDir)) {
        @file_put_contents($backupDir . DIRECTORY_SEPARATOR . '.env.' . gmdate('Ymd_His') . '.bak', $content);
    }

    $lines = $content === '' ? [] : (preg_split('/\r\n|\r|\n/', $content) ?: []);
    $seen  = [];
    foreach ($lines as $i => $line) {
        if (preg_match('/^\s*(?:export\s+)?([A-Z0-9_]+)\s*=/', $line, $m) && array_key_exists($m[1], $kv)) {
            $lines[$i] = $m[1] . '=' . rdEnvValue($kv[$m[1]]);
            $seen[$m[1]] = true;
        }
    }
    foreach ($kv as $k => $v) {
        if (empty($seen[$k])) {
            $lines[] = $k . '=' . rdEnvValue($v);
        }
    }

    $ok = @file_put_contents($envFile, rtrim(implode("\n", $lines), "\n") . "\n") !== false;

    flock($fp, LOCK_UN);
    fclose($fp);

    return $ok;
}

function rdJson(int $code, array $payload): never
{
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($payload, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR);
    exit;
}

function rdClearSession(): void
{
    $_SESSION = [];

    if (ini_get('session.use_cookies')) {
        $params = session_get_cookie_params();
        setcookie(
            (string) session_name(),
            '',
            [
                'expires' => time() - 42000,
                'path' => (string) ($params['path'] ?? '/'),
                'domain' => (string) ($params['domain'] ?? ''),
                'secure' => (bool) ($params['secure'] ?? rdHttps()),
                'httponly' => true,
                'samesite' => 'Strict',
            ],
        );
    }

    session_destroy();
}

function rdUnauthorized(): never
{
    rdClearSession();
    rdJson(401, ['ok' => false, 'err' => 'unauthorized', 'logout' => true]);
}

function rdLogout(): never
{
    rdClearSession();
    header('Location: ' . strtok((string) ($_SERVER['REQUEST_URI'] ?? '/redirect-decision.php'), '?'));
    exit;
}

rdStartSession();

if (!rdPasswordHashUsable((string) $rdPasswordHash)) {
    rdPasswordNotConfigured();
}

header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: same-origin');
header('Permissions-Policy: accelerometer=(), autoplay=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');

// ── Logout ──────────────────────────────────────────────────────────────────
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && ($_POST['action'] ?? '') === 'logout') {
    if (!rdIsAuthed()) {
        rdUnauthorized();
    }

    if (!rdValidCsrf($_POST['csrf_token'] ?? null)) {
        http_response_code(403);
        header('Content-Type: text/plain; charset=utf-8');
        echo 'Invalid CSRF token.';
        exit;
    }

    rdLogout();
}

// ── Login (standalone password gate) ────────────────────────────────────────
if (!rdIsAuthed() && ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && ($_POST['action'] ?? '') === 'login') {
    // Throttle on the strongest of two signals: per-session counter AND a per-IP
    // file counter (login_throttle.php). The IP counter survives a dropped session
    // cookie, so clearing cookies can no longer reset the lockout.
    $sessionFails = isset($_SESSION['rd_fails']) && is_int($_SESSION['rd_fails']) ? $_SESSION['rd_fails'] : 0;
    $fails = max($sessionFails, srp_login_throttle_state('rd_login')['fails']);
    if ($fails >= RD_MAX_FAILS) {
        rdRenderLogin('Terlalu banyak percobaan. Tutup browser / tunggu sesi baru.');
    }
    if (!rdValidCsrf($_POST['csrf_token'] ?? null)) {
        rdRenderLogin('Sesi kedaluwarsa, coba lagi.');
    }
    if (password_verify((string) ($_POST['password'] ?? ''), $rdPasswordHash)) {
        session_regenerate_id(true);
        $_SESSION['rd_authed'] = true;
        unset($_SESSION['rd_fails']);
        srp_login_throttle_reset('rd_login');
        header('Location: ' . strtok((string) ($_SERVER['REQUEST_URI'] ?? '/redirect-decision.php'), '?'));
        exit;
    }
    $_SESSION['rd_fails'] = $sessionFails + 1;
    srp_login_throttle_register_fail('rd_login');
    rdRenderLogin('Password salah.');
}

// ── Gate everything below behind the panel session ──────────────────────────
if (!rdIsAuthed()) {
    if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
        rdUnauthorized();
    }
    rdRenderLogin();
}

// ── Live stats poll ─────────────────────────────────────────────────────────
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'GET' && isset($_GET['stats'])) {
    rdJson(200, ['ok' => true] + rdLiveStats());
}

// ── Save ────────────────────────────────────────────────────────────────────
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
    if (!rdValidCsrf($_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_POST['csrf_token'] ?? null))) {
        rdJson(419, ['ok' => false, 'err' => 'invalid-csrf']);
    }

    $filterMin = (int) ($_POST['filter_min'] ?? -1);
    $normalMin = (int) ($_POST['normal_min'] ?? -1);
    $filterUrl = trim((string) ($_POST['filter_url'] ?? ''));

    // filter 0–1440 min, normal 0–1440 min, cycle must be ≥ 1 min.
    if ($filterMin < 0 || $filterMin > 1440 || $normalMin < 0 || $normalMin > 1440 || ($filterMin + $normalMin) < 1) {
        rdJson(422, ['ok' => false, 'err' => 'invalid-timing']);
    }

    if ($filterUrl !== '') {
        $okUrl = filter_var($filterUrl, FILTER_VALIDATE_URL) !== false
            && strtolower((string) parse_url($filterUrl, PHP_URL_SCHEME)) === 'https';
        if (!$okUrl) {
            rdJson(422, ['ok' => false, 'err' => 'invalid-filter-url (must be https)']);
        }
    }

    $dir = rdCacheDir();
    if (!is_dir($dir) && !@mkdir($dir, 0700, true) && !is_dir($dir)) {
        rdJson(500, ['ok' => false, 'err' => 'cache-dir-unwritable']);
    }

    $timing = [
        'filter_seconds' => $filterMin * 60,
        'cycle_seconds'  => ($filterMin + $normalMin) * 60,
    ];
    $wrote = @file_put_contents(
        $dir . DIRECTORY_SEPARATOR . 'filter_timing.json',
        json_encode($timing, JSON_THROW_ON_ERROR),
        LOCK_EX,
    );
    if ($wrote === false) {
        rdJson(500, ['ok' => false, 'err' => 'write-failed']);
    }

    // Empty filter URL → remove the override (engine falls back to env/null).
    $urlFile = $dir . DIRECTORY_SEPARATOR . 'filter_url.txt';
    if ($filterUrl === '') {
        @unlink($urlFile);
    } else {
        @file_put_contents($urlFile, $filterUrl, LOCK_EX);
    }

    // Persist to .env as the durable source of truth so the setting survives the
    // 30-day cache cleanup (engine precedence: file → env → const).
    $envSynced = rdWriteEnv([
        'SRP_FILTER_URL'     => $filterUrl,
        'SRP_FILTER_SECONDS' => (string) $timing['filter_seconds'],
        'SRP_CYCLE_SECONDS'  => (string) $timing['cycle_seconds'],
    ]);

    rdJson(200, [
        'ok' => true,
        'filter_seconds' => $timing['filter_seconds'],
        'cycle_seconds'  => $timing['cycle_seconds'],
        'filter_url'     => $filterUrl,
        'env_synced'     => $envSynced,
    ]);
}

// ── Render ──────────────────────────────────────────────────────────────────
[$curFilter, $curCycle] = rdCurrentTiming();
$curFilterMin = (int) round($curFilter / 60);
$curNormalMin = (int) round(($curCycle - $curFilter) / 60);
$curUrl       = rdCurrentFilterUrl();
$live         = rdLiveStats();
$trackers     = rdTrackers(20);
$daily        = rdDailyPostbacks(14);
$dbUp         = rdPdo() instanceof PDO;
$csrf         = rdCsrfToken();
$nonce        = base64_encode(random_bytes(16));
header("Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; style-src 'nonce-$nonce'; script-src 'nonce-$nonce'; connect-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'");
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="<?= rdEsc($csrf) ?>">
<title>Redirect Decision</title>
<style nonce="<?= rdEsc($nonce) ?>">
:root{
    --bg:#f7f7f7;
    --fg:#444746;
    --surface:rgba(255,255,255,.84);
    --surface-solid:#ffffff;
    --surface-soft:rgba(255,255,255,.64);
    --border:#e3e5e6;
    --line:rgba(227,229,230,.92);
    --muted:#6b6f72;
    --dark:#fc801a;
    --dark-soft:rgba(68, 71, 70,.12);
    --green:#5c8a6b;
    --blue:#6d86a8;
    --red:#a86b6b;
    --shadow:0 1px 0 rgba(41,43,44,.04),0 10px 30px rgba(41,43,44,.045);
    --font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;
    --mono:"SFMono-Regular",Consolas,"Liberation Mono",monospace;
    --radius:.3rem;
    --top-offset:14px;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{width:100%;height:100dvh;margin:0;}
html{color-scheme:light;scroll-behavior:smooth;-webkit-text-size-adjust:100%}
::selection{background:var(--border);color:var(--fg)}
::-moz-selection{background:var(--border);color:var(--fg)}
body{
    padding:var(--top-offset) 12px 34px;
    background:
        radial-gradient(circle at 50% 0,rgba(68, 71, 70,.055),transparent 34rem),
        linear-gradient(180deg,#ffffff 0,#f7f7f7 44%,#f3f4f4 100%);
    color:var(--fg);
    font:13px/1.5 var(--font);
    overflow-x:hidden;
    position:relative;
    text-rendering:optimizeLegibility;
    -webkit-font-smoothing:antialiased;
    -moz-osx-font-smoothing:grayscale;
}
body::before{
    content:"";
    position:fixed;
    inset:-24vmax;
    background:
        radial-gradient(ellipse at 16% 42%,rgba(227,229,230,.34) 0,rgba(227,229,230,.22) 18%,rgba(227,229,230,0) 44%),
        radial-gradient(ellipse at 72% 32%,rgba(255,255,255,.72) 0,rgba(227,229,230,.26) 20%,rgba(227,229,230,0) 48%),
        radial-gradient(ellipse at 48% 74%,rgba(227,229,230,.24) 0,rgba(247,247,247,.18) 24%,rgba(247,247,247,0) 54%),
        conic-gradient(from 90deg at 50% 50%,rgba(227,229,230,0),rgba(227,229,230,.15),rgba(255,255,255,.36),rgba(227,229,230,.12),rgba(227,229,230,0));
    background-size:86vmax 58vmax,78vmax 52vmax,92vmax 62vmax,128vmax 128vmax;
    background-position:-18vmax 18%,62vw 20%,34vw 76%,center;
    opacity:.86;
    filter:blur(34px) saturate(1.04);
    transform:translate3d(0,0,0);
    animation:rdFogDrift 34s ease-in-out infinite alternate;
    pointer-events:none;
    z-index:0;
}
body::after{
    content:"";
    position:fixed;
    inset:-18vmax;
    background:
        linear-gradient(90deg,rgba(255,255,255,.72),rgba(255,255,255,.34),rgba(255,255,255,.72)),
        radial-gradient(ellipse at 22% 24%,rgba(255,255,255,.54) 0,rgba(227,229,230,.20) 28%,rgba(227,229,230,0) 58%),
        radial-gradient(ellipse at 80% 78%,rgba(227,229,230,.28) 0,rgba(247,247,247,.22) 30%,rgba(247,247,247,0) 62%),
        radial-gradient(circle at center,rgba(227,229,230,.05) 0,rgba(247,247,247,.22) 35%,rgba(247,247,247,.82) 100%);
    background-size:100% 100%,76vmax 48vmax,82vmax 54vmax,100% 100%;
    background-position:center,left 10% top 12%,right 4% bottom 0,center;
    opacity:.94;
    filter:blur(18px);
    transform:translate3d(0,0,0);
    animation:rdFogVeil 46s ease-in-out infinite alternate;
    pointer-events:none;
    z-index:0;
}
@keyframes rdFogDrift{
    0%{transform:translate3d(-2.5%,-1.2%,0) scale(1)}
    50%{transform:translate3d(2.2%,1.6%,0) scale(1.04)}
    100%{transform:translate3d(-1.2%,2.4%,0) scale(1.02)}
}
@keyframes rdFogVeil{
    0%{transform:translate3d(1.6%,0,0) scale(1);opacity:.88}
    50%{transform:translate3d(-1.8%,1.2%,0) scale(1.03);opacity:.98}
    100%{transform:translate3d(.8%,-1.6%,0) scale(1.01);opacity:.92}
}
@media(prefers-reduced-motion:reduce){
    body::before,
    body::after{
        animation:none;
    }
}
a{color:inherit}
button,input{font:inherit}
button{appearance:none}
.rd-watermark{
    position:fixed;
    inset:0;
    z-index:1;
    display:flex;
    align-items:center;
    justify-content:center;
    gap:clamp(22px,4.4vw,58px);
    pointer-events:none;
    transform:none;
    white-space:nowrap;
    user-select:none;
}
.rd-watermark::before{
    content:"";
    position:fixed;
    inset:clamp(22px,3vw,42px);
    background:
        linear-gradient(var(--dark),var(--dark)) left top/28px 4px no-repeat,
        linear-gradient(var(--dark),var(--dark)) left top/4px 28px no-repeat,
        linear-gradient(var(--dark),var(--dark)) right top/28px 4px no-repeat,
        linear-gradient(var(--dark),var(--dark)) right bottom/28px 4px no-repeat,
        linear-gradient(var(--dark),var(--dark)) right bottom/4px 28px no-repeat,
        linear-gradient(var(--dark),var(--dark)) left bottom/28px 4px no-repeat;
    opacity:.82;
}
.rd-watermark span,
.rd-watermark strong{
    display:inline-block;
    font:500 clamp(42px,7.8vw,92px)/1 var(--mono);
    letter-spacing:.42em;
    text-transform:uppercase;
    text-shadow:0 1px 0 rgba(255,255,255,.72),0 16px 40px rgba(41,43,44,.15);
}
.rd-watermark span{
    color:#111315;
    opacity:.18;
}
.rd-watermark b{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    margin:0 clamp(4px,1vw,12px);
    color:var(--dark);
    font:900 clamp(34px,6.2vw,74px)/1 var(--mono);
    letter-spacing:0;
    opacity:.30;
}
.rd-watermark strong{
    color:var(--dark);
    opacity:.26;
}
.rd-shell{
    position:relative;
    z-index:2;
    width:min(100%,980px);
    margin:0 auto;
}
.rd-tabs{
    position:sticky;
    top:10px;
    z-index:14;
    display:flex;
    align-items:center;
    gap:6px;
    width:100%;
    min-height:48px;
    margin:0 0 12px;
    padding:7px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:rgba(247,247,247,.94);
    box-shadow:var(--shadow);
    overflow-x:auto;
    overscroll-behavior-x:contain;
    scrollbar-width:thin;
    backdrop-filter:blur(14px);
    -webkit-backdrop-filter:blur(14px);
}
.rd-tab{
    appearance:none;
    -webkit-appearance:none;
    flex:0 0 auto;
    min-height:32px;
    padding:8px 11px;
    border:1px solid transparent;
    border-radius:var(--radius);
    background:transparent;
    color:var(--muted);
    font:11px/1 var(--font);
    font-weight:900;
    text-transform:uppercase;
    letter-spacing:.04em;
    white-space:nowrap;
    cursor:pointer;
    outline:0;
    transition:border-color .15s ease,background .15s ease,color .15s ease,transform .15s ease;
}
.rd-tab:hover{
    border-color:rgba(227,229,230,.98);
    background:rgba(255,255,255,.78);
    color:var(--fg);
    transform:translateY(-1px);
}
.rd-tab:focus-visible{outline:2px solid rgba(68, 71, 70, 1);outline-offset:2px}
.rd-tab.is-active{
    border-color:rgba(68, 71, 70,.45);
    background:rgba(255,255,255,.94);
    color:var(--fg);
}
.rd-logout{
    appearance:none;
    -webkit-appearance:none;
    flex:0 0 auto;
    margin-left:auto;
    min-height:32px;
    padding:9px 8px;
    border:0;
    background:transparent;
    color:var(--muted);
    font-size:10px;
    font-weight:800;
    letter-spacing:.06em;
    line-height:1;
    text-decoration:none;
    text-transform:uppercase;
    white-space:nowrap;
    cursor:pointer;
}
.rd-logout:hover{color:var(--red)}
.rd-logout-form{
    flex:0 0 auto;
    margin-left:auto;
}
.rd-logout-form .rd-logout{
    margin-left:0;
}
.rd-pane{display:none}
.rd-pane.is-active{display:block}
.rd-card{
    width:100%;
    margin-bottom:12px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:var(--surface);
    box-shadow:var(--shadow);
    overflow:hidden;
    backdrop-filter:blur(12px);
    -webkit-backdrop-filter:blur(12px);
}
.rd-card-heading{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:8px;
    min-height:50px;
    padding:10px 11px;
    border-bottom:1px solid var(--line);
    background:rgba(247,247,247,.92);
    color:var(--fg);
    font-size:12px;
    font-weight:900;
    letter-spacing:.04em;
    text-transform:uppercase;
}
.rd-card-body{padding:12px 13px}
.rd-card-body.rd-pad0{padding:0;overflow-x:auto}
.rd-sub{
    margin:0 0 12px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:rgba(255,255,255,.72);
    padding:9px 10px;
    color:var(--muted);
    font-size:12px;
    line-height:1.55;
    overflow-wrap:anywhere;
}
code{
    font-family:var(--mono);
    font-size:11px;
    color:var(--fg);
    background:rgba(247,247,247,.72);
    border:1px solid rgba(227,229,230,.88);
    border-radius:var(--radius);
    padding:1px 4px;
}
label{
    display:block;
    margin:0 0 6px;
    color:var(--muted);
    font-size:10px;
    font-weight:800;
    letter-spacing:.12em;
    text-transform:uppercase;
}
.rd-row{
    display:grid;
    grid-template-columns:repeat(2,minmax(0,1fr));
    gap:10px;
    margin-bottom:12px;
}
.rd-col{min-width:0}
.rd-control{
    width:100%;
    min-height:34px;
    border:1px solid rgba(227,229,230,.98);
    border-radius:var(--radius);
    background:rgba(255,255,255,.86);
    color:var(--fg);
    font:12px/1.2 var(--font);
    font-weight:700;
    font-variant-numeric:tabular-nums;
    outline:0;
    padding:8px 9px;
}
.rd-control:focus{
    border-color:rgba(68, 71, 70, 1);
    background:#fff;
}
input[type=url].rd-control{font-size:12px;overflow-wrap:anywhere}
.rd-bar{
    display:flex;
    height:34px;
    margin:7px 0 7px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:rgba(255,255,255,.72);
    overflow:hidden;
}
.rd-bar>div{
    display:flex;
    align-items:center;
    justify-content:center;
    min-width:0;
    padding:0 8px;
    overflow:hidden;
    font-size:11px;
    font-weight:900;
    text-overflow:ellipsis;
    text-transform:uppercase;
    white-space:nowrap;
}
.seg-f{background:var(--fg);color:var(--bg)}
.seg-n{background:rgba(247,247,247,.72);color:var(--fg)}
.rd-legend{
    display:flex;
    flex-wrap:wrap;
    gap:8px 16px;
    margin:0 0 12px;
    color:var(--muted);
    font-size:11px;
}
.rd-live-mode{
    display:flex;
    align-items:center;
    gap:9px;
    margin-bottom:12px;
    flex-wrap:wrap;
}
.rd-badge{
    display:inline-flex;
    align-items:center;
    min-height:24px;
    padding:5px 8px;
    border:1px solid rgba(227,229,230,.88);
    border-radius:var(--radius);
    background:rgba(247,247,247,.72);
    color:rgba(41,43,44,.72);
    font-size:9px;
    font-weight:900;
    letter-spacing:.06em;
    text-transform:uppercase;
    white-space:nowrap;
}
.rd-badge--filter{background:var(--fg);border-color:var(--fg);color:var(--bg)}
.rd-badge--normal{background:rgba(255,255,255,.86);border-color:rgba(68, 71, 70,.42);color:var(--fg)}
.rd-badge--off{background:rgba(247,247,247,.72);color:var(--muted)}
.rd-count{font:700 12px var(--mono);color:var(--muted)}
.rd-dot{
    display:inline-block;
    width:9px;
    height:9px;
    margin-right:5px;
    border-radius:2px;
    vertical-align:middle;
}
.rd-dot--f{background:var(--fg)}
.rd-dot--n{background:rgba(247,247,247,.72);border:1px solid var(--line)}
.rd-hint{
    margin:7px 0 0;
    color:var(--muted);
    font-size:11.5px;
    line-height:1.55;
    overflow-wrap:anywhere;
}
.rd-actions{
    display:flex;
    justify-content:flex-end;
    margin-top:14px;
}
.rd-btn{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    gap:7px;
    min-width:96px;
    min-height:34px;
    border:1px solid rgba(227,229,230,.98);
    border-radius:var(--radius);
    background:rgba(255,255,255,.78);
    color:var(--fg);
    padding:8px 11px;
    font:11px/1 var(--font);
    font-weight:900;
    text-transform:uppercase;
    letter-spacing:.04em;
    cursor:pointer;
    text-decoration:none;
    user-select:none;
    white-space:nowrap;
}
.rd-btn:hover{filter:contrast(1.05)}
.rd-btn:focus-visible{outline:2px solid rgba(68, 71, 70, 1);outline-offset:2px}
.rd-btn-primary{background:var(--fg);border-color:var(--fg);color:var(--bg)}
.rd-btn-primary:hover{filter:contrast(1.12)}
.rd-btn:disabled{opacity:.65;cursor:not-allowed}
.rd-status{
    min-height:18px;
    margin-top:10px;
    font-size:12px;
    overflow-wrap:anywhere;
}
.rd-status.ok{color:var(--green)}
.rd-status.bad{color:var(--red)}
.rd-pad0{padding:0}
.rd-ts{
    margin-left:auto;
    color:var(--muted);
    font-family:var(--mono);
    font-size:11px;
    font-weight:400;
    text-transform:none;
    white-space:nowrap;
}
.rd-stat-grid{
    display:grid;
    grid-template-columns:repeat(4,minmax(0,1fr));
    gap:7px;
}
.rd-stat{
    min-width:0;
    padding:9px;
    border:1px solid var(--line);
    border-radius:var(--radius);
    background:rgba(255,255,255,.68);
    text-align:left;
}
.rd-stat-num{
    display:block;
    overflow:hidden;
    color:var(--fg);
    font:900 18px/1.1 var(--mono);
    text-overflow:ellipsis;
    white-space:nowrap;
}
.rd-stat-lbl{
    display:block;
    margin-top:4px;
    color:var(--muted);
    font-size:9px;
    font-weight:900;
    letter-spacing:.08em;
    text-transform:uppercase;
}
.rd-table{
    width:100%;
    min-width:760px;
    border-collapse:collapse;
    font-size:12px;
}
.rd-table th{
    position:sticky;
    top:0;
    z-index:1;
    padding:8px 10px;
    background:rgba(247,247,247,.96);
    border-bottom:1px solid var(--line);
    color:var(--muted);
    font-size:10px;
    font-weight:900;
    letter-spacing:.08em;
    text-align:left;
    text-transform:uppercase;
    white-space:nowrap;
}
.rd-table td{
    padding:8px 10px;
    border-top:1px solid rgba(227,229,230,.72);
    color:var(--fg);
    vertical-align:middle;
    white-space:nowrap;
}
.rd-table tbody tr:hover td{background:rgba(255,255,255,.70)}
.rd-r{text-align:right}
.rd-mono{font-family:var(--mono);font-size:11.5px}
.rd-empty{padding:16px 10px;color:var(--muted);text-align:center}
@media(max-width:780px){
    .rd-shell{width:100%}
    .rd-tabs{top:8px}
    .rd-stat-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(max-width:560px){
    :root{--top-offset:9px}
    body{padding:9px 8px 24px}
    .rd-tabs{position:relative;top:auto;margin-bottom:9px}
    .rd-tab{padding:8px 10px}
    .rd-card-heading{align-items:flex-start;flex-direction:column;gap:2px;min-height:48px}
    .rd-card-body{padding:11px}
    .rd-row{grid-template-columns:1fr}
    .rd-actions{display:block}
    .rd-btn{width:100%}
    .rd-stat-grid{grid-template-columns:1fr}
    .rd-ts{margin-left:0}
    .rd-watermark{gap:clamp(10px,2vw,20px);align-items:center}
    .rd-watermark::before{inset:18px;background-size:22px 3px,3px 22px,22px 3px,22px 3px,3px 22px,22px 3px}
    .rd-watermark span,
    .rd-watermark strong{font-size:clamp(28px,9vw,50px);letter-spacing:.28em}
    .rd-watermark b{font-size:clamp(26px,8vw,46px)}
}
body::before{content:'NGIX\2022 XCTD';position:fixed;top:12px;left:14px;z-index:0;font:600 11px/1 Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;letter-spacing:.24em;text-transform:uppercase;color:rgba(68,71,70,.06);pointer-events:none}body::after{content:'';position:fixed;inset:0;z-index:0;pointer-events:none;background-image:url('/assets/favicon.svg'),url('/assets/bg.png');background-repeat:no-repeat,no-repeat;background-position:right 14px bottom 12px,center center;background-size:28px 28px,cover;opacity:.28;filter:saturate(.68) contrast(.84) brightness(1.03) blur(.32px);transform:translateZ(0)}
</style>
</head>
<body>
<div class="rd-watermark" aria-hidden="true"><span>NGIX</span><b>•</b><strong>XCTD</strong></div>
<div class="rd-shell">
  <div class="rd-tabs" role="tablist" aria-label="Redirect Decision">
    <button class="rd-tab is-active" id="tab-config" type="button" role="tab" aria-controls="pane-config" aria-selected="true" data-pane="pane-config">Redirect Decision</button>
    <button class="rd-tab" id="tab-live" type="button" role="tab" aria-controls="pane-live" aria-selected="false" data-pane="pane-live">Live</button>
    <button class="rd-tab" id="tab-tracker" type="button" role="tab" aria-controls="pane-tracker" aria-selected="false" data-pane="pane-tracker">Tracker</button>
    <button class="rd-tab" id="tab-postback" type="button" role="tab" aria-controls="pane-postback" aria-selected="false" data-pane="pane-postback">Postback</button>
    <form class="rd-logout-form" method="post" action="<?= rdEsc(strtok((string) ($_SERVER['REQUEST_URI'] ?? '/redirect-decision.php'), '?')) ?>">
      <input type="hidden" name="action" value="logout">
      <input type="hidden" name="csrf_token" value="<?= rdEsc($csrf) ?>">
      <button class="rd-logout" type="submit">keluar</button>
    </form>
  </div>

  <section class="rd-pane is-active" id="pane-config" role="tabpanel" aria-labelledby="tab-config">
  <div class="rd-card">
    <div class="rd-card-heading">Redirect Decision</div>
    <div class="rd-card-body">
      <p class="rd-sub">Siklus filter berulang: <b>filter</b> → <b>normal</b> → ulangi. Berlaku langsung tanpa deploy, tersinkron ke <code>.env</code>.</p>

      <div class="rd-row">
        <div class="rd-col">
          <label for="filter_min">Filter (menit)</label>
          <input class="rd-control" id="filter_min" type="number" min="0" max="1440" step="1" value="<?= $curFilterMin ?>">
        </div>
        <div class="rd-col">
          <label for="normal_min">Normal (menit)</label>
          <input class="rd-control" id="normal_min" type="number" min="0" max="1440" step="1" value="<?= $curNormalMin ?>">
        </div>
      </div>

      <label>Pratinjau siklus</label>
      <div class="rd-bar" id="bar"></div>
      <div class="rd-legend">
        <span><span class="rd-dot rd-dot--f"></span>Filter → SRP_FILTER_URL</span>
        <span><span class="rd-dot rd-dot--n"></span>Normal → alur asli</span>
      </div>

      <label>Status sekarang · live (sinkron traffic)</label>
      <div class="rd-live-mode">
        <span class="rd-badge" id="cycle-badge">—</span>
        <span class="rd-count" id="cycle-count"></span>
      </div>

      <label for="filter_url">Filter URL (https) — kosongkan untuk nonaktif</label>
      <input class="rd-control" id="filter_url" type="url" placeholder="https://example.com/safe" value="<?= rdEsc($curUrl) ?>">
      <p class="rd-hint">Saat jendela <b>filter</b>, traffic mobile (non-VPN) dialihkan ke URL ini. Filter = 0 menit menonaktifkan jendela filter sepenuhnya.</p>

      <div class="rd-actions">
        <button class="rd-btn rd-btn-primary" id="save">Simpan</button>
      </div>
      <div class="rd-status" id="status"></div>
    </div>
  </div>
  </section>

  <section class="rd-pane" id="pane-live" role="tabpanel" aria-labelledby="tab-live" hidden>
  <div class="rd-card">
    <div class="rd-card-heading">Live — hari ini <span class="rd-ts" id="live-ts"><?= rdEsc((string) $live['ts']) ?></span></div>
    <div class="rd-card-body">
      <div class="rd-stat-grid">
        <div class="rd-stat"><div class="rd-stat-num" id="s-clicks"><?= (int) $live['clicks_today'] ?></div><div class="rd-stat-lbl">Clicks</div></div>
        <div class="rd-stat"><div class="rd-stat-num" id="s-leads"><?= (int) $live['leads_today'] ?></div><div class="rd-stat-lbl">Leads</div></div>
        <div class="rd-stat"><div class="rd-stat-num" id="s-pb"><?= (int) $live['postbacks_today'] ?></div><div class="rd-stat-lbl">Postbacks</div></div>
        <div class="rd-stat"><div class="rd-stat-num" id="s-payout">$<?= number_format((float) $live['payout_today'], 2) ?></div><div class="rd-stat-lbl">Payout</div></div>
      </div>
      <?php if (!$dbUp) : ?>
        <p class="rd-hint">DB tidak tersedia — angka menunjukkan 0.</p>
      <?php endif; ?>
    </div>
  </div>
  </section>

  <section class="rd-pane" id="pane-tracker" role="tabpanel" aria-labelledby="tab-tracker" hidden>
  <div class="rd-card">
    <div class="rd-card-heading">Tracker · clickrecord (top 20)</div>
    <div class="rd-card-body rd-pad0">
      <table class="rd-table">
        <thead><tr><th>Click ID</th><th class="rd-r">Clicks</th><th class="rd-r">Leads</th><th class="rd-r">Payout</th><th class="rd-r">CR%</th><th>Terakhir</th></tr></thead>
        <tbody>
        <?php if ($trackers === []) : ?>
          <tr><td colspan="6" class="rd-empty">Belum ada data klik.</td></tr>
        <?php else :
            foreach ($trackers as $t) :
                $c = (int) $t['clicks'];
                $l = (int) $t['leads'];
                $cr = $c > 0 ? round($l / $c * 100, 2) : 0; ?>
          <tr>
            <td class="rd-mono"><?= rdEsc(strtoupper((string) $t['click_id'])) ?></td>
            <td class="rd-r"><?= $c ?></td>
            <td class="rd-r"><?= $l ?></td>
            <td class="rd-r">$<?= number_format((float) $t['payout'], 2) ?></td>
            <td class="rd-r"><?= $cr ?></td>
            <td class="rd-mono"><?= rdEsc((string) $t['last_date']) ?></td>
          </tr>
            <?php endforeach;
        endif; ?>
        </tbody>
      </table>
    </div>
  </div>
  </section>

  <section class="rd-pane" id="pane-postback" role="tabpanel" aria-labelledby="tab-postback" hidden>
  <div class="rd-card">
    <div class="rd-card-heading">Postback masuk per hari · leadreport (14 hari)</div>
    <div class="rd-card-body rd-pad0">
      <table class="rd-table">
        <thead><tr><th>Tanggal</th><th class="rd-r">Postbacks</th><th class="rd-r">Total payout</th></tr></thead>
        <tbody>
        <?php if ($daily === []) : ?>
          <tr><td colspan="3" class="rd-empty">Belum ada postback.</td></tr>
        <?php else :
            foreach ($daily as $d) : ?>
          <tr>
            <td class="rd-mono"><?= rdEsc((string) $d['conversion_date']) ?></td>
            <td class="rd-r"><?= (int) $d['postbacks'] ?></td>
            <td class="rd-r">$<?= number_format((float) $d['payout'], 2) ?></td>
          </tr>
            <?php endforeach;
        endif; ?>
        </tbody>
      </table>
    </div>
  </div>
  </section>
</div>

<script nonce="<?= rdEsc($nonce) ?>">
(function(){
  var csrf = document.querySelector('meta[name=csrf-token]').content;
  var fEl = document.getElementById('filter_min'),
      nEl = document.getElementById('normal_min'),
      uEl = document.getElementById('filter_url'),
      bar = document.getElementById('bar'),
      st  = document.getElementById('status'),
      btn = document.getElementById('save');

  // Live cycle state — same math as the redirect engine (serverTime % cycle <
  // filter ⇒ FILTER window). RD_CYCLE = the SAVED config; RD_OFFSET aligns the
  // client clock to the server so the countdown matches real traffic exactly.
  var RD_CYCLE = { filter: <?= (int) $curFilter ?>, cycle: <?= (int) $curCycle ?>, hasUrl: <?= $curUrl !== '' ? 'true' : 'false' ?> };
  var RD_OFFSET = <?= time() ?> - Math.floor(Date.now() / 1000);
  function rdServerNow(){ return Math.floor(Date.now() / 1000) + RD_OFFSET; }
  function rdDur(s){ s = Math.max(0, Math.floor(s)); var m = Math.floor(s / 60), x = s % 60; return m + ':' + (x < 10 ? '0' : '') + x; }
  function tickCycle(){
    var b = document.getElementById('cycle-badge'), c = document.getElementById('cycle-count');
    if(!b || !c){ return; }
    if(RD_CYCLE.filter <= 0 || RD_CYCLE.cycle <= 0){
      b.textContent = 'Filter nonaktif'; b.className = 'rd-badge rd-badge--off';
      c.textContent = 'semua traffic → alur normal'; return;
    }
    var pos = rdServerNow() % RD_CYCLE.cycle; if(pos < 0){ pos += RD_CYCLE.cycle; }
    if(pos < RD_CYCLE.filter){
      b.textContent = RD_CYCLE.hasUrl ? 'FILTER aktif' : 'FILTER aktif · tanpa URL';
      b.className = 'rd-badge rd-badge--filter';
      c.textContent = '→ NORMAL dalam ' + rdDur(RD_CYCLE.filter - pos);
    } else {
      b.textContent = 'NORMAL'; b.className = 'rd-badge rd-badge--normal';
      c.textContent = '→ FILTER dalam ' + rdDur(RD_CYCLE.cycle - pos);
    }
  }
  setInterval(tickCycle, 1000); tickCycle();

  function clampInt(v, lo, hi){ v = parseInt(v,10); if(isNaN(v)) v=0; return Math.max(lo, Math.min(hi, v)); }
  function forceLogoutIfUnauthorized(res){
    if(res && res.j && res.j.logout === true && res.j.err === 'unauthorized'){
      window.location.replace(window.location.pathname);
      return true;
    }
    return false;
  }

  function draw(){
    var f = clampInt(fEl.value,0,1440), n = clampInt(nEl.value,0,1440), t = f+n;
    bar.innerHTML = '';
    if(t<=0){ var e=document.createElement('div'); e.className='seg-n'; e.style.flex='1'; e.textContent='—'; bar.appendChild(e); return; }
    if(f>0){ var d=document.createElement('div'); d.className='seg-f'; d.style.flex=f; d.textContent='Filter '+f+'m'; bar.appendChild(d); }
    if(n>0){ var d2=document.createElement('div'); d2.className='seg-n'; d2.style.flex=n; d2.textContent='Normal '+n+'m'; bar.appendChild(d2); }
  }
  fEl.addEventListener('input', draw);
  nEl.addEventListener('input', draw);
  draw();

  btn.addEventListener('click', function(){
    var f = clampInt(fEl.value,0,1440), n = clampInt(nEl.value,0,1440);
    if(f+n < 1){ st.className='rd-status bad'; st.textContent='Total filter+normal minimal 1 menit.'; return; }
    btn.disabled = true; st.className='rd-status'; st.textContent='Menyimpan…';
    var body = new URLSearchParams({ filter_min:f, normal_min:n, filter_url:uEl.value.trim(), csrf_token:csrf });
    fetch(window.location.pathname, {
      method:'POST',
      headers:{'X-CSRF-Token':csrf,'Content-Type':'application/x-www-form-urlencoded'},
      body: body.toString()
    }).then(function(r){ return r.json().then(function(j){ return {ok:r.ok, j:j}; }); })
      .then(function(res){
        if(forceLogoutIfUnauthorized(res)){ return; }
        if(res.ok && res.j.ok){
          st.className='rd-status ok';
          st.textContent='Tersimpan. Filter '+(res.j.filter_seconds/60)+'m / cycle '+(res.j.cycle_seconds/60)+'m.'
            + (res.j.env_synced ? ' (.env tersinkron)' : ' (.env GAGAL ditulis — cache aktif)');
          RD_CYCLE.filter = res.j.filter_seconds;
          RD_CYCLE.cycle  = res.j.cycle_seconds;
          RD_CYCLE.hasUrl = !!(res.j.filter_url && res.j.filter_url.length);
          tickCycle();
        } else {
          st.className='rd-status bad';
          st.textContent='Gagal: '+((res.j && res.j.err) || 'unknown');
        }
      }).catch(function(){ st.className='rd-status bad'; st.textContent='Permintaan gagal.'; })
      .then(function(){ btn.disabled = false; });
  });

  // Live poll — refresh today's counters without reloading. Only runs while the
  // Live tab is open and the document is visible (saves needless requests).
  function fmtMoney(v){ return '$' + (Number(v)||0).toFixed(2); }
  function pollLive(){
    if (document.hidden) { return; }
    var pane = document.getElementById('pane-live');
    if (pane && !pane.classList.contains('is-active')) { return; }
    fetch(window.location.pathname + '?stats=1', { headers:{'Accept':'application/json'}, cache:'no-store' })
      .then(function(r){ return r.json(); })
      .then(function(j){
        if(!j || !j.ok) return;
        document.getElementById('s-clicks').textContent = j.clicks_today;
        document.getElementById('s-leads').textContent  = j.leads_today;
        document.getElementById('s-pb').textContent     = j.postbacks_today;
        document.getElementById('s-payout').textContent = fmtMoney(j.payout_today);
        document.getElementById('live-ts').textContent  = j.ts;
      }).catch(function(){});
  }
  setInterval(pollLive, 12000);

  // Tabs — switch panes (CSP-safe: listeners, no inline handlers).
  var tabs = Array.prototype.slice.call(document.querySelectorAll('.rd-tab'));
  function activate(paneId){
    tabs.forEach(function(t){
      var on = t.getAttribute('data-pane') === paneId;
      t.classList.toggle('is-active', on);
      t.setAttribute('aria-selected', on ? 'true' : 'false');
    });
    Array.prototype.forEach.call(document.querySelectorAll('.rd-pane'), function(p){
      var on = p.id === paneId;
      p.classList.toggle('is-active', on);
      p.hidden = !on;
    });
    try { history.replaceState(null, '', '#' + paneId); } catch(e){}
  }
  tabs.forEach(function(t){ t.addEventListener('click', function(){ activate(t.getAttribute('data-pane')); }); });
  if (location.hash && /^#pane-/.test(location.hash) && document.getElementById(location.hash.slice(1))){
    activate(location.hash.slice(1));
  }
})();
</script>
</body>
</html>
