<?php
/**
 * iCloud Hide My Email — single-file PHP edition
 *
 * Deploy:
 *   1. Upload file ini ke folder yang ingin diakses publik (mis. public_html/hme/).
 *   2. (Opsional) Upload .htaccess yang disertakan supaya URL /api/* bersih.
 *   3. Akses https://domain-anda.com/icloud-hme.php — atau atur InvocationKindName index.
 *
 * Fitur:
 *   - Multi-akun iCloud (DSID, cookies, IMAP per akun) tersimpan di accounts.json.
 *   - HME generate / reserve / get / list via cURL.
 *   - Inbox via IMAP (ekstensi PHP imap) — render HTML asli email seperti iCloud Mail.
 *   - Panel Admin terlindungi Access Code.
 *   - Kompatibel dengan Chrome Extension (paths /api/* via .htaccess, atau ?path= tanpa).
 *
 * Persyaratan PHP:
 *   - PHP 7.4+ (8.x direkomendasikan)
 *   - Ekstensi: curl, json, imap, mbstring (semua biasanya tersedia di cPanel)
 */

declare(strict_types=1);

// Tampilkan error sebagai JSON agar tidak blank page saat 500
ini_set('display_errors', '0');
error_reporting(E_ALL);
set_exception_handler(function (Throwable $e) {
    if (!headers_sent()) {
        http_response_code(500);
        header('Content-Type: application/json; charset=utf-8');
    }
    echo json_encode([
        'success' => false,
        'error'   => $e->getMessage(),
        'where'   => basename($e->getFile()) . ':' . $e->getLine(),
    ]);
    exit;
});
set_error_handler(function ($severity, $message, $file, $line) {
    // Hanya error fatal yang dilempar sebagai exception. Notice/warning dibiarkan
    // supaya fungsi seperti imap_* yang sering "warning" tidak crash.
    $fatal = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR;
    if (!($severity & $fatal)) return false;
    throw new ErrorException($message, 0, $severity, $file, $line);
});

// ============== KONFIG ==============
$ADMIN_PASSWORD   = getenv('ADMIN_PASSWORD') ?: '4rg0v14N';
$ACCOUNTS_FILE    = __DIR__ . '/accounts.json';
$MEMBERS_FILE     = __DIR__ . '/members.json';
$TRACKED_FILE     = __DIR__ . '/tracked_emails.json';
$SWEEP_LOCK_FILE  = __DIR__ . '/last_sweep.txt';
$DEFAULT_SUBDOMAIN = 'p188-maildomainws.icloud.com';
$APP_VERSION      = '1.3.0';
$DEFAULT_AUTO_DELETE_MIN = 10; // default TTL email, menit
$DEFAULT_DAILY_LIMIT     = 15; // default limit email/hari per member
$SWEEP_INTERVAL_SEC      = 60; // jangan sweep lebih sering dari ini

// ============== HELPER ==============
function read_json_body(): array {
    $raw = file_get_contents('php://input');
    if (!$raw) return [];
    $j = json_decode($raw, true);
    return is_array($j) ? $j : [];
}
function json_response($data, int $status = 200): void {
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}
function ok($data = null, array $extra = []): void {
    $payload = ['success' => true] + $extra;
    if ($data !== null) $payload['data'] = $data;
    json_response($payload);
}
function err(int $status, string $msg, array $extra = []): void {
    json_response(['success' => false, 'error' => $msg] + $extra, $status);
}
function require_admin(): void {
    global $ADMIN_PASSWORD;
    $code = $_SERVER['HTTP_X_ACCESS_CODE'] ?? '';
    if ($code !== $ADMIN_PASSWORD) err(401, 'Access Code tidak valid');
}
function gen_id(): string {
    return bin2hex(random_bytes(8));
}
function is_custom_domain(string $email): bool {
    $domain = '@tekno-mail.my.id';
    return substr(strtolower($email), -strlen($domain)) === $domain;
}
function generate_custom_email(?string $prefix = null): string {
    $adjectives = [
        'ganteng', 'cantik', 'kece', 'keren', 'pintar', 'hebat', 'bagus', 'jaya', 'sukses',
        'mantap', 'juara', 'prima', 'sakti', 'berkah', 'makmur', 'sejahtera', 'gemilang',
        'indah', 'damai', 'ceria', 'semangat', 'fokus', 'rajin', 'tangguh', 'lancar',
        'aman', 'nyaman', 'selamat', 'sehat', 'kuat', 'cepat', 'cerdas', 'tangkas', 'pandai'
    ];
    
    if ($prefix !== null && $prefix !== '') {
        $prefix = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $prefix));
    }
    
    if ($prefix !== null && $prefix !== '') {
        $num = str_pad((string)random_int(0, 999), 3, '0', STR_PAD_LEFT);
        return $prefix . $num . '@tekno-mail.my.id';
    } else {
        $adj = $adjectives[array_rand($adjectives)];
        $num = str_pad((string)random_int(0, 999), 3, '0', STR_PAD_LEFT);
        return 'tek' . $adj . $num . '@tekno-mail.my.id';
    }
}
function ext_check(): array {
    $missing = [];
    foreach (['curl', 'json', 'imap', 'mbstring'] as $ext) {
        if (!extension_loaded($ext)) $missing[] = $ext;
    }
    return $missing;
}

// ============== AKUN ==============
function read_accounts(): array {
    global $ACCOUNTS_FILE;
    if (!file_exists($ACCOUNTS_FILE)) return ['accounts' => []];
    $raw = @file_get_contents($ACCOUNTS_FILE);
    $j = json_decode($raw ?: '{}', true);
    if (!is_array($j) || !isset($j['accounts']) || !is_array($j['accounts'])) {
        return ['accounts' => []];
    }
    return $j;
}
function write_accounts(array $data): void {
    global $ACCOUNTS_FILE;
    $tmp = $ACCOUNTS_FILE . '.tmp';
    file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), LOCK_EX);
    rename($tmp, $ACCOUNTS_FILE);
    @chmod($ACCOUNTS_FILE, 0600);
}
function public_view(array $a): array {
    global $DEFAULT_SUBDOMAIN;
    return [
        'id'        => $a['id'],
        'name'      => $a['name'],
        'email'     => $a['email'] ?? '',
        'subdomain' => $a['subdomain'] ?? $DEFAULT_SUBDOMAIN,
    ];
}
function admin_view(array $a): array {
    global $DEFAULT_SUBDOMAIN;
    return [
        'id'          => $a['id'],
        'name'        => $a['name'],
        'subdomain'   => $a['subdomain'] ?? $DEFAULT_SUBDOMAIN,
        'dsid'        => $a['dsid'] ?? '',
        'clientId'    => $a['clientId'] ?? '',
        'cookies'     => $a['cookies'] ?? '',
        'email'       => $a['email'] ?? '',
        'appPassword' => !empty($a['appPassword']) ? '********' : '',
        'createdAt'   => $a['createdAt'] ?? '',
    ];
}
function find_account_by_id(string $id): ?array {
    foreach (read_accounts()['accounts'] as $a) {
        if (($a['id'] ?? '') === $id) return $a;
    }
    return null;
}
function resolve_account(string $id = ''): array {
    $list = read_accounts()['accounts'];
    if (!$list) throw new RuntimeException('Belum ada akun iCloud yang terdaftar. Tambahkan via panel Admin.');
    if ($id !== '') {
        foreach ($list as $a) if (($a['id'] ?? '') === $id) return $a;
        throw new RuntimeException('Akun iCloud tidak ditemukan');
    }
    return $list[0];
}
function create_account(array $payload): array {
    global $DEFAULT_SUBDOMAIN;
    $acc = [
        'id'          => gen_id(),
        'name'        => trim((string)($payload['name'] ?? '')) ?: 'Akun iCloud',
        'subdomain'   => trim((string)($payload['subdomain'] ?? '')) ?: $DEFAULT_SUBDOMAIN,
        'dsid'        => trim((string)($payload['dsid'] ?? '')),
        'clientId'    => trim((string)($payload['clientId'] ?? '')),
        'cookies'     => trim((string)($payload['cookies'] ?? '')),
        'email'       => trim((string)($payload['email'] ?? '')),
        'appPassword' => trim((string)($payload['appPassword'] ?? '')),
        'createdAt'   => date('c'),
    ];
    if ($acc['dsid'] === '' || $acc['clientId'] === '' || $acc['cookies'] === '') {
        throw new InvalidArgumentException('DSID, Client ID, dan Cookies wajib diisi');
    }
    $data = read_accounts();
    $data['accounts'][] = $acc;
    write_accounts($data);
    return admin_view($acc);
}
function update_account(string $id, array $payload): array {
    global $DEFAULT_SUBDOMAIN;
    $data = read_accounts();
    $idx = -1;
    foreach ($data['accounts'] as $i => $a) if (($a['id'] ?? '') === $id) { $idx = $i; break; }
    if ($idx === -1) throw new RuntimeException('Akun tidak ditemukan');
    $acc = $data['accounts'][$idx];
    $next = $acc;
    if (isset($payload['name']))      $next['name']      = trim((string)$payload['name']) ?: $acc['name'];
    if (isset($payload['subdomain'])) $next['subdomain'] = trim((string)$payload['subdomain']) ?: $DEFAULT_SUBDOMAIN;
    if (isset($payload['dsid']))      $next['dsid']      = trim((string)$payload['dsid']);
    if (isset($payload['clientId']))  $next['clientId']  = trim((string)$payload['clientId']);
    if (isset($payload['cookies']))   $next['cookies']   = trim((string)$payload['cookies']);
    if (isset($payload['email']))     $next['email']     = trim((string)$payload['email']);
    if (isset($payload['appPassword'])) {
        $v = (string)$payload['appPassword'];
        if ($v !== '' && $v !== '********') $next['appPassword'] = trim($v);
    }
    if ($next['dsid'] === '' || $next['clientId'] === '' || $next['cookies'] === '') {
        throw new InvalidArgumentException('DSID, Client ID, dan Cookies wajib diisi');
    }
    $data['accounts'][$idx] = $next;
    write_accounts($data);
    return admin_view($next);
}
function delete_account(string $id): void {
    $data = read_accounts();
    $before = count($data['accounts']);
    $data['accounts'] = array_values(array_filter($data['accounts'], function ($a) use ($id) {
        return ($a['id'] ?? '') !== $id;
    }));
    if (count($data['accounts']) === $before) throw new RuntimeException('Akun tidak ditemukan');
    write_accounts($data);
}

// ============== MEMBER (terpadu dengan voucher akses) ==============
// Struktur tiap member:
//   id, name, whatsapp, note, priceIdr, durationDays, voucherCode,
//   status (unused|active|expired|revoked), createdAt, redeemedAt,
//   expiresAt, sessionToken
function read_members(): array {
    global $MEMBERS_FILE;
    if (!file_exists($MEMBERS_FILE)) return ['members' => []];
    $j = json_decode((string)@file_get_contents($MEMBERS_FILE), true);
    if (!is_array($j) || !isset($j['members']) || !is_array($j['members'])) return ['members' => []];
    return $j;
}
function write_members(array $data): void {
    global $MEMBERS_FILE;
    $tmp = $MEMBERS_FILE . '.tmp';
    file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), LOCK_EX);
    rename($tmp, $MEMBERS_FILE);
    @chmod($MEMBERS_FILE, 0600);
}
function normalize_wa(string $raw): string {
    $s = preg_replace('/[^0-9+]/', '', $raw);
    if ($s === '') return '';
    if (strpos($s, '+') === 0) $s = substr($s, 1);
    if (strpos($s, '0') === 0) $s = '62' . substr($s, 1);
    return $s;
}
function normalize_voucher_code(string $raw): string {
    return strtoupper(preg_replace('/[^A-Z0-9-]/i', '', $raw));
}
function gen_voucher_code(): string {
    $chars = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
    $len   = strlen($chars);
    $blocks = [];
    for ($b = 0; $b < 3; $b++) {
        $s = '';
        for ($i = 0; $i < 4; $i++) $s .= $chars[random_int(0, $len - 1)];
        $blocks[] = $s;
    }
    return 'HME-' . implode('-', $blocks);
}
function find_member_by_id(string $id): ?array {
    foreach (read_members()['members'] as $m) if (($m['id'] ?? '') === $id) return $m;
    return null;
}
function find_member_by_code(string $code): ?array {
    $code = normalize_voucher_code($code);
    if ($code === '') return null;
    foreach (read_members()['members'] as $m) if (($m['voucherCode'] ?? '') === $code) return $m;
    return null;
}
function find_member_by_token(string $token): ?array {
    if ($token === '') return null;
    foreach (read_members()['members'] as $m) if (($m['sessionToken'] ?? '') === $token) return $m;
    return null;
}
// Hitung status efektif (auto-degrade ke 'expired' kalau lewat tanggal)
function effective_status(array $m): array {
    $now = time();
    $status = $m['status'] ?? 'unused';
    $remaining = null;
    if ($status === 'active' && !empty($m['expiresAt'])) {
        $diff = strtotime($m['expiresAt']) - $now;
        if ($diff <= 0) $status = 'expired';
        else $remaining = (int)ceil($diff / 86400);
    }
    return ['status' => $status, 'remainingDays' => $remaining];
}
function member_view_admin(array $m): array {
    $eff = effective_status($m);
    return [
        'id'             => $m['id'],
        'name'           => $m['name'],
        'whatsapp'       => $m['whatsapp'] ?? '',
        'note'           => $m['note'] ?? '',
        'voucherCode'    => $m['voucherCode'] ?? '',
        'durationDays'   => (int)($m['durationDays'] ?? 0),
        'priceIdr'       => (int)($m['priceIdr'] ?? 0),
        'dailyLimit'     => isset($m['dailyLimit']) ? (int)$m['dailyLimit'] : 15,  // 0 = unlimited
        'autoDeleteMin'  => isset($m['autoDeleteMin']) ? (int)$m['autoDeleteMin'] : 10,
        'status'         => $eff['status'],
        'remainingDays'  => $eff['remainingDays'],
        'createdAt'      => $m['createdAt'] ?? '',
        'redeemedAt'     => $m['redeemedAt'] ?? null,
        'expiresAt'      => $m['expiresAt'] ?? null,
        'currentDeviceId'=> $m['currentDeviceId'] ?? null,
        'lastDeviceInfo' => $m['lastDeviceInfo'] ?? null,
    ];
}

function create_member(array $payload): array {
    $name = trim((string)($payload['name'] ?? ''));
    if ($name === '') throw new InvalidArgumentException('Nama member wajib diisi');

    $duration = (int)($payload['durationDays'] ?? 0);
    if ($duration <= 0 || $duration > 3650) throw new InvalidArgumentException('Durasi (hari) tidak valid');
    $price = max(0, (int)($payload['priceIdr'] ?? 0));

    // Voucher code: manual atau auto, harus unik
    $code = trim((string)($payload['voucherCode'] ?? ''));
    if ($code === '') {
        do { $code = gen_voucher_code(); } while (find_member_by_code($code));
    } else {
        $code = normalize_voucher_code($code);
        if (strlen($code) < 6) throw new InvalidArgumentException('Kode voucher minimal 6 karakter');
        if (find_member_by_code($code)) throw new InvalidArgumentException('Kode voucher sudah dipakai');
    }

    $member = [
        'id'             => gen_id(),
        'name'           => $name,
        'whatsapp'       => normalize_wa((string)($payload['whatsapp'] ?? '')),
        'note'           => trim((string)($payload['note'] ?? '')),
        'voucherCode'    => $code,
        'durationDays'   => $duration,
        'priceIdr'       => $price,
        'dailyLimit'     => isset($payload['dailyLimit']) ? max(0, (int)$payload['dailyLimit']) : 15, // 0 = unlimited
        'autoDeleteMin'  => isset($payload['autoDeleteMin']) ? max(0, (int)$payload['autoDeleteMin']) : 10,
        'status'         => 'unused',
        'createdAt'      => date('c'),
        'redeemedAt'     => null,
        'expiresAt'      => null,
        'sessionToken'   => null,
        'currentDeviceId'=> null,
        'lastDeviceInfo' => null,
    ];
    $data = read_members();
    $data['members'][] = $member;
    write_members($data);
    return $member;
}

function update_member(string $id, array $payload): array {
    $data = read_members();
    $idx = -1;
    foreach ($data['members'] as $i => $m) if (($m['id'] ?? '') === $id) { $idx = $i; break; }
    if ($idx === -1) throw new RuntimeException('Member tidak ditemukan');
    $m = $data['members'][$idx];

    if (isset($payload['name']))     $m['name']     = trim((string)$payload['name']) ?: $m['name'];
    if (isset($payload['whatsapp'])) $m['whatsapp'] = normalize_wa((string)$payload['whatsapp']);
    if (isset($payload['note']))     $m['note']     = trim((string)$payload['note']);
    if (isset($payload['priceIdr'])) $m['priceIdr'] = max(0, (int)$payload['priceIdr']);
    if (isset($payload['dailyLimit'])) $m['dailyLimit'] = max(0, (int)$payload['dailyLimit']);
    if (isset($payload['autoDeleteMin'])) $m['autoDeleteMin'] = max(0, (int)$payload['autoDeleteMin']);

    // Ganti durasi → hanya update field, tidak reset masa aktif. Operator pakai
    // tombol "Reset" / "Perpanjang" eksplisit.
    if (isset($payload['durationDays'])) {
        $d = (int)$payload['durationDays'];
        if ($d <= 0 || $d > 3650) throw new InvalidArgumentException('Durasi (hari) tidak valid');
        $m['durationDays'] = $d;
    }

    // Ganti voucher code (manual rotation)
    if (isset($payload['voucherCode'])) {
        $newCode = normalize_voucher_code((string)$payload['voucherCode']);
        if ($newCode !== '' && $newCode !== ($m['voucherCode'] ?? '')) {
            if (strlen($newCode) < 6) throw new InvalidArgumentException('Kode voucher minimal 6 karakter');
            $existing = find_member_by_code($newCode);
            if ($existing && ($existing['id'] ?? '') !== $id) throw new InvalidArgumentException('Kode voucher sudah dipakai');
            $m['voucherCode']  = $newCode;
            $m['sessionToken'] = null; // logout paksa
        }
    }

    // Aksi: revoke / unrevoke / reset / extend
    if (isset($payload['action'])) {
        $action = (string)$payload['action'];
        if ($action === 'revoke') {
            $m['status'] = 'revoked';
            $m['sessionToken'] = null;
        } elseif ($action === 'unrevoke' && ($m['status'] ?? '') === 'revoked') {
            if (!empty($m['redeemedAt']) && !empty($m['expiresAt']) && strtotime($m['expiresAt']) > time()) {
                $m['status'] = 'active';
            } else {
                $m['status'] = 'unused';
                $m['redeemedAt'] = null;
                $m['expiresAt'] = null;
            }
        } elseif ($action === 'reset') {
            // Kembalikan ke unused (siap aktivasi ulang)
            $m['status']         = 'unused';
            $m['redeemedAt']     = null;
            $m['expiresAt']      = null;
            $m['sessionToken']   = null;
            $m['currentDeviceId']= null;
            $m['lastDeviceInfo'] = null;
        } elseif ($action === 'force-logout') {
            // Putuskan semua sesi tanpa mengubah status langganan
            $m['sessionToken']   = null;
            $m['currentDeviceId']= null;
        } elseif ($action === 'extend') {
            // Perpanjang durationDays dari sekarang (atau dari expiresAt jika masih aktif)
            $extraDays = isset($payload['extendDays']) ? (int)$payload['extendDays'] : (int)$m['durationDays'];
            if ($extraDays <= 0) throw new InvalidArgumentException('Hari perpanjangan tidak valid');
            $base = (!empty($m['expiresAt']) && strtotime($m['expiresAt']) > time())
                ? strtotime($m['expiresAt'])
                : time();
            $m['expiresAt'] = date('c', $base + $extraDays * 86400);
            if (empty($m['redeemedAt'])) $m['redeemedAt'] = date('c');
            $m['status'] = 'active';
            if (empty($m['sessionToken'])) $m['sessionToken'] = bin2hex(random_bytes(24));
        }
    }

    $data['members'][$idx] = $m;
    write_members($data);
    return $m;
}

function delete_member(string $id): void {
    $data = read_members();
    $before = count($data['members']);
    $data['members'] = array_values(array_filter($data['members'], function ($m) use ($id) {
        return ($m['id'] ?? '') !== $id;
    }));
    if (count($data['members']) === $before) throw new RuntimeException('Member tidak ditemukan');
    write_members($data);
}

// Redeem voucher (idempotent untuk active member). Single-device enforcement:
// device baru selalu menang dan menggusur sesi lama.
function redeem_voucher_code(string $code, string $deviceId = '', array $deviceInfo = []): array {
    $code = normalize_voucher_code($code);
    if ($code === '') throw new InvalidArgumentException('Kode voucher wajib diisi');
    if ($deviceId === '') throw new InvalidArgumentException('Device ID wajib diisi');
    $data = read_members();
    $idx = -1;
    foreach ($data['members'] as $i => $m) if (($m['voucherCode'] ?? '') === $code) { $idx = $i; break; }
    if ($idx === -1) throw new InvalidArgumentException('Kode voucher tidak ditemukan');
    $m = $data['members'][$idx];

    if (($m['status'] ?? '') === 'revoked') throw new InvalidArgumentException('Akses dicabut. Hubungi admin.');

    // Auto-degrade kalau expired
    if (!empty($m['expiresAt']) && strtotime($m['expiresAt']) <= time()) {
        $m['status'] = 'expired';
        $data['members'][$idx] = $m;
        write_members($data);
        throw new InvalidArgumentException('Masa langganan sudah berakhir.');
    }

    // Belum aktif → activate sekarang
    if (($m['status'] ?? 'unused') === 'unused') {
        $now = time();
        $m['status']     = 'active';
        $m['redeemedAt'] = date('c', $now);
        $m['expiresAt']  = date('c', $now + ((int)$m['durationDays']) * 86400);
    }

    // Single-device enforcement: device baru selalu mengambil alih
    $previousDevice = $m['currentDeviceId'] ?? null;
    $kicked = ($previousDevice && $previousDevice !== $deviceId);
    $m['sessionToken']    = bin2hex(random_bytes(24));
    $m['currentDeviceId'] = $deviceId;
    $m['lastDeviceInfo']  = [
        'ua'        => substr((string)($deviceInfo['ua'] ?? ($_SERVER['HTTP_USER_AGENT'] ?? '')), 0, 200),
        'ip'        => $_SERVER['REMOTE_ADDR'] ?? '',
        'loginAt'   => date('c'),
    ];

    $data['members'][$idx] = $m;
    write_members($data);

    return [
        'token'         => $m['sessionToken'],
        'expiresAt'     => $m['expiresAt'],
        'remainingDays' => (int)ceil((strtotime($m['expiresAt']) - time()) / 86400),
        'memberName'    => $m['name'],
        'kicked'        => $kicked,
        'dailyLimit'    => isset($m['dailyLimit']) ? (int)$m['dailyLimit'] : 15,
        'autoDeleteMin' => isset($m['autoDeleteMin']) ? (int)$m['autoDeleteMin'] : 10,
    ];
}

// Middleware: validasi token + device match
function require_member_token(): array {
    $h = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if (stripos($h, 'Bearer ') !== 0) err(401, 'Token member dibutuhkan');
    $token = trim(substr($h, 7));
    $m = find_member_by_token($token);
    if (!$m) err(401, 'Sesi tidak valid. Mungkin login dari device lain.');
    if (($m['status'] ?? '') === 'revoked') err(403, 'Akses dicabut. Hubungi admin.');
    if (empty($m['expiresAt']) || strtotime($m['expiresAt']) <= time()) err(403, 'Masa langganan sudah berakhir.');
    // Device check
    $deviceId = $_SERVER['HTTP_X_DEVICE_ID'] ?? '';
    if (!empty($m['currentDeviceId']) && $deviceId !== '' && $m['currentDeviceId'] !== $deviceId) {
        err(401, 'Sesi diambil alih oleh device lain.');
    }
    return $m;
}

// ============== TRACKED EMAILS (auto-delete state global) ==============
// Schema:
// {
//   "emails": [
//     {
//       "memberId": "...",
//       "accountId": "...",
//       "hme": "user@icloud.com",
//       "anonymousId": "abc123",
//       "label": "...",
//       "createdAt": "...",
//       "expiresAt": "...",   // null kalau pinned/unlimited
//       "pinned": false
//     }
//   ]
// }
function read_tracked(): array {
    global $TRACKED_FILE;
    if (!file_exists($TRACKED_FILE)) return ['emails' => []];
    $j = json_decode((string)@file_get_contents($TRACKED_FILE), true);
    if (!is_array($j) || !isset($j['emails']) || !is_array($j['emails'])) return ['emails' => []];
    return $j;
}
function write_tracked(array $data): void {
    global $TRACKED_FILE;
    $tmp = $TRACKED_FILE . '.tmp';
    file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), LOCK_EX);
    rename($tmp, $TRACKED_FILE);
    @chmod($TRACKED_FILE, 0600);
}
function tracked_view(array $e): array {
    return [
        'hme'         => $e['hme'],
        'anonymousId' => $e['anonymousId'] ?? '',
        'accountId'   => $e['accountId'] ?? '',
        'label'       => $e['label'] ?? '',
        'note'        => $e['note'] ?? '',
        'createdAt'   => $e['createdAt'] ?? '',
        'expiresAt'   => $e['expiresAt'] ?? null,
        'pinned'      => !empty($e['pinned']),
    ];
}
function add_tracked(string $memberId, array $payload): void {
    $data = read_tracked();
    // Hindari duplikasi entry per member+hme
    $data['emails'] = array_values(array_filter($data['emails'], function ($e) use ($memberId, $payload) {
        return !(($e['memberId'] ?? '') === $memberId && ($e['hme'] ?? '') === ($payload['hme'] ?? ''));
    }));
    $data['emails'][] = [
        'memberId'    => $memberId,
        'accountId'   => $payload['accountId'] ?? '',
        'hme'         => $payload['hme'] ?? '',
        'anonymousId' => $payload['anonymousId'] ?? '',
        'label'       => $payload['label'] ?? '',
        'note'        => $payload['note'] ?? '',
        'createdAt'   => date('c'),
        'expiresAt'   => $payload['expiresAt'] ?? null,
        'pinned'      => false,
    ];
    write_tracked($data);
}
function update_tracked(string $memberId, string $hme, array $changes): ?array {
    $data = read_tracked();
    foreach ($data['emails'] as $i => $e) {
        if (($e['memberId'] ?? '') !== $memberId) continue;
        if (($e['hme'] ?? '') !== $hme) continue;
        if (isset($changes['pinned'])) {
            $data['emails'][$i]['pinned'] = !empty($changes['pinned']);
            if (!empty($changes['pinned'])) {
                $data['emails'][$i]['expiresAt'] = null;
            }
        }
        if (array_key_exists('expiresAt', $changes)) {
            $data['emails'][$i]['expiresAt'] = $changes['expiresAt'];
        }
        write_tracked($data);
        return $data['emails'][$i];
    }
    return null;
}
function remove_tracked(string $memberId, string $hme): bool {
    $data = read_tracked();
    $before = count($data['emails']);
    $data['emails'] = array_values(array_filter($data['emails'], function ($e) use ($memberId, $hme) {
        return !(($e['memberId'] ?? '') === $memberId && ($e['hme'] ?? '') === $hme);
    }));
    if (count($data['emails']) === $before) return false;
    write_tracked($data);
    return true;
}
function list_tracked_for_member(string $memberId): array {
    $out = [];
    foreach (read_tracked()['emails'] as $e) {
        if (($e['memberId'] ?? '') === $memberId) $out[] = tracked_view($e);
    }
    // Terbaru di atas
    usort($out, function ($a, $b) { return strcmp($b['createdAt'] ?? '', $a['createdAt'] ?? ''); });
    return $out;
}

// Hitung berapa email yang sudah dibuat hari ini oleh member (timezone Asia/Jakarta)
function count_today_for_member(string $memberId): int {
    $todayStart = strtotime(date('Y-m-d') . ' 00:00:00');
    $count = 0;
    foreach (read_tracked()['emails'] as $e) {
        if (($e['memberId'] ?? '') !== $memberId) continue;
        $created = strtotime($e['createdAt'] ?? '0');
        if ($created >= $todayStart) $count++;
    }
    return $count;
}

// Sweep: hapus email yang sudah lewat expiresAt dari iCloud + tracking.
// Dipanggil opportunistik dari setiap request (dengan throttle file-lock 60 detik).
function maybe_run_sweep(): void {
    global $SWEEP_LOCK_FILE, $SWEEP_INTERVAL_SEC;
    $last = file_exists($SWEEP_LOCK_FILE) ? (int)@file_get_contents($SWEEP_LOCK_FILE) : 0;
    if (time() - $last < $SWEEP_INTERVAL_SEC) return;
    @file_put_contents($SWEEP_LOCK_FILE, (string)time());

    $data = read_tracked();
    if (empty($data['emails'])) return;

    // Build account index
    $accIndex = [];
    foreach (read_accounts()['accounts'] as $acc) $accIndex[$acc['id']] = $acc;

    $now = time();
    $kept = [];
    foreach ($data['emails'] as $e) {
        $exp = $e['expiresAt'] ?? null;
        if (!empty($e['pinned']) || empty($exp) || strtotime($exp) > $now) {
            $kept[] = $e;
            continue;
        }
        if (is_custom_domain($e['hme'] ?? '')) {
            continue; // Langsung hapus dari tracking
        }
        // Sudah lewat → coba hapus di iCloud
        $acc = $accIndex[$e['accountId'] ?? ''] ?? null;
        if (!$acc || empty($e['anonymousId'])) {
            // Tidak bisa cleanup karena akun hilang/anonymousId tidak ada → buang dari tracking
            continue;
        }
        try {
            $r1 = icloud_call($acc, 'v1', 'deactivate', ['anonymousId' => $e['anonymousId']]);
            if ($r1['status'] < 400) {
                @icloud_call($acc, 'v1', 'delete', ['anonymousId' => $e['anonymousId']]);
            }
        } catch (Throwable $ex) {
            // Lewatkan kalau cookie expired dll. Tetap drop dari tracking biar tidak nyangkut selamanya.
            error_log('[hme.sweep] ' . $ex->getMessage());
        }
        usleep(120000); // 120 ms
    }
    if (count($kept) !== count($data['emails'])) {
        $data['emails'] = $kept;
        write_tracked($data);
    }
}

// ============== iCloud HTTP ==============
function icloud_headers(array $account): array {
    return [
        'accept: */*',
        'accept-language: en-US,en;q=0.9',
        'content-type: text/plain',
        'cookie: ' . ($account['cookies'] ?? ''),
        'origin: https://www.icloud.com',
        'referer: https://www.icloud.com/',
        'sec-ch-ua: "Chromium";v="148", "Google Chrome";v="148", "Not/A)Brand";v="99"',
        'sec-ch-ua-mobile: ?0',
        'sec-ch-ua-platform: "Windows"',
        'sec-fetch-dest: empty',
        'sec-fetch-mode: cors',
        'sec-fetch-site: same-site',
        'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36',
    ];
}
function icloud_url(array $account, string $version, string $op): string {
    global $DEFAULT_SUBDOMAIN;
    $sub = $account['subdomain'] ?? $DEFAULT_SUBDOMAIN;
    return "https://{$sub}/{$version}/hme/{$op}?clientBuildNumber=2616Build17&clientMasteringNumber=2616Build17&clientId="
        . urlencode($account['clientId']) . "&dsid=" . urlencode($account['dsid']);
}
function icloud_call(array $account, string $version, string $op, ?array $body, string $method = 'POST'): array {
    $url = icloud_url($account, $version, $op);
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => icloud_headers($account),
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body !== null ? json_encode($body) : '');
    }
    $resp   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $cerr   = curl_error($ch);
    curl_close($ch);
    if ($resp === false) throw new RuntimeException('Network error: ' . $cerr);
    $j = json_decode((string)$resp, true);
    if (!is_array($j)) throw new RuntimeException('iCloud returned non-JSON (HTTP ' . $status . ')');
    return ['status' => $status, 'data' => $j];
}

// ============== IMAP ==============
function walk_parts($structure, string $prefix = ''): array {
    $out = [];
    if (!empty($structure->parts)) {
        foreach ($structure->parts as $i => $part) {
            $partNum = $prefix === '' ? (string)($i + 1) : $prefix . '.' . ($i + 1);
            if (!empty($part->parts)) {
                $out = array_merge($out, walk_parts($part, $partNum));
            } else {
                $out[] = ['part' => $part, 'partNum' => $partNum];
            }
        }
    } else {
        $out[] = ['part' => $structure, 'partNum' => '1'];
    }
    return $out;
}
function decode_imap_body(string $data, $part): string {
    $enc = $part->encoding ?? 0;
    if ($enc === 3) $data = base64_decode($data) ?: $data;     // BASE64
    elseif ($enc === 4) $data = quoted_printable_decode($data); // QUOTED_PRINTABLE
    $charset = 'UTF-8';
    if (!empty($part->parameters)) {
        foreach ($part->parameters as $p) if (strtolower($p->attribute) === 'charset') { $charset = $p->value; break; }
    }
    if (strtoupper($charset) !== 'UTF-8') {
        $conv = @iconv($charset, 'UTF-8//TRANSLIT//IGNORE', $data);
        if ($conv !== false) $data = $conv;
    }
    return $data;
}
function decode_mime_str(string $s): string {
    if ($s === '') return $s;
    $parts = imap_mime_header_decode($s);
    $out = '';
    foreach ($parts as $p) {
        $charset = $p->charset === 'default' ? 'UTF-8' : $p->charset;
        $text = $p->text;
        if (strtoupper($charset) !== 'UTF-8') {
            $conv = @iconv($charset, 'UTF-8//TRANSLIT//IGNORE', $text);
            if ($conv !== false) $text = $conv;
        }
        $out .= $text;
    }
    return $out;
}
function imap_fetch_inbox(array $account, string $hmeEmail): array {
    if (!extension_loaded('imap')) {
        throw new RuntimeException('Ekstensi PHP "imap" tidak aktif di server. Aktifkan di cPanel atau hubungi hosting.');
    }
    if (empty($account['email']) || empty($account['appPassword'])) {
        throw new InvalidArgumentException('Akun ini belum punya kredensial IMAP (email + app-specific password). Atur di panel Admin.');
    }
    // Atur timeout yang singkat agar request tidak gantung (stuck)
    @imap_timeout(IMAP_OPENTIMEOUT, 4);
    @imap_timeout(IMAP_READTIMEOUT, 6);
    @imap_timeout(IMAP_WRITETIMEOUT, 6);

    $mailbox = '{imap.mail.me.com:993/imap/ssl/novalidate-cert}INBOX';
    $imap = @imap_open($mailbox, $account['email'], $account['appPassword'], 0, 1);
    if (!$imap) {
        $errs = imap_errors() ?: [];
        throw new RuntimeException('Gagal IMAP: ' . (implode('; ', $errs) ?: 'koneksi ditolak'));
    }

    $numMsgs = @imap_num_msg($imap);
    $msgs = [];

    if ($numMsgs > 0) {
        // Ambil overview dari 15 email terbaru (semuanya dalam 1 kali roundtrip!)
        $start = max(1, $numMsgs - 14);
        $range = "{$start}:{$numMsgs}";
        $overviews = @imap_fetch_overview($imap, $range, 0) ?: [];
        $target = strtolower($hmeEmail);

        foreach ($overviews as $ov) {
            $msgNo = $ov->msgno;
            $toaddress = strtolower((string)($ov->to ?? ''));

            // Cocokkan penerima target secara lokal
            if (strpos($toaddress, $target) === false) {
                continue; // Lewati jika bukan email target
            }

            $structure = @imap_fetchstructure($imap, $msgNo);
            if (!$structure) continue;

            $parts = walk_parts($structure);
            $text = ''; $html = '';
            foreach ($parts as $p) {
                $part = $p['part'];
                $isAttachment = false;
                if (isset($part->ifdisposition) && $part->ifdisposition && isset($part->disposition)
                    && strtolower($part->disposition) === 'attachment') {
                    $isAttachment = true;
                }
                if ($isAttachment) continue;
                // type 0 = TEXT
                if (isset($part->type) && (int)$part->type !== 0) continue;
                $body = @imap_fetchbody($imap, $msgNo, $p['partNum']);
                $body = decode_imap_body($body, $part);
                $sub = strtolower($part->subtype ?? '');
                if ($sub === 'html' && $html === '') $html = $body;
                elseif ($sub === 'plain' && $text === '') $text = $body;
            }

            $dateStr = '';
            if (!empty($ov->date)) {
                $ts = strtotime($ov->date);
                if ($ts) $dateStr = date('c', $ts);
            }

            $msgs[] = [
                'id'      => @imap_uid($imap, $msgNo),
                'date'    => $dateStr,
                'subject' => decode_mime_str((string)($ov->subject ?? '')),
                'from'    => decode_mime_str((string)($ov->from ?? '')),
                'to'      => decode_mime_str((string)($ov->to ?? '')),
                'cc'      => '',
                'text'    => $text,
                'html'    => $html,
            ];
        }
    }

    @imap_errors(); // clear
    @imap_close($imap);
    usort($msgs, function ($a, $b) {
        return strcmp($b['date'] ?? '', $a['date'] ?? '');
    });
    return $msgs;
}

function mailws2_call(array $account, string $endpoint, array $body): array {
    $sub = $account['subdomain'] ?? 'p188-maildomainws.icloud.com';
    $parts = explode('-', $sub, 2);
    $prefix = $parts[0] ?: 'p149';
    $sep = (strpos($endpoint, '?') !== false) ? '&' : '?';
    $url = "https://{$prefix}-mccgateway.icloud.com/mailws2/v1/{$endpoint}{$sep}clientBuildNumber=2618Build25&clientMasteringNumber=2618Build25&clientId="
        . urlencode($account['clientId']) . "&dsid=" . urlencode($account['dsid']);
        
    $headers = [
        'accept: application/json',
        'accept-language: en-US,en;q=0.9',
        'content-type: application/json',
        'cookie: ' . ($account['cookies'] ?? ''),
        'origin: https://www.icloud.com',
        'referer: https://www.icloud.com/',
        'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36',
    ];
    
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_TIMEOUT        => 8,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode($body)
    ]);
    
    $resp   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $cerr   = curl_error($ch);
    curl_close($ch);
    
    if ($resp === false) throw new RuntimeException('Mail API network error: ' . $cerr);
    $j = json_decode((string)$resp, true);
    if (!is_array($j)) throw new RuntimeException('Mail API returned non-JSON (HTTP ' . $status . ')');
    
    return ['status' => $status, 'data' => $j];
}

function fetch_universal_inbox(array $account): array {
    // 1. Fetch INBOX threads using thread/search (provides preview text and subject for INBOX)
    $body = [
        "responseType" => "THREAD_DIGEST",
        "includeFolderStatus" => true,
        "maxResults" => 100,
        "sessionHeaders" => [
            "folder" => "INBOX",
            "threadmode" => 1
        ]
    ];
    $res = mailws2_call($account, 'thread/search', $body);
    if ($res['status'] >= 400 || !isset($res['data']['threadList'])) {
        throw new RuntimeException('Gagal mengambil threads dari iCloud Mail (HTTP ' . $res['status'] . ')');
    }
    $threads = $res['data']['threadList'];
    
    $merged = [];
    foreach ($threads as $t) {
        $tid = $t['threadId'] ?? '';
        if ($tid === '') continue;
        
        $dateStr = isset($t['timestamp']) ? date('c', intval($t['timestamp'] / 1000)) : '';
        
        $merged[$tid] = [
            'id'      => $tid,
            'date'    => $dateStr,
            'subject' => $t['subject'] ?? '(Tanpa Subjek)',
            'from'    => !empty($t['senders']) ? $t['senders'][0] : 'Unknown',
            'to'      => '', // Will be populated from the message list
            'text'    => $t['preview'] ?? '',
            'html'    => '',
            'folder'  => 'INBOX'
        ];
    }
    
    // 2. Fetch recent messages to extract recipient 'To' headers and Junk messages
    $recipientMap = [];
    try {
        $msgBody = [
            "domain" => "email",
            "properties" => [
                "uid", "appleThrid", "identifier", "stateInternalDate", "mboxRef",
                [
                    "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "From",
                    "alias" => "from", "decoder" => "encodedWords"
                ],
                [
                    "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "To",
                    "decoder" => "encodedWords", "alias" => "to"
                ],
                [
                    "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "Subject",
                    "decoder" => "encodedWords", "alias" => "subject"
                ]
            ],
            "limit" => 150,
            "predicate" => [
                "expression" => ["property" => "flags", "value" => "DELETED", "type" => "fieldOf"],
                "value" => false,
                "type" => "eq",
                "and" => [
                    [
                        "type" => "notIn",
                        "expression" => ["property" => "mboxRef"],
                        "value" => ["32", "35"] // Exclude Trash (32) and Archive (35)
                    ]
                ]
            ],
            "orderby" => [
                "expressions" => [
                    ["property" => "stateInternalDate", "type" => "property"]
                ],
                "ascending" => false
            ]
        ];
        
        $msgRes = mailws2_call($account, 'message/list?clientIntent=fetchMessageList', $msgBody);
        if ($msgRes['status'] === 200 && isset($msgRes['data']['domainObjects']) && is_array($msgRes['data']['domainObjects'])) {
            foreach ($msgRes['data']['domainObjects'] as $msg) {
                $tid = $msg['appleThrid'] ?? '';
                if ($tid === '') continue;
                
                $toAddress = $msg['to'] ?? '';
                
                // Populate recipient map
                if (!isset($recipientMap[$tid]) || $toAddress !== '') {
                    $recipientMap[$tid] = $toAddress;
                }
                
                // Check if message is in Junk folder (mboxRef = 33)
                $mboxId = '';
                if (isset($msg['mboxRef'])) {
                    if (is_array($msg['mboxRef'])) {
                        $mboxId = (string)($msg['mboxRef']['id'] ?? '');
                    } else {
                        $mboxId = (string)$msg['mboxRef'];
                    }
                }
                
                if ($mboxId === '33') {
                    $dateStr = isset($msg['stateInternalDate']) ? date('c', intval($msg['stateInternalDate'])) : '';
                    
                    $junkItem = [
                        'id'      => $tid,
                        'date'    => $dateStr,
                        'subject' => (!empty($msg['subject']) ? $msg['subject'] : '(Tanpa Subjek)'),
                        'from'    => $msg['from'] ?? 'Unknown',
                        'to'      => $toAddress,
                        'text'    => '⚠️ [JUNK] Pesan terdeteksi di folder Junk/Spam.',
                        'html'    => '',
                        'folder'  => 'Junk'
                    ];
                    
                    if (isset($merged[$tid])) {
                        if (strtotime($dateStr) > strtotime($merged[$tid]['date'])) {
                            $merged[$tid] = $junkItem;
                        }
                    } else {
                        $merged[$tid] = $junkItem;
                    }
                }
            }
        }
    } catch (Throwable $e) {
        error_log('[universal.inbox.messages] ' . $e->getMessage());
    }
    
    // 3. Apply recipient 'to' addresses from the map to merged threads
    foreach ($merged as $tid => &$tItem) {
        if (empty($tItem['to']) && isset($recipientMap[$tid])) {
            $tItem['to'] = $recipientMap[$tid];
        }
    }
    unset($tItem);
    
    $msgs = array_values($merged);
    usort($msgs, function ($a, $b) {
        return strcmp($b['date'] ?? '', $a['date'] ?? '');
    });
    
    return $msgs;
}

function fetch_universal_thread(array $account, string $threadId): array {
    $body = [
        "domain" => "email",
        "predicate" => [
            "type" => "eq",
            "expression" => [
                "property" => "appleThrid"
            ],
            "value" => $threadId,
            "and" => [
                [
                    "type" => "eq",
                    "expression" => [
                        "type" => "fieldOf",
                        "property" => "flags",
                        "value" => "DELETED"
                    ],
                    "value" => false
                ],
                [
                    "type" => "notIn",
                    "expression" => [
                        "property" => "mboxRef"
                    ],
                    "value" => ["32","35"]
                ]
            ]
        ],
        "properties" => [
            "uid", "appleThrid", "identifier", "flags", "stateInternalDate", "rfc822Size",
            "hasAttachment", "messageId", "mboxRef", "previewId", "parts", "isSeen", "modseq",
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "From",
                "alias" => "from", "decoder" => "encodedWords"
            ],
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "To",
                "decoder" => "encodedWords", "alias" => "to"
            ],
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "Cc",
                "decoder" => "encodedWords", "alias" => "cc"
            ],
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "Subject",
                "decoder" => "encodedWords", "alias" => "subject"
            ],
            "mboxRefUidMap"
        ]
    ];
    
    $res = mailws2_call($account, 'message/list?clientIntent=fetchMessageListByThreadId', $body);
    if ($res['status'] >= 400 || empty($res['data']['domainObjects'])) {
        throw new RuntimeException('Gagal mengambil rincian thread (HTTP ' . $res['status'] . ')');
    }
    
    $messages = $res['data']['domainObjects'];
    if (count($messages) > 3) {
        $messages = array_slice($messages, -3);
    }
    
    $outputMessages = [];
    foreach ($messages as $msg) {
        $uid = $msg['uid'] ?? '';
        if ($uid === '') continue;
        
        $htmlPartId = '';
        $plainPartId = '';
        if (!empty($msg['parts'])) {
            foreach ($msg['parts'] as $part) {
                $cType = strtolower($part['contentType'] ?? '');
                if ($cType === 'text/html') {
                    $htmlPartId = $part['partId'] ?? '';
                } elseif ($cType === 'text/plain') {
                    $plainPartId = $part['partId'] ?? '';
                }
            }
        }
        
        $partToFetch = $htmlPartId !== '' ? $htmlPartId : ($plainPartId !== '' ? $plainPartId : '');
        $isHtml = ($partToFetch === $htmlPartId);
        
        $htmlContent = '';
        $textContent = '';
        
        if ($partToFetch !== '') {
            try {
                $mboxId = '';
                if (isset($msg['mboxRef'])) {
                    if (is_array($msg['mboxRef'])) {
                        $mboxId = (string)($msg['mboxRef']['id'] ?? '');
                    } else {
                        $mboxId = (string)$msg['mboxRef'];
                    }
                }
                
                $folderName = 'INBOX';
                if ($mboxId === '33') {
                    $folderName = 'Junk';
                } elseif ($mboxId === '32') {
                    $folderName = 'Trash';
                } elseif ($mboxId === '35') {
                    $folderName = 'Archive';
                }

                $msgBody = [
                    "uid" => $uid,
                    "parts" => [$partToFetch],
                    "sessionHeaders" => [
                        "folder" => $folderName,
                        "threadmode" => 1
                    ]
                ];
                $msgRes = mailws2_call($account, 'message/get', $msgBody);
                if ($msgRes['status'] === 200 && !empty($msgRes['data']['parts'])) {
                    $content = $msgRes['data']['parts'][0]['content'] ?? '';
                    if ($isHtml) {
                        $htmlContent = $content;
                    } else {
                        $textContent = $content;
                    }
                }
            } catch (Throwable $e) {
                error_log('[universal.thread.message] ' . $e->getMessage());
                $htmlContent = '<p class="text-red-500">Gagal memuat isi pesan: ' . htmlspecialchars($e->getMessage()) . '</p>';
            }
        }
        
        $dateStr = '';
        if (isset($msg['stateInternalDate'])) {
            $dateStr = date('c', intval($msg['stateInternalDate']));
        }
        
        $outputMessages[] = [
            'id'      => $uid,
            'date'    => $dateStr,
            'subject' => $msg['subject'] ?? '(Tanpa Subjek)',
            'from'    => $msg['from'] ?? 'Unknown',
            'to'      => $msg['to'] ?? '',
            'cc'      => $msg['cc'] ?? '',
            'text'    => $textContent,
            'html'    => $htmlContent,
        ];
    }
    
    return $outputMessages;
}

function fetch_filtered_inbox(array $account, string $hmeEmail): array {
    $body = [
        "domain" => "email",
        "properties" => [
            "uid", "appleThrid", "identifier", "flags", "stateInternalDate", "rfc822Size",
            "hasAttachment", "messageId", "mboxRef", "previewId", "parts", "isSeen", "modseq",
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "From",
                "alias" => "from", "decoder" => "encodedWords"
            ],
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "To",
                "decoder" => "encodedWords", "alias" => "to"
            ],
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "Cc",
                "decoder" => "encodedWords", "alias" => "cc"
            ],
            [
                "type" => "fieldOf", "property" => "rfc822HeaderBytes", "value" => "Subject",
                "decoder" => "encodedWords", "alias" => "subject"
            ]
        ],
        "limit" => 200,
        "predicate" => [
            "expression" => ["property" => "flags", "value" => "DELETED", "type" => "fieldOf"],
            "value" => false,
            "type" => "eq"
        ],
        "orderby" => [
            "expressions" => [
                ["property" => "stateInternalDate", "type" => "property"]
            ],
            "ascending" => false
        ]
    ];
    
    $res = mailws2_call($account, 'message/list?clientIntent=fetchMessageList', $body);
    if ($res['status'] >= 400) {
        throw new RuntimeException('iCloud Web Mail session expired or unauthorized (HTTP ' . $res['status'] . ')');
    }
    if (empty($res['data']['domainObjects'])) {
        return [];
    }
    
    $messages = $res['data']['domainObjects'];
    $filtered = [];
    $targetEmail = strtolower(trim($hmeEmail));
    
    foreach ($messages as $msg) {
        $toStr = strtolower($msg['to'] ?? '');
        $ccStr = strtolower($msg['cc'] ?? '');
        
        $emailsFound = [];
        if (preg_match_all('/[a-zA-Z0-9\._%+-]+@[a-zA-Z0-9\.-]+\.[a-zA-Z]{2,}/', $toStr . ' ' . $ccStr, $matches)) {
            foreach ($matches[0] as $match) {
                $emailsFound[] = strtolower(trim($match));
            }
        }
        
        if (!in_array($targetEmail, $emailsFound, true)) {
            continue;
        }
        
        $uid = $msg['uid'] ?? '';
        if ($uid === '') continue;
        
        $htmlPartId = '';
        $plainPartId = '';
        if (!empty($msg['parts'])) {
            foreach ($msg['parts'] as $part) {
                $cType = strtolower($part['contentType'] ?? '');
                if ($cType === 'text/html') {
                    $htmlPartId = $part['partId'] ?? '';
                } elseif ($cType === 'text/plain') {
                    $plainPartId = $part['partId'] ?? '';
                }
            }
        }
        
        $partToFetch = $htmlPartId !== '' ? $htmlPartId : ($plainPartId !== '' ? $plainPartId : '');
        $isHtml = ($partToFetch === $htmlPartId);
        
        $htmlContent = '';
        $textContent = '';
        
        if ($partToFetch !== '') {
            try {
                $mboxId = '';
                if (isset($msg['mboxRef'])) {
                    if (is_array($msg['mboxRef'])) {
                        $mboxId = (string)($msg['mboxRef']['id'] ?? '');
                    } else {
                        $mboxId = (string)$msg['mboxRef'];
                    }
                }
                
                $folderName = 'INBOX';
                if ($mboxId === '33') {
                    $folderName = 'Junk';
                } elseif ($mboxId === '32') {
                    $folderName = 'Trash';
                } elseif ($mboxId === '35') {
                    $folderName = 'Archive';
                }
                
                $msgBody = [
                    "uid" => $uid,
                    "parts" => [$partToFetch],
                    "sessionHeaders" => [
                        "folder" => $folderName,
                        "threadmode" => 1
                    ]
                ];
                $msgRes = mailws2_call($account, 'message/get', $msgBody);
                if ($msgRes['status'] === 200 && !empty($msgRes['data']['parts'])) {
                    $content = $msgRes['data']['parts'][0]['content'] ?? '';
                    if ($isHtml) {
                        $htmlContent = $content;
                    } else {
                        $textContent = $content;
                    }
                }
            } catch (Throwable $e) {
                error_log('[universal.filtered.message] ' . $e->getMessage());
                $htmlContent = '<p class="text-red-500">Gagal memuat isi pesan: ' . htmlspecialchars($e->getMessage()) . '</p>';
            }
        }
        
        $dateStr = '';
        if (isset($msg['stateInternalDate'])) {
            $dateStr = date('c', intval($msg['stateInternalDate']));
        }
        
        $filtered[] = [
            'id'      => $uid,
            'date'    => $dateStr,
            'subject' => $msg['subject'] ?? '(Tanpa Subjek)',
            'from'    => $msg['from'] ?? 'Unknown',
            'to'      => $msg['to'] ?? '',
            'cc'      => $msg['cc'] ?? '',
            'text'    => $textContent,
            'html'    => $htmlContent,
        ];
    }
    
    return $filtered;
}

// ============== ROUTING ==============
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';

// Tentukan path API: prefer ?path=, fallback parse REQUEST_URI untuk pola /api/...
$path = '';
if (isset($_GET['path'])) {
    $path = (string)$_GET['path'];
} else {
    $uri = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?: '';
    $script = $_SERVER['SCRIPT_NAME'] ?? '';
    $base = rtrim(str_replace('\\', '/', dirname($script)), '/');
    if ($base !== '' && strpos($uri, $base) === 0) $uri = substr($uri, strlen($base));
    $uri = '/' . ltrim($uri, '/');
    if (preg_match('#^/api/(.+)$#', $uri, $m)) $path = $m[1];
}
$path = trim($path, '/');
// Terima ?page=admin (URL bersih) atau ?view=admin (kompatibilitas lama)
$view = $_GET['page'] ?? ($_GET['view'] ?? '');

// Header umum keamanan
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: same-origin');

// CORS untuk extension (origin: extension://...)
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ($origin) {
    header('Access-Control-Allow-Origin: ' . $origin);
    header('Vary: Origin');
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, x-access-code, x-account-id');
}
if ($method === 'OPTIONS') { http_response_code(204); exit; }

// Render HTML jika tidak ada API path
if ($path === '') {
    render_html(in_array($view, ['admin', 'inbox']) ? $view : 'home');
    exit;
}

// === API DISPATCH ===
try {
    $body      = read_json_body();
    $accountId = $_SERVER['HTTP_X_ACCOUNT_ID'] ?? ($_GET['accountId'] ?? ($body['accountId'] ?? ''));

    // Public endpoint
    if ($path === 'accounts' && $method === 'GET') {
        require_member_token();
        $list = array_map('public_view', read_accounts()['accounts']);
        ok($list);
    }
    // ===== Auth member (voucher) =====
    if ($path === 'auth/redeem' && $method === 'POST') {
        $code = (string)($body['code'] ?? '');
        $deviceId = (string)($body['deviceId'] ?? '');
        $deviceInfo = is_array($body['deviceInfo'] ?? null) ? $body['deviceInfo'] : [];
        $res = redeem_voucher_code($code, $deviceId, $deviceInfo);
        ok($res);
    }
    if ($path === 'auth/me' && $method === 'GET') {
        $m = require_member_token();
        ok([
            'memberName'    => $m['name'] ?? '',
            'voucherCode'   => $m['voucherCode'] ?? '',
            'expiresAt'     => $m['expiresAt'],
            'remainingDays' => (int)ceil((strtotime($m['expiresAt']) - time()) / 86400),
            'dailyLimit'    => isset($m['dailyLimit']) ? (int)$m['dailyLimit'] : 15,
            'autoDeleteMin' => isset($m['autoDeleteMin']) ? (int)$m['autoDeleteMin'] : 10,
            'usedToday'     => count_today_for_member($m['id']),
        ]);
    }
    if ($path === 'generate' && $method === 'POST') {
        $member = require_member_token();
        // Cek limit harian member
        $limit = isset($member['dailyLimit']) ? (int)$member['dailyLimit'] : 15;
        if ($limit > 0) {
            $used = count_today_for_member($member['id']);
            if ($used >= $limit) {
                err(429, 'Batas pembuatan email harian tercapai (' . $used . '/' . $limit . '). Hapus email lama atau tunggu reset besok.', [
                    'memberLimitReached' => true,
                    'usedToday' => $used,
                    'dailyLimit' => $limit,
                ]);
            }
        }
        // Sweep ringan opportunistik
        maybe_run_sweep();
        
        $acc = resolve_account((string)$accountId);
        $prefix = isset($body['prefix']) ? trim((string)$body['prefix']) : '';
        $custom_email = generate_custom_email($prefix);
        $anonId = 'custom_' . md5($custom_email);
        
        // Auto-track langsung untuk custom domain (auto-delete)
        $ttl = isset($member['autoDeleteMin']) ? (int)$member['autoDeleteMin'] : 10;
        $expiresAt = $ttl > 0 ? date('c', time() + $ttl * 60) : null;
        add_tracked($member['id'], [
            'accountId'   => $acc['id'],
            'hme'         => $custom_email,
            'anonymousId' => $anonId,
            'label'       => '',
            'note'        => 'Custom Domain Email',
            'expiresAt'   => $expiresAt,
        ]);
        
        ok(null, [
            'accountId' => $acc['id'],
            'data' => [
                'success' => true,
                'result' => [
                    'hme' => $custom_email
                ]
            ]
        ]);
    }
    if ($path === 'reserve' && $method === 'POST') {
        $member = require_member_token();
        $acc = resolve_account((string)$accountId);
        $hme = (string)($body['hme'] ?? '');
        $label = (string)($body['label'] ?? '');
        $note = (string)($body['note'] ?? '');
        
        // Jika custom domain, lewati iCloud reserve (sudah ditambahkan saat generate)
        if ($hme !== '' && is_custom_domain($hme)) {
            ok(null, [
                'accountId' => $acc['id'],
                'data' => [
                    'success' => true,
                    'result' => [
                        'hme' => [
                            'hme' => $hme,
                            'anonymousId' => 'custom_' . md5($hme)
                        ]
                    ]
                ]
            ]);
        }
        
        if ($hme === '' || $label === '') err(400, 'Parameter "hme" (email) dan "label" wajib diisi');
        $r = icloud_call($acc, 'v1', 'reserve', ['hme' => $hme, 'label' => $label, 'note' => $note]);
        $d = $r['data'];
        if (isset($d['success']) && $d['success'] === false) {
            err(400, $d['error']['errorMessage'] ?? 'Gagal dari sisi server iCloud', ['details' => $d]);
        }
        if ($r['status'] >= 400) err($r['status'], 'Gagal mereservasi email', ['details' => $d]);

        // Auto-track utk auto-delete server-side
        $anonId = $d['result']['hme']['anonymousId'] ?? '';
        if ($anonId !== '') {
            $ttl = isset($member['autoDeleteMin']) ? (int)$member['autoDeleteMin'] : 10;
            $expiresAt = $ttl > 0 ? date('c', time() + $ttl * 60) : null;
            add_tracked($member['id'], [
                'accountId'   => $acc['id'],
                'hme'         => $hme,
                'anonymousId' => $anonId,
                'label'       => $label,
                'note'        => $note,
                'expiresAt'   => $expiresAt,
            ]);
        }
        ok(null, ['accountId' => $acc['id'], 'data' => $d]);
    }
    if ($path === 'get' && $method === 'POST') {
        require_member_token();
        $acc = resolve_account((string)$accountId);
        $aid = (string)($body['anonymousId'] ?? '');
        if ($aid === '') err(400, 'Parameter "anonymousId" wajib diisi');
        
        // Mock get untuk custom domain
        if (str_starts_with($aid, 'custom_')) {
            // Find in tracked emails
            $foundEmail = '';
            foreach (read_tracked()['emails'] as $e) {
                if (($e['anonymousId'] ?? '') === $aid) {
                    $foundEmail = $e['hme'] ?? '';
                    break;
                }
            }
            ok(null, [
                'accountId' => $acc['id'],
                'data' => [
                    'hme' => [
                        'hme' => $foundEmail,
                        'anonymousId' => $aid,
                        'label' => 'Custom Domain',
                        'note' => 'Custom Domain Email'
                    ]
                ]
            ]);
        }
        
        $r = icloud_call($acc, 'v2', 'get', ['anonymousId' => $aid]);
        if ($r['status'] >= 400) err($r['status'], 'Gagal mengambil detail email', ['details' => $r['data']]);
        ok(null, ['accountId' => $acc['id'], 'data' => $r['data']]);
    }
    if ($path === 'list' && $method === 'GET') {
        require_member_token();
        $acc = resolve_account((string)$accountId);
        $r = icloud_call($acc, 'v2', 'list', null, 'GET');
        if ($r['status'] >= 400) err($r['status'], 'Gagal mengambil daftar email', ['details' => $r['data']]);
        ok(null, ['accountId' => $acc['id'], 'data' => $r['data']]);
    }
    // Deactivate (hapus / nonaktifkan) satu HME
    // Default: deactivate + delete (permanen). Pass {soft: true} untuk hanya deactivate.
    if ($path === 'deactivate' && $method === 'POST') {
        $member = require_member_token();
        $aid = (string)($body['anonymousId'] ?? '');
        $hme = trim((string)($body['hme'] ?? ''));
        if ($aid === '') err(400, 'Parameter "anonymousId" wajib diisi');

        // Cek jika custom domain
        if (($hme !== '' && is_custom_domain($hme)) || str_starts_with($aid, 'custom_')) {
            if ($hme === '') {
                foreach (read_tracked()['emails'] as $e) {
                    if (($e['anonymousId'] ?? '') === $aid) {
                        $hme = $e['hme'] ?? '';
                        break;
                    }
                }
            }
            if ($hme !== '') remove_tracked($member['id'], $hme);
            ok(null, ['accountId' => (string)$accountId, 'data' => ['success' => true], 'permanent' => true]);
        }

        // Cari akun yang tepat dari tracked_emails.json secara dinamis berdasarkan email HME jika tersedia
        $targetAccountId = '';
        if ($hme !== '') {
            foreach (read_tracked()['emails'] as $e) {
                if (strcasecmp(($e['hme'] ?? ''), $hme) === 0) {
                    $targetAccountId = $e['accountId'] ?? '';
                    break;
                }
            }
        }
        
        if ($targetAccountId !== '') {
            $acc = resolve_account($targetAccountId);
        } else {
            $acc = resolve_account((string)$accountId);
        }
        $soft = !empty($body['soft']);

        // Step 1: deactivate
        $r1 = icloud_call($acc, 'v1', 'deactivate', ['anonymousId' => $aid]);
        if ($r1['status'] >= 400) err($r1['status'], 'Gagal menonaktifkan email', ['details' => $r1['data']]);

        if ($soft) {
            if ($hme !== '') remove_tracked($member['id'], $hme);
            ok(null, ['accountId' => $acc['id'], 'data' => $r1['data'], 'permanent' => false]);
        }

        // Step 2: delete permanen (membebaskan kuota harian)
        $r2 = icloud_call($acc, 'v1', 'delete', ['anonymousId' => $aid]);
        if ($r2['status'] >= 400) {
            err($r2['status'], 'Email dinonaktifkan tapi gagal dihapus permanen', [
                'details' => $r2['data'],
                'partial' => true
            ]);
        }
        if ($hme !== '') remove_tracked($member['id'], $hme);
        ok(null, ['accountId' => $acc['id'], 'data' => $r2['data'], 'permanent' => true]);
    }
    // Cleanup massal: deactivate + delete banyak HME sekaligus
    if ($path === 'cleanup' && $method === 'POST') {
        $member = require_member_token();
        $acc = resolve_account((string)$accountId);
        $ids = $body['anonymousIds'] ?? [];
        // Optional: pasangan hme array supaya bisa drop dari tracking
        $hmes = is_array($body['hmes'] ?? null) ? $body['hmes'] : [];
        if (!is_array($ids) || empty($ids)) err(400, 'Parameter "anonymousIds" (array) wajib diisi');
        $soft = !empty($body['soft']);

        $results = ['ok' => [], 'failed' => []];
        
        // Map tracked emails for fast lookup
        $trackedEmails = [];
        foreach (read_tracked()['emails'] as $e) {
            if (($e['memberId'] ?? '') === $member['id']) {
                $trackedEmails[$e['anonymousId'] ?? ''] = $e['hme'] ?? '';
            }
        }

        foreach ($ids as $i => $rawId) {
            $aid = is_string($rawId) ? trim($rawId) : '';
            if ($aid === '') continue;
            
            $hmeStr = isset($hmes[$i]) ? (string)$hmes[$i] : ($trackedEmails[$aid] ?? '');
            
            if (($hmeStr !== '' && is_custom_domain($hmeStr)) || str_starts_with($aid, 'custom_')) {
                if ($hmeStr !== '') remove_tracked($member['id'], $hmeStr);
                $results['ok'][] = $aid;
                continue;
            }
            
            try {
                $r1 = icloud_call($acc, 'v1', 'deactivate', ['anonymousId' => $aid]);
                if ($r1['status'] >= 400) {
                    $results['failed'][] = ['id' => $aid, 'stage' => 'deactivate', 'status' => $r1['status']];
                    continue;
                }
                if (!$soft) {
                    $r2 = icloud_call($acc, 'v1', 'delete', ['anonymousId' => $aid]);
                    if ($r2['status'] >= 400) {
                        $results['failed'][] = ['id' => $aid, 'stage' => 'delete', 'status' => $r2['status']];
                        continue;
                    }
                }
                $results['ok'][] = $aid;
                if ($hmeStr !== '') remove_tracked($member['id'], $hmeStr);
            } catch (Throwable $e) {
                $results['failed'][] = ['id' => $aid, 'error' => $e->getMessage()];
            }
            usleep(150000); // 150 ms throttle antar item
        }
        ok($results, ['accountId' => $acc['id']]);
    }
    // Daftar tracked emails milik member yang login (untuk sinkronisasi cross-device)
    if ($path === 'tracked' && $method === 'GET') {
        $member = require_member_token();
        maybe_run_sweep();
        ok([
            'emails'     => list_tracked_for_member($member['id']),
            'usedToday'  => count_today_for_member($member['id']),
            'dailyLimit' => isset($member['dailyLimit']) ? (int)$member['dailyLimit'] : 15,
            'autoDeleteMin' => isset($member['autoDeleteMin']) ? (int)$member['autoDeleteMin'] : 10,
        ]);
    }
    // Pin / unpin satu email tracked
    if ($path === 'tracked/pin' && $method === 'POST') {
        $member = require_member_token();
        $hme = (string)($body['hme'] ?? '');
        $pinned = !empty($body['pinned']);
        if ($hme === '') err(400, 'Parameter "hme" wajib diisi');
        $changes = ['pinned' => $pinned];
        if (!$pinned) {
            // Saat unpin, kembalikan jadwal expiresAt = now + autoDeleteMin
            $ttl = isset($member['autoDeleteMin']) ? (int)$member['autoDeleteMin'] : 10;
            $changes['expiresAt'] = $ttl > 0 ? date('c', time() + $ttl * 60) : null;
        }
        $updated = update_tracked($member['id'], $hme, $changes);
        if (!$updated) err(404, 'Email tidak ditemukan di daftar tracking');
        ok(tracked_view($updated));
    }
    // Trigger sweep manual (dipakai client untuk hasil instan)
    if ($path === 'tracked/sweep' && $method === 'POST') {
        require_member_token();
        // Force sweep dengan reset lock
        global $SWEEP_LOCK_FILE;
        @unlink($SWEEP_LOCK_FILE);
        maybe_run_sweep();
        ok(['done' => true]);
    }
    // Hapus 1 entry tracking (dipanggil setelah deactivate sukses)
    if ($path === 'tracked/forget' && $method === 'POST') {
        $member = require_member_token();
        $hme = (string)($body['hme'] ?? '');
        if ($hme === '') err(400, 'Parameter "hme" wajib diisi');
        remove_tracked($member['id'], $hme);
        ok(['done' => true]);
    }

    if ($path === 'inbox' && $method === 'POST') {
        require_member_token();
        $hmeEmail = trim((string)($body['hmeEmail'] ?? ''));
        if ($hmeEmail === '') err(400, 'Parameter "hmeEmail" wajib diisi');

        // Cari akun yang tepat dari tracked_emails.json secara dinamis berdasarkan email HME
        $targetAccountId = '';
        foreach (read_tracked()['emails'] as $e) {
            if (strcasecmp(($e['hme'] ?? ''), $hmeEmail) === 0) {
                $targetAccountId = $e['accountId'] ?? '';
                break;
            }
        }
        
        if ($targetAccountId !== '') {
            $acc = resolve_account($targetAccountId);
        } else {
            $acc = resolve_account((string)$accountId);
        }

        $msgs = [];
        $sessionValid = false;

        // 1. Coba Web Mail API terlebih dahulu karena sangat cepat dan ringan via HTTPS (port 443)
        try {
            $msgs = fetch_filtered_inbox($acc, $hmeEmail);
            $sessionValid = true; // Sesi web aktif
        } catch (Throwable $e) {
            error_log('[inbox.web.failed] Web Mail API failed: ' . $e->getMessage());
        }

        // 2. Jika sesi web tidak valid (cookies kedaluwarsa) dan kredensial IMAP tersedia, gunakan IMAP sebagai fallback
        if (!$sessionValid) {
            $useImap = (!empty($acc['email']) && !empty($acc['appPassword']));
            if ($useImap) {
                try {
                    $msgs = imap_fetch_inbox($acc, $hmeEmail);
                } catch (Throwable $e) {
                    error_log('[inbox.imap.failed] IMAP fallback failed: ' . $e->getMessage());
                    $msgs = [];
                }
            }
        }
        
        ok(null, ['accountId' => $acc['id'], 'data' => $msgs]);
    }

    if ($path === 'inbox/universal' && $method === 'GET') {
        require_member_token();
        $acc = resolve_account((string)$accountId);
        $msgs = fetch_universal_inbox($acc);
        ok($msgs);
    }

    if ($path === 'inbox/universal/thread' && $method === 'POST') {
        require_member_token();
        $threadId = (string)($body['threadId'] ?? '');
        if ($threadId === '') err(400, 'Parameter "threadId" wajib diisi');
        $acc = resolve_account((string)$accountId);
        $msgs = fetch_universal_thread($acc, $threadId);
        ok($msgs);
    }

    // Admin endpoints
    if ($path === 'admin/accounts' && $method === 'GET') {
        require_admin();
        ok(array_map('admin_view', read_accounts()['accounts']));
    }
    if ($path === 'admin/accounts' && $method === 'POST') {
        require_admin();
        ok(create_account($body));
    }
    if (preg_match('#^admin/accounts/([a-z0-9]+)$#i', $path, $m)) {
        require_admin();
        if ($method === 'PUT')    ok(update_account($m[1], $body));
        if ($method === 'DELETE') { delete_account($m[1]); ok(); }
    }

    // ===== Admin: Member (terpadu) =====
    if ($path === 'admin/members' && $method === 'GET') {
        require_admin();
        $list = array_map('member_view_admin', read_members()['members']);
        usort($list, function ($a, $b) { return strcmp($b['createdAt'] ?? '', $a['createdAt'] ?? ''); });
        ok($list);
    }
    if ($path === 'admin/members' && $method === 'POST') {
        require_admin();
        ok(member_view_admin(create_member($body)));
    }
    if (preg_match('#^admin/members/([a-z0-9]+)$#i', $path, $m)) {
        require_admin();
        if ($method === 'PUT')    ok(member_view_admin(update_member($m[1], $body)));
        if ($method === 'DELETE') { delete_member($m[1]); ok(); }
    }

    if ($path === 'health' && $method === 'GET') {
        ok([
            'phpVersion'   => PHP_VERSION,
            'extensions'   => [
                'curl'      => extension_loaded('curl'),
                'json'      => extension_loaded('json'),
                'imap'      => extension_loaded('imap'),
                'mbstring'  => extension_loaded('mbstring'),
            ],
            'accounts'     => count(read_accounts()['accounts']),
            'members'      => count(read_members()['members']),
            'accountsFile' => $ACCOUNTS_FILE,
            'writable'     => is_writable(dirname($ACCOUNTS_FILE)),
            'time'         => date('c'),
            'version'      => $APP_VERSION,
        ]);
    }

    err(404, 'Endpoint tidak dikenal: ' . $path);

} catch (InvalidArgumentException $e) {
    err(400, $e->getMessage());
} catch (Throwable $e) {
    error_log('[hme] ' . $e->getMessage());
    err(500, $e->getMessage());
}

// ============== HTML RENDER ==============
function render_html(string $view): void {
    $missing = ext_check();
    $script = htmlspecialchars($_SERVER['SCRIPT_NAME'] ?? '/icloud-hme.php', ENT_QUOTES);
    if ($view === 'admin') {
        admin_html($script, $missing);
    } elseif ($view === 'inbox') {
        inbox_html($script, $missing);
    } else {
        home_html($script, $missing);
    }
}

function ext_warning_html(array $missing): string {
    return '<div class="bg-yellow-50 border-l-4 border-yellow-400 p-3 text-xs text-yellow-800 mb-2">'
        . '<b>Peringatan:</b> Gunakan dengan bijak. Jika tidak, akses anda akan kami batasi.'
        . '</div>';
}

function home_html(string $script, array $missing): void {
    ?><!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iCloud Hide My Email Generator</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center p-4">
<a href="?page=admin" class="fixed top-4 right-4 z-10 bg-white text-blue-600 hover:text-blue-800 hover:bg-blue-50 font-medium text-sm px-3 py-1.5 rounded-md shadow border border-gray-200 transition">⚙️ Admin</a>

<!-- LOGIN OVERLAY (member voucher) -->
<div id="login-overlay" class="hidden bg-white p-8 rounded-lg shadow-md w-full max-w-md">
    <div class="flex justify-center mb-6">
        <svg class="w-12 h-12 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0-1.105.895-2 2-2s2 .895 2 2-.895 2-2 2-2-.895-2-2zM5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
    </div>
    <h1 class="text-2xl font-bold text-center text-gray-800 mb-2">Akses Member</h1>
    <p class="text-sm text-gray-500 text-center mb-6">Masukkan kode voucher untuk mengakses layanan Hide My Email.</p>
    <form id="login-form" class="space-y-3">
        <div class="relative">
            <input type="password" id="login-code" autocomplete="off" spellcheck="false"
                class="block w-full border-gray-300 border rounded-md shadow-sm p-3 pr-12 text-center font-mono uppercase tracking-wider focus:ring-blue-500 focus:border-blue-500"
                placeholder="HME-XXXX-XXXX-XXXX" required>
            <button type="button" data-pw-toggle="login-code"
                class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition" title="Tampilkan / Sembunyikan">
                <svg class="eye-show w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
                <svg class="eye-hide w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.542-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.542 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>
            </button>
        </div>
        <button type="submit" id="login-submit" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition">Masuk</button>
    </form>
    <div id="login-message" class="hidden mt-4 p-3 rounded text-sm text-center font-medium"></div>
    <p class="text-xs text-gray-400 text-center mt-6">Belum punya voucher? Hubungi admin via WhatsApp.</p>
</div>

<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md relative transition-all duration-300" id="main-card">
    <?= ext_warning_html($missing) ?>
    <div class="flex justify-center mb-6 mt-2">
        <svg class="w-12 h-12 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
    </div>
    <h1 class="text-2xl font-bold text-center text-gray-800 mb-4">Hide My Email</h1>

    <div id="session-bar" class="hidden mb-4 flex items-center justify-between bg-blue-50 border border-blue-100 rounded px-3 py-2 text-xs">
        <div class="text-blue-900">
            <span class="font-semibold">Aktif</span>
            <span id="session-meta" class="text-blue-700"></span>
        </div>
        <button id="btn-logout" class="text-red-600 hover:text-red-800 font-semibold">Keluar</button>
    </div>

    <div id="account-picker-wrap" class="mb-4">
        <label class="block text-xs font-medium text-gray-600 mb-1">Akun iCloud Aktif</label>
        <select id="account-picker" class="block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white"></select>
        <p id="no-account-hint" class="hidden text-xs text-red-600 mt-2">Belum ada akun. Tambahkan via <a href="?page=admin" class="underline">Admin</a>.</p>
    </div>

    <div id="step-generate" class="space-y-4">
        <p class="text-sm text-gray-600 text-center">Hasilkan alamat email unik dan acak yang terhubung ke akun iCloud Anda.</p>
        
        <div>
            <label class="block text-xs font-medium text-gray-600 mb-1">Prefix Email Custom (Opsional)</label>
            <input type="text" id="custom-prefix" class="block w-full border-gray-300 border rounded-md shadow-sm p-2 text-sm focus:ring-blue-500 focus:border-blue-500" placeholder="Contoh: teknganteng">
            <p class="text-[10px] text-gray-400 mt-1">Jika kosong, email akan otomatis di-generate secara acak (contoh: teknganteng001).</p>
        </div>

        <button id="btn-generate" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2.5 px-4 rounded transition duration-200">Generate Email Acak</button>
        <button id="btn-show-saved" class="w-full bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-2.5 px-4 rounded transition duration-200 mt-2">Lihat Email Tersimpan</button>
        <a href="inbox" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2.5 px-4 rounded transition duration-200 mt-2 flex items-center justify-center gap-2 shadow-sm text-center text-sm">
            <span>📥</span>
            <span>Buka Universal Inbox</span>
        </a>


    </div>

    <div id="step-saved" class="hidden space-y-4 mt-6 border-t pt-4">
        <div class="flex items-center justify-between gap-2 flex-wrap">
            <h2 class="text-lg font-bold text-gray-800">Email Tersimpan <span id="saved-count" class="text-xs font-normal text-gray-500"></span></h2>
            <button id="btn-cleanup-all" class="hidden text-xs bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 font-semibold px-2 py-1 rounded transition" title="Hapus semua email tersimpan dari iCloud">Hapus Semua</button>
        </div>
        <div class="bg-blue-50 border border-blue-100 rounded-md p-3 text-xs text-blue-900 flex items-center gap-2 flex-wrap">
            <span>⏱</span>
            <span class="flex-1 min-w-[180px]"><b>Auto-delete:</b> <span id="auto-delete-info">10 menit</span> setelah email dibuat. Pin email agar tidak terhapus.</span>
        </div>
        <div id="saved-emails-list" class="space-y-3 max-h-64 overflow-y-auto border border-gray-200 rounded p-2 bg-gray-50">
            <p class="text-sm text-gray-500 text-center py-4">Belum ada email yang tersimpan.</p>
        </div>
        <button id="btn-back-from-saved" class="w-full bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-2 px-4 rounded transition duration-200 mt-4">Kembali</button>
    </div>

    <div id="step-reserve" class="hidden space-y-4 mt-6 border-t pt-4">
        <div>
            <label class="block text-sm font-medium text-gray-700 text-center">Email yang dihasilkan:</label>
            <div class="mt-2">
                <input type="text" id="generated-email" readonly class="block w-full border-gray-300 rounded-md shadow-sm bg-gray-50 p-3 font-mono text-blue-700 text-center text-lg font-bold" value="">
            </div>
        </div>
        <button id="btn-copy-and-inbox" class="w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-3 px-4 rounded transition duration-200 text-base shadow flex items-center justify-center gap-2">
            <span>📋</span> Salin & Buka Inbox
        </button>
        <button id="btn-cancel" class="w-full bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-2 px-4 rounded transition duration-200 text-sm">Batal</button>
    </div>

    <div id="step-inbox" class="hidden space-y-4 mt-6 border-t pt-4">
        <div>
            <h2 class="text-lg font-bold text-gray-800">Inbox</h2>
            <p class="text-sm text-gray-600 mb-2 flex items-center gap-2 flex-wrap">
                <span>Email:</span>
                <span id="inbox-email-display" class="font-mono text-blue-600 break-all"></span>
                <button type="button" id="btn-copy-inbox-email" title="Salin email" class="px-2 py-0.5 bg-blue-50 hover:bg-blue-100 text-blue-700 border border-blue-200 rounded text-xs font-semibold transition">Salin</button>
            </p>
            <button id="btn-refresh-inbox" class="w-full bg-blue-100 hover:bg-blue-200 text-blue-800 font-semibold py-2 px-4 rounded transition duration-200 text-sm mb-4">Refresh Inbox</button>
        </div>
        <div id="inbox-messages" class="space-y-3 max-h-[70vh] overflow-y-auto border border-gray-200 rounded p-2 bg-gray-50">
            <p class="text-sm text-gray-500 text-center py-4">Belum ada pesan.</p>
        </div>
        <button id="btn-back-home" class="w-full bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-2 px-4 rounded transition duration-200 mt-4">Kembali</button>
    </div>

    <div id="loading" class="hidden mt-4 text-center">
        <svg class="animate-spin h-5 w-5 text-blue-500 mx-auto" viewBox="0 0 24 24" fill="none">
            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        <p class="text-sm text-gray-500 mt-2" id="loading-text">Memproses...</p>
    </div>
    <div id="message" class="hidden mt-4 p-3 rounded text-sm text-center font-medium"></div>
</div>

<!-- LIMIT REACHED MODAL -->
<div id="limit-modal" class="hidden fixed inset-0 z-50 bg-black bg-opacity-50 flex items-center justify-center p-4">
    <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
        <div class="flex items-center gap-3 mb-3">
            <div class="flex-shrink-0 w-10 h-10 rounded-full bg-yellow-100 flex items-center justify-center">
                <svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
            </div>
            <h3 class="text-lg font-semibold text-gray-900">Batas Harian Tercapai</h3>
        </div>
        <p id="limit-modal-msg" class="text-sm text-gray-600 mb-4">Anda telah mencapai batas pembuatan email harian. Hapus beberapa email yang tidak terpakai untuk melanjutkan.</p>
        <div class="bg-blue-50 border border-blue-100 rounded p-3 text-xs text-blue-900 mb-4">
            <b>Saran:</b> Buka <b>Lihat Email Tersimpan</b>, pilih email yang sudah tidak dipakai, lalu klik tombol Hapus untuk membebaskan kuota.
        </div>
        <div class="flex flex-col sm:flex-row gap-2">
            <button id="limit-go-saved" class="flex-1 bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition">Buka Email Tersimpan</button>
            <a href="https://www.icloud.com/icloudplus/" target="_blank" class="flex-1 bg-gray-100 hover:bg-gray-200 text-gray-800 font-semibold py-2 px-4 rounded transition text-center">Buka iCloud+</a>
        </div>
        <button id="limit-close" class="w-full mt-2 text-xs text-gray-500 hover:text-gray-700 py-2">Tutup</button>
    </div>
</div>

<script>window.HME_CFG = { script: <?= json_encode($script) ?> };</script>
<script><?php echo home_js(); ?></script>
</body>
</html>
<?php
}

function admin_html(string $script, array $missing): void {
?><!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin - Pengaturan iCloud</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100 min-h-screen p-4">
<div class="max-w-5xl mx-auto bg-white rounded-lg shadow-md p-6 md:p-8">
    <div class="flex items-center justify-between mb-6 flex-wrap gap-2">
        <h1 class="text-2xl font-bold text-gray-800">Pengaturan Akun iCloud</h1>
        <a href="./" class="text-blue-500 hover:underline text-sm font-medium">⬅ Kembali ke Home</a>
    </div>
    <?= ext_warning_html($missing) ?>

    <div id="login-section" class="max-w-md mx-auto">
        <p class="text-sm text-gray-600 mb-6 text-center">Masukkan Access Code untuk membuka pengaturan.</p>
        <form id="login-form" class="space-y-4">
            <div class="relative">
                <input type="password" id="access-code" class="block w-full border-gray-300 border rounded-md shadow-sm p-2 pr-10 focus:ring-blue-500 focus:border-blue-500 text-center font-mono" placeholder="Access Code" required>
                <button type="button" data-pw-toggle="access-code" class="absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition" title="Tampilkan / Sembunyikan">
                    <svg class="eye-show w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
                    <svg class="eye-hide w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.542-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.542 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>
                </button>
            </div>
            <button type="submit" id="btn-login" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition duration-200">Masuk</button>
        </form>
        <div id="login-message" class="hidden mt-4 p-3 rounded text-sm text-center font-medium"></div>
    </div>

    <div id="admin-section" class="hidden">
        <div class="mb-4 p-3 rounded-md bg-blue-50 border border-blue-200 text-sm text-blue-900 flex items-start gap-2">
            <span class="text-lg leading-none">⚡</span>
            <div class="flex-1">
                <div class="font-semibold">Tip: Pakai Extension untuk auto-capture</div>
                <div class="text-xs text-blue-800 mt-1">Install extension dari folder <span class="font-mono bg-white px-1 rounded">extension/</span> di proyek (Chrome/Edge: chrome://extensions → Developer mode → Load unpacked). Login iCloud Web, buka HME, klik icon extension → Kirim ke Admin.</div>
            </div>
        </div>

        <!-- TAB NAV -->
        <div class="border-b border-gray-200 mb-4 flex gap-1 flex-wrap">
            <button data-tab="accounts" class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-blue-500 text-blue-600">Akun iCloud</button>
            <button data-tab="members"  class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-600 hover:text-gray-900">Member</button>
        </div>

        <!-- TAB: AKUN ICLOUD -->
        <div id="tab-accounts" class="tab-pane">
        <p class="text-sm text-gray-600 mb-4">Tambahkan satu atau lebih akun iCloud. Setiap akun punya DSID, cookies, dan IMAP sendiri, dan bisa dipilih dari halaman utama saat membuat / membuka Hide My Email.</p>

        <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
            <div class="md:col-span-1">
                <div class="flex items-center justify-between mb-2">
                    <h2 class="text-lg font-semibold text-gray-800">Daftar Akun</h2>
                    <button id="btn-new" class="text-xs bg-blue-500 hover:bg-blue-600 text-white font-semibold py-1 px-3 rounded">+ Baru</button>
                </div>
                <div id="account-list" class="space-y-2 max-h-[60vh] overflow-y-auto border border-gray-200 rounded p-2 bg-gray-50">
                    <p class="text-sm text-gray-500 text-center py-4">Belum ada akun.</p>
                </div>
            </div>
            <div class="md:col-span-2">
                <h2 id="form-title" class="text-lg font-semibold text-gray-800 mb-3">Tambah Akun Baru</h2>
                <form id="admin-form" class="space-y-4">
                    <input type="hidden" id="account-id" value="">
                    <div>
                        <label class="block text-sm font-medium text-gray-700">Nama Akun (label)</label>
                        <input type="text" id="acc-name" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Contoh: Akun Utama" required>
                    </div>
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                        <div>
                            <label class="block text-sm font-medium text-gray-700">DSID</label>
                            <input type="text" id="acc-dsid" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500" required>
                        </div>
                        <div>
                            <label class="block text-sm font-medium text-gray-700">Client ID</label>
                            <input type="text" id="acc-clientid" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500" required>
                        </div>
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700">iCloud Cookies</label>
                        <textarea id="acc-cookies" rows="4" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-xs" required></textarea>
                    </div>
                    <div class="border-t pt-4">
                        <h3 class="text-base font-semibold text-gray-800 mb-2">Konfigurasi IMAP (untuk Inbox)</h3>
                        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                            <div>
                                <label class="block text-sm font-medium text-gray-700">Email iCloud</label>
                                <input type="email" id="acc-email" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                            </div>
                            <div>
                                <label class="block text-sm font-medium text-gray-700">App-Specific Password</label>
                                <div class="relative">
                                    <input type="password" id="acc-app-password" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 pr-10 focus:ring-blue-500 focus:border-blue-500" placeholder="Kosongkan untuk tidak mengubah">
                                    <button type="button" data-pw-toggle="acc-app-password" class="absolute right-2 top-1/2 mt-0.5 -translate-y-1/2 p-1 rounded text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition" title="Tampilkan / Sembunyikan">
                                        <svg class="eye-show w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
                                        <svg class="eye-hide w-4 h-4 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.542-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.542 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="border-t pt-4">
                        <label class="block text-sm font-medium text-gray-700">iCloud Subdomain (Opsional)</label>
                        <input type="text" id="acc-subdomain" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500" placeholder="default: p188-maildomainws.icloud.com">
                    </div>
                    <div class="flex gap-2 pt-2">
                        <button type="submit" id="btn-save" class="flex-1 bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition duration-200">Simpan</button>
                        <button type="button" id="btn-delete" class="hidden bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-4 rounded transition duration-200">Hapus</button>
                        <button type="button" id="btn-reset" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-2 px-4 rounded transition duration-200">Bersihkan</button>
                    </div>
                </form>
                <div id="message" class="hidden mt-4 p-3 rounded text-sm text-center font-medium"></div>
            </div>
        </div>
        </div>

        <!-- TAB: MEMBER (terpadu dengan voucher akses) -->
        <div id="tab-members" class="tab-pane hidden">
            <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                <div class="md:col-span-1">
                    <div class="flex items-center justify-between mb-2">
                        <h2 class="text-lg font-semibold text-gray-800">Daftar Member</h2>
                        <button id="btn-member-new" class="text-xs bg-blue-500 hover:bg-blue-600 text-white font-semibold py-1 px-3 rounded">+ Baru</button>
                    </div>
                    <div class="mb-2">
                        <select id="m-filter" class="w-full text-sm border-gray-300 border rounded p-1">
                            <option value="">Semua status</option>
                            <option value="unused">Belum dipakai</option>
                            <option value="active">Aktif</option>
                            <option value="expired">Kedaluwarsa</option>
                            <option value="revoked">Dicabut</option>
                        </select>
                    </div>
                    <div id="member-list" class="space-y-2 max-h-[60vh] overflow-y-auto border border-gray-200 rounded p-2 bg-gray-50">
                        <p class="text-sm text-gray-500 text-center py-4">Belum ada member.</p>
                    </div>
                </div>
                <div class="md:col-span-2">
                    <h2 id="member-form-title" class="text-lg font-semibold text-gray-800 mb-3">Tambah Member Baru</h2>
                    <form id="member-form" class="space-y-4">
                        <input type="hidden" id="member-id" value="">
                        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                            <div>
                                <label class="block text-sm font-medium text-gray-700">Nama</label>
                                <input type="text" id="m-name" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500" required>
                            </div>
                            <div>
                                <label class="block text-sm font-medium text-gray-700">No. WhatsApp</label>
                                <input type="text" id="m-wa" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500" placeholder="08xx atau 62xx">
                            </div>
                        </div>
                        <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
                            <div>
                                <label class="block text-sm font-medium text-gray-700">Durasi</label>
                                <select id="m-duration" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                                    <option value="7">Mingguan (7 hari)</option>
                                    <option value="30" selected>Bulanan (30 hari)</option>
                                    <option value="custom">Custom (hari)…</option>
                                </select>
                                <input type="number" id="m-duration-custom" min="1" max="3650" placeholder="Jumlah hari" class="hidden mt-2 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                            </div>
                            <div>
                                <label class="block text-sm font-medium text-gray-700">Harga (IDR)</label>
                                <input type="number" id="m-price" min="0" placeholder="0" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                            </div>
                            <div>
                                <label class="block text-sm font-medium text-gray-700">Kode voucher</label>
                                <input type="text" id="m-code" placeholder="Kosong = auto-generate" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 font-mono uppercase text-sm focus:ring-blue-500 focus:border-blue-500">
                            </div>
                        </div>
                        <div>
                            <label class="block text-sm font-medium text-gray-700">Catatan (opsional)</label>
                            <textarea id="m-note" rows="2" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
                        </div>
                        <div class="grid grid-cols-1 md:grid-cols-2 gap-4 border-t pt-4">
                            <div>
                                <label class="block text-sm font-medium text-gray-700">Limit Email per Hari</label>
                                <select id="m-daily-limit" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                                    <option value="15" selected>15 email / hari (default)</option>
                                    <option value="0">Unlimited</option>
                                    <option value="custom">Custom…</option>
                                </select>
                                <input type="number" id="m-daily-limit-custom" min="1" max="999" placeholder="Jumlah email" class="hidden mt-2 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                            </div>
                            <div>
                                <label class="block text-sm font-medium text-gray-700">Auto-Delete (menit)</label>
                                <select id="m-autodel" class="mt-1 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                                    <option value="0">OFF (tidak auto-hapus)</option>
                                    <option value="5">5 menit</option>
                                    <option value="10" selected>10 menit (rekomendasi)</option>
                                    <option value="30">30 menit</option>
                                    <option value="60">1 jam</option>
                                    <option value="180">3 jam</option>
                                    <option value="1440">24 jam</option>
                                    <option value="custom">Custom…</option>
                                </select>
                                <input type="number" id="m-autodel-custom" min="1" max="10080" placeholder="Menit" class="hidden mt-2 block w-full border-gray-300 border rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500">
                            </div>
                        </div>
                        <div class="flex gap-2 pt-2 flex-wrap">
                            <button type="submit" id="m-save" class="flex-1 min-w-[120px] bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded transition">Simpan</button>
                            <button type="button" id="m-extend" class="hidden bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded transition">Perpanjang</button>
                            <button type="button" id="m-force-logout" class="hidden bg-orange-500 hover:bg-orange-600 text-white font-semibold py-2 px-4 rounded transition" title="Putuskan sesi semua device">Logout Paksa</button>
                            <button type="button" id="m-revoke" class="hidden bg-yellow-500 hover:bg-yellow-600 text-white font-semibold py-2 px-4 rounded transition">Cabut</button>
                            <button type="button" id="m-unrevoke" class="hidden bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded transition">Aktifkan</button>
                            <button type="button" id="m-resetv" class="hidden bg-gray-300 hover:bg-gray-400 text-gray-800 font-semibold py-2 px-4 rounded transition">Reset Voucher</button>
                            <button type="button" id="m-delete" class="hidden bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-4 rounded transition">Hapus</button>
                            <button type="button" id="m-reset" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-2 px-4 rounded transition">Bersihkan</button>
                        </div>
                    </form>
                    <div id="member-message" class="hidden mt-4 p-3 rounded text-sm text-center font-medium"></div>
                </div>
            </div>
        </div>
    </div>
</div>
<script>window.HME_CFG = { script: <?= json_encode($script) ?> };</script>
<script><?php echo admin_js(); ?></script>
</body>
</html>
<?php
}

function home_js(): string {
    return <<<'JS'
(function () {
    const SCRIPT = window.HME_CFG.script;
    const apiUrl = (path, qs = '') => SCRIPT + '?path=' + encodeURIComponent(path) + (qs ? '&' + qs : '');

    document.addEventListener('DOMContentLoaded', () => {
        const $ = id => document.getElementById(id);
        const btnGenerate = $('btn-generate'), btnCancel = $('btn-cancel');
        const btnRefreshInbox = $('btn-refresh-inbox'), btnBackHome = $('btn-back-home');
        const btnShowSaved = $('btn-show-saved'), btnBackFromSaved = $('btn-back-from-saved');
        const stepGenerate = $('step-generate'), stepReserve = $('step-reserve'), stepInbox = $('step-inbox'), stepSaved = $('step-saved');
        const inputEmail = $('generated-email'), inputPrefix = $('custom-prefix');
        const loading = $('loading'), loadingText = $('loading-text'), messageBox = $('message');
        const inboxEmailDisplay = $('inbox-email-display'), inboxMessages = $('inbox-messages'), savedEmailsList = $('saved-emails-list');
        const mainCard = $('main-card'), accountPicker = $('account-picker'), noAccountHint = $('no-account-hint');
        const loginOverlay = $('login-overlay'), loginForm = $('login-form'), loginCode = $('login-code'),
              loginSubmit = $('login-submit'), loginMessage = $('login-message');
        const sessionBar = $('session-bar'), sessionMeta = $('session-meta'), btnLogout = $('btn-logout');

        const btnCopyAndInbox = $('btn-copy-and-inbox');

        let accountsList = [];
        let currentHmeEmail = '';
        let authToken = localStorage.getItem('memberToken') || '';
        let authMeta = null;
        let inboxPollInterval = null;
        let lastInboxUids = [];

        let isUniversalMode = false;
        let currentThreadId = '';
        let isUniversalLoading = false;

        function getOrCreateDeviceId() {
            let id = localStorage.getItem('deviceId');
            if (!id) {
                id = (crypto.randomUUID && crypto.randomUUID()) ||
                     (Date.now().toString(36) + Math.random().toString(36).slice(2, 12));
                localStorage.setItem('deviceId', id);
            }
            return id;
        }
        const deviceId = getOrCreateDeviceId();

        const apiHeaders = (extra = {}) => {
            const id = accountPicker?.value || '';
            const headers = { ...extra };
            if (id) headers['x-account-id'] = id;
            if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
            if (deviceId) headers['x-device-id'] = deviceId;
            return headers;
        };
        const setWideMode = wide => {
            if (!mainCard) return;
            mainCard.classList.toggle('max-w-md', !wide);
            mainCard.classList.toggle('max-w-3xl', wide);
        };
        const switchStep = activeStep => {
            [stepGenerate, stepReserve, stepInbox, stepSaved].forEach(step => {
                if (step) step.classList.toggle('hidden', step !== activeStep);
            });
            const wrap = $('account-picker-wrap');
            if (wrap) {
                wrap.classList.toggle('hidden', activeStep !== stepGenerate);
            }
        };
        const escapeHtml = s => String(s || '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
        const showMessage = (text, isError = false) => {
            messageBox.textContent = text;
            messageBox.className = `mt-4 p-3 rounded text-sm text-center font-medium block ${isError ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`;
            setTimeout(() => messageBox.classList.add('hidden'), 5000);
        };
        const showLoading = (show, text = 'Memproses...') => {
            loadingText.textContent = text;
            loading.classList.toggle('hidden', !show);
            btnGenerate.disabled = show;
        };

        async function copyToClipboard(text, btnEl) {
            const value = String(text || '').trim();
            if (!value) return false;
            try {
                if (navigator.clipboard && window.isSecureContext) {
                    await navigator.clipboard.writeText(value);
                } else {
                    const ta = document.createElement('textarea');
                    ta.value = value;
                    ta.style.position = 'fixed'; ta.style.opacity = '0';
                    document.body.appendChild(ta);
                    ta.select();
                    document.execCommand('copy');
                    document.body.removeChild(ta);
                }
                if (btnEl) {
                    const original = btnEl.innerHTML;
                    btnEl.innerHTML = '✓ Tersalin';
                    btnEl.classList.add('bg-green-100', 'text-green-700', 'border-green-300');
                    setTimeout(() => {
                        btnEl.innerHTML = original;
                        btnEl.classList.remove('bg-green-100', 'text-green-700', 'border-green-300');
                    }, 1500);
                }
                showMessage('Email disalin ke clipboard: ' + value);
                return true;
            } catch (e) {
                showMessage('Gagal menyalin. Silakan salin manual.', true);
                return false;
            }
        }
        const showLoginMsg = (t, isErr) => {
            loginMessage.textContent = t;
            loginMessage.className = 'mt-4 p-3 rounded text-sm text-center font-medium block ' + (isErr ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700');
            setTimeout(() => loginMessage.classList.add('hidden'), 4000);
        };

        async function apiFetch(url, opts = {}) {
            const r = await fetch(url, opts);
            if (r.status === 401 || r.status === 403) {
                let msg = 'Sesi berakhir. Silakan login ulang.';
                try { const j = await r.clone().json(); if (j.error) msg = j.error; } catch (e) {}
                logout(msg);
                throw new Error(msg);
            }
            return r;
        }

        const limitModal = $('limit-modal');
        const limitMsg = $('limit-modal-msg');
        function showLimitModal(msg) {
            if (msg) limitMsg.textContent = msg;
            limitModal?.classList.remove('hidden');
        }
        function hideLimitModal() { limitModal?.classList.add('hidden'); }
        $('limit-close')?.addEventListener('click', hideLimitModal);
        $('limit-go-saved')?.addEventListener('click', () => {
            hideLimitModal();
            stepGenerate.classList.add('hidden');
            stepSaved.classList.remove('hidden');
            loadTrackedFromServer();
        });

        let serverTracked = [];
        async function loadTrackedFromServer() {
            try {
                const r = await fetch(apiUrl('tracked'), { headers: apiHeaders() });
                if (r.status === 401 || r.status === 403) {
                    const j = await r.json().catch(() => ({}));
                    logout(j.error || 'Sesi tidak valid');
                    return;
                }
                const j = await r.json();
                if (!j.success) return;
                serverTracked = j.data.emails || [];
                if (authMeta) {
                    authMeta.usedToday = j.data.usedToday;
                    authMeta.dailyLimit = j.data.dailyLimit;
                    authMeta.autoDeleteMin = j.data.autoDeleteMin;
                }
                renderSessionBar();
                if (!stepSaved.classList.contains('hidden')) loadSavedEmails();
            } catch (e) {
                console.error('loadTrackedFromServer', e);
            }
        }

        async function deleteEmailEverywhere(emailStr) {
            const target = serverTracked.find(em => em.hme === emailStr);
            if (!target) return { ok: false, error: 'tidak ada di server' };

            const prevAcc = accountPicker.value;
            if (target.accountId && accountsList.some(a => a.id === target.accountId)) {
                accountPicker.value = target.accountId;
            }
            try {
                const dRes = await fetch(apiUrl('deactivate'), {
                    method: 'POST',
                    headers: apiHeaders({ 'Content-Type': 'application/json' }),
                    body: JSON.stringify({ anonymousId: target.anonymousId, hme: target.hme })
                });
                const dJson = await dRes.json();
                if (!dJson.success && !dJson.partial) return { ok: false, error: dJson.error || 'gagal deactivate' };
                return { ok: true, partial: !!dJson.partial };
            } catch (err) {
                return { ok: false, error: 'jaringan' };
            } finally {
                if (prevAcc && accountsList.some(a => a.id === prevAcc)) accountPicker.value = prevAcc;
            }
        }

        setInterval(async () => { await loadTrackedFromServer(); }, 30 * 1000);
        setInterval(async () => {
            try {
                await fetch(apiUrl('tracked/sweep'), { method: 'POST', headers: apiHeaders() });
                await loadTrackedFromServer();
            } catch (e) {}
        }, 60 * 1000);

        function renderSessionBar() {
            if (!authMeta || !sessionBar) return;
            sessionBar.classList.remove('hidden');
            const days = authMeta.remainingDays;
            const tail = days <= 1 ? 'kurang dari 1 hari' : (days + ' hari lagi');
            const limit = authMeta.dailyLimit > 0
                ? (authMeta.usedToday + '/' + authMeta.dailyLimit + ' hari ini')
                : 'unlimited';
            sessionMeta.textContent = (authMeta.memberName ? '· ' + authMeta.memberName + ' · ' : '· ')
                + tail + ' · ' + limit;
        }

        function showLoginScreen() {
            mainCard.classList.add('hidden');
            loginOverlay.classList.remove('hidden');
            sessionBar.classList.add('hidden');
        }
        function showAppScreen() {
            loginOverlay.classList.add('hidden');
            mainCard.classList.remove('hidden');
            renderSessionBar();
            switchStep(stepGenerate);
        }
        function logout(reason) {
            authToken = ''; authMeta = null;
            localStorage.removeItem('memberToken');
            showLoginScreen();
            if (reason) showLoginMsg(reason, true);
        }
        async function fetchMe() {
            const r = await fetch(apiUrl('auth/me'), { headers: apiHeaders() });
            if (r.status === 401 || r.status === 403) {
                const j = await r.json().catch(() => ({}));
                throw new Error(j.error || 'Sesi tidak valid');
            }
            const j = await r.json();
            if (!j.success) throw new Error(j.error || 'Sesi tidak valid');
            return j.data;
        }
        async function gateOrLoad() {
            if (!authToken) { showLoginScreen(); return; }
            try {
                authMeta = await fetchMe();
                showAppScreen();
                loadAccounts();
                loadTrackedFromServer();
            } catch (e) {
                logout(e.message);
            }
        }

        loginForm.addEventListener('submit', async (e) => {
            e.preventDefault();
            const code = loginCode.value.trim();
            if (!code) return;
            loginSubmit.disabled = true; loginSubmit.textContent = 'Memeriksa...';
            try {
                const r = await fetch(apiUrl('auth/redeem'), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ code, deviceId, deviceInfo: { ua: navigator.userAgent } })
                });
                const j = await r.json();
                if (!j.success) { showLoginMsg(j.error || 'Gagal memvalidasi voucher', true); return; }
                authToken = j.data.token;
                localStorage.setItem('memberToken', authToken);
                authMeta = await fetchMe();
                setTimeout(() => { showAppScreen(); loadAccounts(); loadTrackedFromServer(); }, 600);
            } catch (err) { showLoginMsg('Gangguan jaringan. Coba lagi.', true); }
            finally { loginSubmit.disabled = false; loginSubmit.textContent = 'Masuk'; }
        });

        btnLogout?.addEventListener('click', () => { if (confirm('Keluar dari sesi?')) logout('Anda telah keluar.'); });

        document.querySelectorAll('[data-pw-toggle]').forEach(btn => {
            btn.addEventListener('click', () => {
                const target = document.getElementById(btn.dataset.pwToggle);
                if (!target) return;
                const isPw = target.type === 'password';
                target.type = isPw ? 'text' : 'password';
                btn.querySelector('.eye-show')?.classList.toggle('hidden', isPw);
                btn.querySelector('.eye-hide')?.classList.toggle('hidden', !isPw);
            });
        });

        gateOrLoad();

        async function loadAccounts() {
            try {
                const res = await apiFetch(apiUrl('accounts'), { headers: apiHeaders() });
                const result = await res.json();
                accountsList = (result.success && result.data) ? result.data : [];
                accountPicker.innerHTML = '';
                if (!accountsList.length) {
                    noAccountHint.classList.remove('hidden');
                    return;
                }
                noAccountHint.classList.add('hidden');
                const lastUsed = localStorage.getItem('lastUsedAccountId');
                accountsList.forEach((a, idx) => {
                    const opt = document.createElement('option');
                    opt.value = a.id;
                    opt.textContent = 'Server ' + (idx + 1);
                    accountPicker.appendChild(opt);
                });
                if (lastUsed && accountsList.some(a => a.id === lastUsed)) {
                    accountPicker.value = lastUsed;
                }
                loadUniversalInbox(true);
            } catch (err) { console.error(err); }
        }
        accountPicker?.addEventListener('change', () => {
            if (accountPicker.value) {
                localStorage.setItem('lastUsedAccountId', accountPicker.value);
                loadUniversalInbox(true);
            }
        });

        btnGenerate.addEventListener('click', async () => {
            const prefixVal = inputPrefix ? inputPrefix.value.trim() : '';
            showLoading(true, 'Menghasilkan email acak...');
            try {
                const r = await fetch(apiUrl('generate'), {
                    method: 'POST',
                    headers: apiHeaders({ 'Content-Type': 'application/json' }),
                    body: JSON.stringify({ prefix: prefixVal })
                });
                const result = await r.json();
                if (result.success && result.data?.result?.hme) {
                    inputEmail.value = result.data.result.hme;
                    switchStep(stepReserve);
                    return;
                }
                if (result.limitReached || r.status === 409) {
                    showLimitModal(result.error || 'Anda telah mencapai batas pembuatan email harian.');
                    return;
                }
                showMessage(result.error || 'Gagal menghasilkan email.', true);
            } catch (e) { showMessage('Terjadi kesalahan jaringan.', true); }
            finally { showLoading(false); }
        });

        btnCancel.addEventListener('click', () => {
            switchStep(stepGenerate);
            inputEmail.value = '';
        });
        btnBackHome.addEventListener('click', () => {
            switchStep(stepGenerate);
            setWideMode(false);
            inputEmail.value = '';
            currentHmeEmail = '';
            currentThreadId = '';
            isUniversalMode = false;
            stopInboxPolling();
            loadUniversalInbox(false);

            // Restore inbox label & copy button state
            const labelEl = inboxEmailDisplay.previousElementSibling;
            if (labelEl) labelEl.textContent = 'Email:';
            const btnCopy = $('btn-copy-inbox-email');
            if (btnCopy) btnCopy.classList.remove('hidden');
        });
        btnShowSaved?.addEventListener('click', () => {
            switchStep(stepSaved);
            loadTrackedFromServer();
        });
        btnBackFromSaved?.addEventListener('click', () => {
            switchStep(stepGenerate);
            loadUniversalInbox(false);
        });

        function loadSavedEmails() {
            const list = serverTracked || [];
            if (!list.length) {
                savedEmailsList.innerHTML = '<p class="text-sm text-gray-500 text-center py-4">Belum ada email yang tersimpan.</p>';
                return;
            }
            savedEmailsList.innerHTML = '';
            list.forEach(item => {
                const dateStr = new Date(item.createdAt).toLocaleString('id-ID', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
                const el = document.createElement('div');
                el.className = 'bg-white p-3 rounded shadow-sm border border-gray-100 flex flex-col cursor-pointer hover:bg-gray-50 transition';
                el.innerHTML = `
                    <div class="flex justify-between items-start gap-2">
                        <span class="font-bold text-gray-800 text-sm truncate">${escapeHtml(item.label || 'Tanpa Label')}</span>
                        <div class="flex items-center gap-1 flex-shrink-0">
                            <button class="text-blue-600 text-xs hover:text-blue-800 font-semibold btn-copy-saved px-2 py-1 rounded" data-email="${escapeHtml(item.hme)}">Salin</button>
                            <button class="text-red-500 text-xs hover:text-red-700 font-semibold btn-delete-saved px-2 py-1 rounded" data-email="${escapeHtml(item.hme)}">Hapus</button>
                        </div>
                    </div>
                    <span class="text-blue-600 font-mono text-sm mt-1 break-all">${escapeHtml(item.hme)}</span>
                    <div class="text-xs text-gray-400 mt-2">${dateStr}</div>
                `;
                el.addEventListener('click', async e => {
                    const t = e.target;
                    if (t.classList.contains('btn-copy-saved')) { e.stopPropagation(); copyToClipboard(t.getAttribute('data-email'), t); return; }
                    if (t.classList.contains('btn-delete-saved')) {
                        e.stopPropagation();
                        const emailToDelete = t.getAttribute('data-email');
                        if (!confirm('Hapus email "' + emailToDelete + '"?\nKuota harian akan terbebas.')) return;
                        t.disabled = true; t.textContent = '...';
                        try {
                            const r = await deleteEmailEverywhere(emailToDelete);
                            if (!r.ok) { showMessage(r.error || 'Gagal hapus.', true); return; }
                            showMessage('Email dihapus.');
                            await loadTrackedFromServer();
                        } catch (er) { showMessage('Jaringan bermasalah.', true); }
                        return;
                    }
                    if (item.accountId && accountsList.some(a => a.id === item.accountId)) {
                        accountPicker.value = item.accountId;
                        localStorage.setItem('lastUsedAccountId', item.accountId);
                    }
                    currentHmeEmail = item.hme;
                    inboxEmailDisplay.textContent = currentHmeEmail;
                    switchStep(stepInbox);
                    setWideMode(true); startInboxPolling();
                });
                savedEmailsList.appendChild(el);
            });
        }

        setInterval(() => {
            if (stepSaved.classList.contains('hidden')) return;
        }, 1000);

        btnRefreshInbox.addEventListener('click', () => {
            if (isUniversalMode) openUniversalThread(currentThreadId);
            else if (currentHmeEmail) startInboxPolling();
        });

        if (btnCopyAndInbox) {
            btnCopyAndInbox.addEventListener('click', async () => {
                const emailStr = inputEmail.value;
                await copyToClipboard(emailStr, btnCopyAndInbox);
                switchStep(stepGenerate);
                setWideMode(false);
                loadUniversalInbox(true);
            });
        }

        const btnCopyInboxEmail = $('btn-copy-inbox-email');
        if (btnCopyInboxEmail) {
            btnCopyInboxEmail.addEventListener('click', () => {
                copyToClipboard(inboxEmailDisplay.textContent, btnCopyInboxEmail);
            });
        }

        const stripInvisible = s => (s || '').replace(/[\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF\u00AD]/g, '');
        const parseAddress = raw => {
            if (!raw) return { name: '', email: '' };
            const m = String(raw).match(/^\s*"?([^"<]*?)"?\s*<([^>]+)>\s*$/);
            return m ? { name: m[1].trim(), email: m[2].trim() } : { name: '', email: String(raw).trim() };
        };
        const getInitial = (n, e) => ((n && n.trim()) || (e || '?')).trim().charAt(0).toUpperCase() || '?';
        const makePreview = t => {
            const c = stripInvisible(t || '').replace(/\s+/g, ' ').trim();
            return c.length > 110 ? c.slice(0, 110) + '…' : c;
        };
        const buildSrcDoc = html => `<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style>html,body{margin:0;padding:12px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#1f2937;background:#fff}img{max-width:100%;height:auto}a{color:#2563eb}table{max-width:100%!important}pre,code{white-space:pre-wrap;word-break:break-word}</style></head><body>${html}</body></html>`;
        const autosizeIframe = iframe => { try { const doc = iframe.contentDocument || iframe.contentWindow.document; const h = Math.max(doc.body?.scrollHeight || 0, doc.documentElement?.scrollHeight || 0, 200); iframe.style.height = (h + 30) + 'px'; } catch (e) {} };

        function startInboxPolling() {
            stopInboxPolling();
            isUniversalMode = false;

            // Restore inbox label & copy button state
            const labelEl = inboxEmailDisplay.previousElementSibling;
            if (labelEl) labelEl.textContent = 'Email:';
            const btnCopy = $('btn-copy-inbox-email');
            if (btnCopy) btnCopy.classList.remove('hidden');

            loadInbox(true);
            inboxPollInterval = setInterval(() => {
                if (!stepInbox.classList.contains('hidden') && currentHmeEmail) loadInbox(false);
                else stopInboxPolling();
            }, 8000);
        }

        function stopInboxPolling() {
            if (inboxPollInterval) { clearInterval(inboxPollInterval); inboxPollInterval = null; }
            lastInboxUids = [];
        }

        async function loadInbox(isManual = true) {
            if (isUniversalLoading) return;
            isUniversalLoading = true;
            try {
                const r = await apiFetch(apiUrl('inbox'), { method: 'POST', headers: apiHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ hmeEmail: currentHmeEmail }) });
                const result = await r.json();
                if (!result.success) return;
                const currentUids = result.data.map(m => m.id);
                if (currentUids.length === lastInboxUids.length && currentUids.every((uid, idx) => uid === lastInboxUids[idx])) return;
                lastInboxUids = currentUids;
                inboxMessages.innerHTML = '';
                
                if (!result.data || !result.data.length) {
                    inboxMessages.innerHTML = '<p class="text-sm text-gray-500 text-center py-4">Belum ada pesan.</p>';
                    return;
                }

                // Color palette for avatar backgrounds
                const colors = [
                    'bg-blue-500', 'bg-green-500', 'bg-indigo-500', 'bg-purple-500', 
                    'bg-pink-500', 'bg-yellow-600', 'bg-red-500', 'bg-teal-500', 'bg-orange-500'
                ];

                result.data.forEach((msg, idx) => {
                    const fromInfo = parseAddress(msg.from);
                    
                    // Choose avatar background color based on sender email hash
                    const charCodeSum = (fromInfo.email || 'a').split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
                    const colorClass = colors[charCodeSum % colors.length];
                    
                    // Parse date
                    const msgDate = new Date(msg.date);
                    const now = new Date();
                    let formattedTime = '';
                    let fullDateStr = msgDate.toLocaleString('id-ID', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });
                    
                    if (msgDate.toDateString() === now.toDateString()) {
                        formattedTime = msgDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
                    } else if (msgDate.getFullYear() === now.getFullYear()) {
                        formattedTime = msgDate.toLocaleDateString([], { day: 'numeric', month: 'short' }) + ' ' + msgDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
                    } else {
                        formattedTime = msgDate.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' });
                    }

                    const card = document.createElement('div');
                    card.className = 'bg-white rounded-lg border border-gray-200 overflow-hidden shadow-sm transition duration-200 mb-3';
                    
                    const isLastMsg = (idx === result.data.length - 1);
                    
                    card.innerHTML = `
                        <!-- CARD HEADER -->
                        <div class="p-4 cursor-pointer hover:bg-gray-50 flex items-center justify-between border-b border-gray-100 transition card-header-clickable">
                            <div class="flex items-center min-w-0">
                                <div class="w-10 h-10 rounded-full ${colorClass} text-white flex items-center justify-center font-bold text-sm select-none flex-shrink-0">
                                    ${getInitial(fromInfo.name, fromInfo.email)}
                                </div>
                                <div class="ml-3 min-w-0">
                                    <div class="font-bold text-gray-800 text-sm truncate">${escapeHtml(fromInfo.name || fromInfo.email || 'Unknown')}</div>
                                    <div class="text-xs text-gray-500 truncate mt-0.5">To: Hide My Email</div>
                                </div>
                            </div>
                            <div class="text-right flex flex-col items-end flex-shrink-0 ml-4">
                                <div class="text-xs text-gray-400 font-medium">${formattedTime}</div>
                                <div class="text-xs text-blue-600 hover:text-blue-800 font-semibold cursor-pointer select-none mt-1 btn-toggle-details">Details</div>
                            </div>
                        </div>
                        
                        <!-- DETAILS PANEL -->
                        <div class="hidden px-4 pb-3 border-b border-gray-100 bg-gray-50 text-xs text-gray-600 space-y-1 text-left details-panel">
                            <div class="pt-2 border-t border-gray-200/50">
                                <div><span class="font-semibold">From:</span> ${escapeHtml(msg.from)}</div>
                                <div><span class="font-semibold">To:</span> ${escapeHtml(msg.to)}</div>
                                ${msg.cc ? `<div><span class="font-semibold">Cc:</span> ${escapeHtml(msg.cc)}</div>` : ''}
                                <div><span class="font-semibold">Date:</span> ${fullDateStr}</div>
                                <div><span class="font-semibold">Subject:</span> ${escapeHtml(msg.subject || '(Tanpa Subjek)')}</div>
                            </div>
                        </div>
                        
                        <!-- CARD BODY -->
                        <div class="${isLastMsg ? 'block' : 'hidden'} card-body-container">
                            <!-- HME FORWARD BANNER -->
                            <div class="bg-blue-50 border-b border-blue-100 text-blue-900 text-xs px-4 py-2.5 flex items-center justify-between">
                                <span class="font-medium">This message was forwarded to you by Hide My Email. To manage, go to <a href="https://www.icloud.com/icloudplus/" target="_blank" class="text-blue-600 hover:underline font-semibold">iCloud+ Features</a>.</span>
                            </div>
                            
                            <!-- SUBJECT LINE -->
                            <div class="text-lg font-bold text-gray-800 px-4 pt-4 pb-2 border-b border-gray-50 bg-white">
                                ${escapeHtml(msg.subject || '(Tanpa Subjek)')}
                            </div>
                            
                            <!-- CONTENT MOUNT -->
                            <div class="p-4 bg-white msg-content-mount"></div>
                        </div>
                    `;
                    
                    const headerClickable = card.querySelector('.card-header-clickable');
                    const btnToggleDetails = card.querySelector('.btn-toggle-details');
                    const detailsPanel = card.querySelector('.details-panel');
                    const bodyContainer = card.querySelector('.card-body-container');
                    const contentMount = card.querySelector('.msg-content-mount');
                    
                    headerClickable.addEventListener('click', (e) => {
                        if (e.target.classList.contains('btn-toggle-details')) return;
                        const isHidden = bodyContainer.classList.contains('hidden');
                        bodyContainer.classList.toggle('hidden', !isHidden);
                        if (isHidden) {
                            loadIframeContent();
                        }
                    });
                    
                    btnToggleDetails.addEventListener('click', (e) => {
                        e.stopPropagation();
                        detailsPanel.classList.toggle('hidden');
                    });
                    
                    let iframeLoaded = false;
                    function loadIframeContent() {
                        if (iframeLoaded) return;
                        iframeLoaded = true;
                        
                        if (msg.html) {
                            const iframe = document.createElement('iframe');
                            iframe.style.cssText = 'width:100%; border:0; display:block; height:300px; background:#fff; overflow:hidden;';
                            iframe.setAttribute('scrolling', 'no');
                            iframe.srcdoc = buildSrcDoc(msg.html);
                            
                            iframe.addEventListener('load', () => {
                                autosizeIframe(iframe);
                                setTimeout(() => autosizeIframe(iframe), 300);
                                setTimeout(() => autosizeIframe(iframe), 1000);
                                setTimeout(() => autosizeIframe(iframe), 2500);
                            });
                            
                            contentMount.appendChild(iframe);
                        } else {
                            const pre = document.createElement('pre');
                            pre.className = 'whitespace-pre-wrap font-sans text-sm text-gray-700 bg-gray-50 p-3 rounded border border-gray-150';
                            pre.textContent = msg.text || '(Pesan Kosong)';
                            contentMount.appendChild(pre);
                        }
                    }
                    
                    if (isLastMsg) {
                        loadIframeContent();
                    }
                    
                    inboxMessages.appendChild(card);
                });
            } finally { isUniversalLoading = false; }
        }

        async function loadUniversalInbox(isManual = false) {}

        async function openUniversalThread(threadId, threadSubject) {
            isUniversalMode = true; currentThreadId = threadId; stopInboxPolling();
            switchStep(stepInbox); setWideMode(true);
            
            // Adjust label & copy button for universal thread view
            const labelEl = inboxEmailDisplay.previousElementSibling;
            if (labelEl) labelEl.textContent = 'Thread:';
            inboxEmailDisplay.textContent = threadSubject || 'Semua Pesan';
            const btnCopy = $('btn-copy-inbox-email');
            if (btnCopy) btnCopy.classList.add('hidden');

            inboxMessages.innerHTML = `
                <div class="flex items-center justify-center py-12">
                    <svg class="animate-spin h-6 w-6 text-blue-500 mr-3" viewBox="0 0 24 24" fill="none">
                        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                    </svg>
                    <span class="text-sm text-gray-500 font-medium">Memuat rincian thread...</span>
                </div>
            `;
            try {
                const r = await fetch(apiUrl('inbox/universal/thread'), { method: 'POST', headers: apiHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ threadId }) });
                const result = await r.json();
                if (!result.success || !result.data || !result.data.length) {
                    inboxMessages.innerHTML = '<p class="text-sm text-gray-500 text-center py-4">Gagal memuat pesan atau thread kosong.</p>';
                    return;
                }
                
                inboxMessages.innerHTML = '';
                
                // Color palette for avatar backgrounds
                const colors = [
                    'bg-blue-500', 'bg-green-500', 'bg-indigo-500', 'bg-purple-500', 
                    'bg-pink-500', 'bg-yellow-600', 'bg-red-500', 'bg-teal-500', 'bg-orange-500'
                ];
                
                result.data.forEach((msg, idx) => {
                    const fromInfo = parseAddress(msg.from);
                    
                    // Choose avatar background color based on sender email hash
                    const charCodeSum = (fromInfo.email || 'a').split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
                    const colorClass = colors[charCodeSum % colors.length];
                    
                    // Parse date
                    const msgDate = new Date(msg.date);
                    const now = new Date();
                    let formattedTime = '';
                    let fullDateStr = msgDate.toLocaleString('id-ID', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });
                    
                    if (msgDate.toDateString() === now.toDateString()) {
                        formattedTime = msgDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
                    } else if (msgDate.getFullYear() === now.getFullYear()) {
                        formattedTime = msgDate.toLocaleDateString([], { day: 'numeric', month: 'short' }) + ' ' + msgDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
                    } else {
                        formattedTime = msgDate.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' });
                    }

                    const card = document.createElement('div');
                    card.className = 'bg-white rounded-lg border border-gray-200 overflow-hidden shadow-sm transition duration-200 mb-3';
                    
                    const isLastMsg = (idx === result.data.length - 1);
                    
                    card.innerHTML = `
                        <!-- CARD HEADER -->
                        <div class="p-4 cursor-pointer hover:bg-gray-50 flex items-center justify-between border-b border-gray-100 transition card-header-clickable">
                            <div class="flex items-center min-w-0">
                                <div class="w-10 h-10 rounded-full ${colorClass} text-white flex items-center justify-center font-bold text-sm select-none flex-shrink-0">
                                    ${getInitial(fromInfo.name, fromInfo.email)}
                                </div>
                                <div class="ml-3 min-w-0">
                                    <div class="font-bold text-gray-800 text-sm truncate">${escapeHtml(fromInfo.name || fromInfo.email || 'Unknown')}</div>
                                    <div class="text-xs text-gray-500 truncate mt-0.5">To: Hide My Email</div>
                                </div>
                            </div>
                            <div class="text-right flex flex-col items-end flex-shrink-0 ml-4">
                                <div class="text-xs text-gray-400 font-medium">${formattedTime}</div>
                                <div class="text-xs text-blue-600 hover:text-blue-800 font-semibold cursor-pointer select-none mt-1 btn-toggle-details">Details</div>
                            </div>
                        </div>
                        
                        <!-- DETAILS PANEL (COLLAPSED BY DEFAULT) -->
                        <div class="hidden px-4 pb-3 border-b border-gray-100 bg-gray-50 text-xs text-gray-600 space-y-1 text-left details-panel">
                            <div class="pt-2 border-t border-gray-200/50">
                                <div><span class="font-semibold">From:</span> ${escapeHtml(msg.from)}</div>
                                <div><span class="font-semibold">To:</span> ${escapeHtml(msg.to)}</div>
                                ${msg.cc ? `<div><span class="font-semibold">Cc:</span> ${escapeHtml(msg.cc)}</div>` : ''}
                                <div><span class="font-semibold">Date:</span> ${fullDateStr}</div>
                                <div><span class="font-semibold">Subject:</span> ${escapeHtml(msg.subject || '(Tanpa Subjek)')}</div>
                            </div>
                        </div>
                        
                        <!-- CARD BODY -->
                        <div class="${isLastMsg ? 'block' : 'hidden'} card-body-container">
                            <!-- HME FORWARD BANNER -->
                            <div class="bg-blue-50 border-b border-blue-100 text-blue-900 text-xs px-4 py-2.5 flex items-center justify-between">
                                <span class="font-medium">This message was forwarded to you by Hide My Email. To manage, go to <a href="https://www.icloud.com/icloudplus/" target="_blank" class="text-blue-600 hover:underline font-semibold">iCloud+ Features</a>.</span>
                            </div>
                            
                            <!-- SUBJECT LINE -->
                            <div class="text-lg font-bold text-gray-800 px-4 pt-4 pb-2 border-b border-gray-50 bg-white">
                                ${escapeHtml(msg.subject || '(Tanpa Subjek)')}
                            </div>
                            
                            <!-- CONTENT MOUNT -->
                            <div class="p-4 bg-white msg-content-mount"></div>
                        </div>
                    `;
                    
                    const headerClickable = card.querySelector('.card-header-clickable');
                    const btnToggleDetails = card.querySelector('.btn-toggle-details');
                    const detailsPanel = card.querySelector('.details-panel');
                    const bodyContainer = card.querySelector('.card-body-container');
                    const contentMount = card.querySelector('.msg-content-mount');
                    
                    // Handler for header click (expand/collapse card body)
                    headerClickable.addEventListener('click', (e) => {
                        // Prevent expansion if user clicked details button
                        if (e.target.classList.contains('btn-toggle-details')) return;
                        
                        const isHidden = bodyContainer.classList.contains('hidden');
                        bodyContainer.classList.toggle('hidden', !isHidden);
                        
                        // Load iframe if expanding and not loaded yet
                        if (isHidden) {
                            loadIframeContent();
                        }
                    });
                    
                    // Handler for Details button click
                    btnToggleDetails.addEventListener('click', (e) => {
                        e.stopPropagation();
                        detailsPanel.classList.toggle('hidden');
                    });
                    
                    // Function to mount the iframe and autosize it
                    let iframeLoaded = false;
                    function loadIframeContent() {
                        if (iframeLoaded) return;
                        iframeLoaded = true;
                        
                        if (msg.html) {
                            const iframe = document.createElement('iframe');
                            iframe.style.cssText = 'width:100%; border:0; display:block; height:300px; background:#fff; overflow:hidden;';
                            iframe.setAttribute('scrolling', 'no');
                            iframe.srcdoc = buildSrcDoc(msg.html);
                            
                            iframe.addEventListener('load', () => {
                                autosizeIframe(iframe);
                                // Call multiple times to handle async layout changes/images loading
                                setTimeout(() => autosizeIframe(iframe), 300);
                                setTimeout(() => autosizeIframe(iframe), 1000);
                                setTimeout(() => autosizeIframe(iframe), 2500);
                            });
                            
                            contentMount.appendChild(iframe);
                        } else {
                            const pre = document.createElement('pre');
                            pre.className = 'whitespace-pre-wrap font-sans text-sm text-gray-700 bg-gray-50 p-3 rounded border border-gray-150';
                            pre.textContent = msg.text || '(Pesan Kosong)';
                            contentMount.appendChild(pre);
                        }
                    }
                    
                    // Load immediately if this card is expanded by default (i.e. the last message)
                    if (isLastMsg) {
                        loadIframeContent();
                    }
                    
                    inboxMessages.appendChild(card);
                });
            } catch (err) {
                console.error(err);
                inboxMessages.innerHTML = '<p class="text-sm text-red-500 text-center py-4">Gagal memproses detail email.</p>';
            } finally {
                btnRefreshInbox.disabled = false;
            }
        }
    });
})();
JS;
}

function admin_js(): string {
    return <<<'JS'
(function () {
    const SCRIPT = window.HME_CFG.script;
    const apiUrl = path => SCRIPT + '?path=' + encodeURIComponent(path);

    document.addEventListener('DOMContentLoaded', () => {
        const $ = id => document.getElementById(id);
        const loginSection = $('login-section'), adminSection = $('admin-section'), loginForm = $('login-form');
        const inputAccessCode = $('access-code'), btnLogin = $('btn-login'), loginMessageBox = $('login-message');
        const accountList = $('account-list'), formTitle = $('form-title'), form = $('admin-form');
        const inputs = {
            id: $('account-id'), name: $('acc-name'), dsid: $('acc-dsid'), clientId: $('acc-clientid'),
            cookies: $('acc-cookies'), email: $('acc-email'), appPassword: $('acc-app-password'), subdomain: $('acc-subdomain')
        };
        const btnSave = $('btn-save'), btnDelete = $('btn-delete'), btnReset = $('btn-reset'), btnNew = $('btn-new');
        const messageBox = $('message');

        let currentAccessCode = sessionStorage.getItem('adminAccessCode') || '';
        let accountsCache = [], selectedId = '';

        const escapeHtml = s => String(s || '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
        const showMessage = (t, e = false) => {
            messageBox.textContent = t;
            messageBox.className = `mt-4 p-3 rounded text-sm text-center font-medium block ${e ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`;
            clearTimeout(showMessage._t);
            showMessage._t = setTimeout(() => messageBox.classList.add('hidden'), 4000);
        };
        const showLoginMessage = (t, e = false) => {
            loginMessageBox.textContent = t;
            loginMessageBox.className = `mt-4 p-3 rounded text-sm text-center font-medium block ${e ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`;
            setTimeout(() => loginMessageBox.classList.add('hidden'), 3000);
        };
        const apiHeaders = (extra = {}) => ({ 'x-access-code': currentAccessCode, ...extra });

        async function fetchAccounts() {
            const res = await fetch(apiUrl('admin/accounts'), { headers: apiHeaders() });
            if (res.status === 401) {
                sessionStorage.removeItem('adminAccessCode');
                currentAccessCode = '';
                adminSection.classList.add('hidden'); loginSection.classList.remove('hidden');
                showLoginMessage('Access Code salah atau kedaluwarsa.', true);
                return [];
            }
            const result = await res.json();
            return result.success ? result.data : [];
        }
        async function refreshList() { accountsCache = await fetchAccounts(); renderList(); }
        async function saveAccount(payload) {
            const isUpdate = !!payload.id;
            const url = isUpdate ? apiUrl('admin/accounts/' + payload.id) : apiUrl('admin/accounts');
            const res = await fetch(url, {
                method: isUpdate ? 'PUT' : 'POST',
                headers: apiHeaders({ 'Content-Type': 'application/json' }),
                body: JSON.stringify(payload)
            });
            return res.json();
        }
        async function deleteAccount(id) {
            const res = await fetch(apiUrl('admin/accounts/' + id), { method: 'DELETE', headers: apiHeaders() });
            return res.json();
        }

        function renderList() {
            accountList.innerHTML = '';
            if (!accountsCache.length) {
                accountList.innerHTML = '<p class="text-sm text-gray-500 text-center py-4">Belum ada akun.</p>';
                return;
            }
            accountsCache.forEach(acc => {
                const el = document.createElement('div');
                const active = acc.id === selectedId;
                el.className = `p-2 rounded cursor-pointer border transition ${active ? 'bg-blue-50 border-blue-300' : 'bg-white border-gray-200 hover:bg-gray-100'}`;
                el.innerHTML = `
                    <div class="font-semibold text-sm text-gray-800 truncate">${escapeHtml(acc.name)}</div>
                    <div class="text-xs text-gray-500 truncate">${escapeHtml(acc.email || '(IMAP belum diatur)')}</div>
                    <div class="text-xs text-gray-400 truncate">DSID: ${escapeHtml(acc.dsid || '-')}</div>`;
                el.addEventListener('click', () => selectAccount(acc.id));
                accountList.appendChild(el);
            });
        }
        function selectAccount(id) {
            const acc = accountsCache.find(a => a.id === id);
            if (!acc) return;
            selectedId = id;
            inputs.id.value = acc.id;
            inputs.name.value = acc.name || '';
            inputs.dsid.value = acc.dsid || '';
            inputs.clientId.value = acc.clientId || '';
            inputs.cookies.value = acc.cookies || '';
            inputs.email.value = acc.email || '';
            inputs.appPassword.value = '';
            inputs.appPassword.placeholder = acc.appPassword === '********' ? '******** (sudah diatur, kosongkan untuk tidak mengubah)' : 'App-specific password';
            inputs.subdomain.value = acc.subdomain || '';
            formTitle.textContent = 'Edit Akun: ' + acc.name;
            btnDelete.classList.remove('hidden');
            renderList();
        }
        function resetForm(forNew = true) {
            selectedId = '';
            Object.values(inputs).forEach(i => { if (i.tagName !== 'INPUT' || i.type !== 'hidden') i.value = ''; });
            inputs.id.value = '';
            inputs.appPassword.placeholder = 'App-specific password';
            formTitle.textContent = forNew ? 'Tambah Akun Baru' : 'Form Akun';
            btnDelete.classList.add('hidden');
            renderList();
        }

        loginForm.addEventListener('submit', async e => {
            e.preventDefault();
            currentAccessCode = inputAccessCode.value.trim();
            btnLogin.disabled = true; btnLogin.textContent = 'Memeriksa...';
            try {
                const res = await fetch(apiUrl('admin/accounts'), { headers: apiHeaders() });
                if (res.status === 401) { showLoginMessage('Access Code salah.', true); currentAccessCode = ''; return; }
                const result = await res.json();
                if (!result.success) { showLoginMessage(result.error || 'Gagal masuk.', true); return; }
                sessionStorage.setItem('adminAccessCode', currentAccessCode);
                loginSection.classList.add('hidden'); adminSection.classList.remove('hidden');
                accountsCache = result.data; renderList();
            } catch (e) { showLoginMessage('Gagal menghubungi server.', true); }
            finally { btnLogin.disabled = false; btnLogin.textContent = 'Masuk'; }
        });

        btnNew.addEventListener('click', () => resetForm(true));
        btnReset.addEventListener('click', () => resetForm(true));

        form.addEventListener('submit', async e => {
            e.preventDefault();
            btnSave.disabled = true; btnSave.textContent = 'Menyimpan...';
            try {
                const payload = {
                    id: inputs.id.value || undefined,
                    name: inputs.name.value.trim(),
                    dsid: inputs.dsid.value.trim(),
                    clientId: inputs.clientId.value.trim(),
                    cookies: inputs.cookies.value.trim(),
                    email: inputs.email.value.trim(),
                    subdomain: inputs.subdomain.value.trim()
                };
                const pwd = inputs.appPassword.value.trim();
                if (pwd) payload.appPassword = pwd;
                const result = await saveAccount(payload);
                if (!result.success) return showMessage(result.error || 'Gagal menyimpan.', true);
                showMessage('Berhasil disimpan.');
                await refreshList(); selectAccount(result.data.id);
            } catch (e) { showMessage('Terjadi kesalahan jaringan.', true); }
            finally { btnSave.disabled = false; btnSave.textContent = 'Simpan'; }
        });

        btnDelete.addEventListener('click', async () => {
            if (!selectedId) return;
            const acc = accountsCache.find(a => a.id === selectedId);
            if (!acc) return;
            if (!confirm(`Hapus akun "${acc.name}"?`)) return;
            const result = await deleteAccount(selectedId);
            if (!result.success) return showMessage(result.error || 'Gagal menghapus.', true);
            showMessage('Akun dihapus.');
            resetForm(true); await refreshList();
        });

        // ===== TAB SWITCHING =====
        const tabBtns  = document.querySelectorAll('.tab-btn');
        const tabPanes = { accounts: $('tab-accounts'), members: $('tab-members') };
        function switchTab(name) {
            tabBtns.forEach(b => {
                const on = b.dataset.tab === name;
                b.classList.toggle('border-blue-500', on);
                b.classList.toggle('text-blue-600', on);
                b.classList.toggle('border-transparent', !on);
                b.classList.toggle('text-gray-600', !on);
            });
            Object.entries(tabPanes).forEach(([k, el]) => el && el.classList.toggle('hidden', k !== name));
            if (name === 'members') loadMembers();
        }
        tabBtns.forEach(b => b.addEventListener('click', () => switchTab(b.dataset.tab)));

        // ===== MEMBER MODULE (terpadu dengan voucher akses) =====
        const memberList = $('member-list'), memberForm = $('member-form'), memberFilter = $('m-filter');
        const mInputs = {
            id: $('member-id'), name: $('m-name'), wa: $('m-wa'), note: $('m-note'),
            duration: $('m-duration'), durationCustom: $('m-duration-custom'),
            price: $('m-price'), code: $('m-code'),
            dailyLimit: $('m-daily-limit'), dailyLimitCustom: $('m-daily-limit-custom'),
            autoDel: $('m-autodel'), autoDelCustom: $('m-autodel-custom')
        };
        const mBtns = {
            save: $('m-save'), delete: $('m-delete'), reset: $('m-reset'), new: $('btn-member-new'),
            extend: $('m-extend'), revoke: $('m-revoke'), unrevoke: $('m-unrevoke'),
            resetv: $('m-resetv'), forceLogout: $('m-force-logout')
        };
        const memberMsg = $('member-message');
        let membersCache = [], selectedMemberId = '';

        const showMemberMsg = (t, e = false) => {
            memberMsg.textContent = t;
            memberMsg.className = 'mt-4 p-3 rounded text-sm text-center font-medium block ' + (e ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700');
            setTimeout(() => memberMsg.classList.add('hidden'), 4500);
        };
        const fmtIDR = n => 'Rp ' + (Number(n) || 0).toLocaleString('id-ID');
        const fmtDate = s => s ? new Date(s).toLocaleString('id-ID', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '—';
        const statusBadge = s => {
            const colors = { unused: 'bg-gray-100 text-gray-700', active: 'bg-green-100 text-green-700', expired: 'bg-yellow-100 text-yellow-700', revoked: 'bg-red-100 text-red-700' };
            const labels = { unused: 'belum dipakai', active: 'aktif', expired: 'kedaluwarsa', revoked: 'dicabut' };
            return `<span class="text-xs px-2 py-0.5 rounded-full font-semibold ${colors[s] || ''}">${labels[s] || s}</span>`;
        };

        mInputs.duration.addEventListener('change', () => {
            mInputs.durationCustom.classList.toggle('hidden', mInputs.duration.value !== 'custom');
        });
        mInputs.dailyLimit.addEventListener('change', () => {
            mInputs.dailyLimitCustom.classList.toggle('hidden', mInputs.dailyLimit.value !== 'custom');
        });
        mInputs.autoDel.addEventListener('change', () => {
            mInputs.autoDelCustom.classList.toggle('hidden', mInputs.autoDel.value !== 'custom');
        });
        memberFilter.addEventListener('change', renderMembers);

        async function loadMembers() {
            const res = await fetch(apiUrl('admin/members'), { headers: apiHeaders() });
            const j = await res.json();
            membersCache = j.success ? j.data : [];
            renderMembers();
        }
        function renderMembers() {
            const filter = memberFilter.value;
            const list = filter ? membersCache.filter(m => m.status === filter) : membersCache;
            memberList.innerHTML = '';
            if (!list.length) {
                memberList.innerHTML = '<p class="text-sm text-gray-500 text-center py-4">Belum ada member.</p>';
                return;
            }
            list.forEach(m => {
                const el = document.createElement('div');
                const active = m.id === selectedMemberId;
                el.className = 'p-2 rounded cursor-pointer border transition ' + (active ? 'bg-blue-50 border-blue-300' : 'bg-white border-gray-200 hover:bg-gray-100');
                const remaining = m.remainingDays != null ? (m.remainingDays + ' hari lagi') : '';
                const wa = m.whatsapp || '';
                const waLink = wa ? `<a href="https://wa.me/${escapeHtml(wa)}" target="_blank" class="text-green-600 hover:underline" onclick="event.stopPropagation()">${escapeHtml(wa)}</a>` : '<span class="text-gray-400">—</span>';
                const limitTxt = (m.dailyLimit ?? 15) === 0 ? '∞' : ((m.dailyLimit ?? 15) + '/hari');
                const adTxt = (m.autoDeleteMin ?? 10) === 0 ? 'auto-del OFF' : ('auto-del ' + (m.autoDeleteMin ?? 10) + 'm');
                const deviceTxt = m.currentDeviceId ? ' · 📱 online' : '';
                el.innerHTML = `
                    <div class="flex justify-between items-start gap-2">
                        <div class="font-semibold text-sm text-gray-800 truncate">${escapeHtml(m.name)}</div>
                        ${statusBadge(m.status)}
                    </div>
                    <div class="text-xs">${waLink}</div>
                    <div class="font-mono text-xs text-blue-700 mt-1 break-all">${escapeHtml(m.voucherCode || '')}</div>
                    <div class="text-xs text-gray-500 mt-1">${m.durationDays} hari · ${fmtIDR(m.priceIdr)}${remaining ? ' · ' + remaining : ''}</div>
                    <div class="text-xs text-gray-500">${limitTxt} · ${adTxt}${deviceTxt}</div>`;
                el.addEventListener('click', () => selectMember(m.id));
                memberList.appendChild(el);
            });
        }
        function selectMember(id) {
            const m = membersCache.find(x => x.id === id);
            if (!m) return;
            selectedMemberId = id;
            mInputs.id.value = m.id;
            mInputs.name.value = m.name || '';
            mInputs.wa.value = m.whatsapp || '';
            mInputs.note.value = m.note || '';
            mInputs.price.value = m.priceIdr || 0;
            mInputs.code.value = m.voucherCode || '';
            // Set durasi dropdown
            const presets = ['7', '30'];
            if (presets.includes(String(m.durationDays))) {
                mInputs.duration.value = String(m.durationDays);
                mInputs.durationCustom.classList.add('hidden');
            } else {
                mInputs.duration.value = 'custom';
                mInputs.durationCustom.classList.remove('hidden');
                mInputs.durationCustom.value = m.durationDays;
            }
            // Daily limit
            const dl = m.dailyLimit ?? 15;
            if (dl === 0) {
                mInputs.dailyLimit.value = '0';
                mInputs.dailyLimitCustom.classList.add('hidden');
            } else if (dl === 15) {
                mInputs.dailyLimit.value = '15';
                mInputs.dailyLimitCustom.classList.add('hidden');
            } else {
                mInputs.dailyLimit.value = 'custom';
                mInputs.dailyLimitCustom.classList.remove('hidden');
                mInputs.dailyLimitCustom.value = dl;
            }
            // Auto-delete minutes
            const ad = m.autoDeleteMin ?? 10;
            const adPresets = ['0','5','10','30','60','180','1440'];
            if (adPresets.includes(String(ad))) {
                mInputs.autoDel.value = String(ad);
                mInputs.autoDelCustom.classList.add('hidden');
            } else {
                mInputs.autoDel.value = 'custom';
                mInputs.autoDelCustom.classList.remove('hidden');
                mInputs.autoDelCustom.value = ad;
            }
            $('member-form-title').textContent = 'Edit: ' + m.name;
            mBtns.delete.classList.remove('hidden');
            mBtns.extend.classList.remove('hidden');
            mBtns.resetv.classList.remove('hidden');
            mBtns.forceLogout.classList.toggle('hidden', !m.currentDeviceId);
            mBtns.revoke.classList.toggle('hidden', m.status === 'revoked');
            mBtns.unrevoke.classList.toggle('hidden', m.status !== 'revoked');
            renderMembers();
        }
        function resetMemberForm() {
            selectedMemberId = '';
            mInputs.id.value = '';
            mInputs.name.value = ''; mInputs.wa.value = ''; mInputs.note.value = '';
            mInputs.price.value = ''; mInputs.code.value = '';
            mInputs.duration.value = '30';
            mInputs.durationCustom.classList.add('hidden');
            mInputs.dailyLimit.value = '15';
            mInputs.dailyLimitCustom.classList.add('hidden');
            mInputs.autoDel.value = '10';
            mInputs.autoDelCustom.classList.add('hidden');
            $('member-form-title').textContent = 'Tambah Member Baru';
            ['delete', 'extend', 'revoke', 'unrevoke', 'resetv', 'forceLogout'].forEach(k => mBtns[k].classList.add('hidden'));
            renderMembers();
        }
        mBtns.new.addEventListener('click', resetMemberForm);
        mBtns.reset.addEventListener('click', resetMemberForm);

        function readDurationDays() {
            if (mInputs.duration.value === 'custom') {
                const d = parseInt(mInputs.durationCustom.value, 10);
                return (!d || d <= 0) ? null : d;
            }
            return parseInt(mInputs.duration.value, 10);
        }
        function readDailyLimit() {
            if (mInputs.dailyLimit.value === 'custom') {
                const d = parseInt(mInputs.dailyLimitCustom.value, 10);
                return (!d || d <= 0) ? null : d;
            }
            return parseInt(mInputs.dailyLimit.value, 10);
        }
        function readAutoDel() {
            if (mInputs.autoDel.value === 'custom') {
                const d = parseInt(mInputs.autoDelCustom.value, 10);
                return (!d || d < 0) ? null : d;
            }
            return parseInt(mInputs.autoDel.value, 10);
        }

        memberForm.addEventListener('submit', async e => {
            e.preventDefault();
            const isUpdate = !!mInputs.id.value;
            const duration = readDurationDays();
            if (!duration) return showMemberMsg('Isi jumlah hari custom yang valid.', true);
            const dailyLimit = readDailyLimit();
            if (dailyLimit === null) return showMemberMsg('Isi limit harian yang valid.', true);
            const autoDeleteMin = readAutoDel();
            if (autoDeleteMin === null) return showMemberMsg('Isi durasi auto-delete yang valid.', true);
            const payload = {
                name: mInputs.name.value.trim(),
                whatsapp: mInputs.wa.value.trim(),
                note: mInputs.note.value.trim(),
                priceIdr: parseInt(mInputs.price.value, 10) || 0,
                durationDays: duration,
                voucherCode: mInputs.code.value.trim(),
                dailyLimit: dailyLimit,
                autoDeleteMin: autoDeleteMin
            };
            const url = isUpdate ? apiUrl('admin/members/' + mInputs.id.value) : apiUrl('admin/members');
            const res = await fetch(url, {
                method: isUpdate ? 'PUT' : 'POST',
                headers: apiHeaders({ 'Content-Type': 'application/json' }),
                body: JSON.stringify(payload)
            });
            const j = await res.json();
            if (!j.success) return showMemberMsg(j.error || 'Gagal simpan.', true);
            showMemberMsg(isUpdate ? 'Member diperbarui.' : ('Member dibuat. Voucher: ' + j.data.voucherCode));
            await loadMembers();
            selectMember(j.data.id);
        });

        async function memberAction(action, extra) {
            if (!selectedMemberId) return;
            const payload = { action, ...(extra || {}) };
            const res = await fetch(apiUrl('admin/members/' + selectedMemberId), {
                method: 'PUT',
                headers: apiHeaders({ 'Content-Type': 'application/json' }),
                body: JSON.stringify(payload)
            });
            const j = await res.json();
            if (!j.success) return showMemberMsg(j.error || 'Gagal.', true);
            showMemberMsg('Berhasil: ' + action);
            await loadMembers();
            selectMember(selectedMemberId);
        }

        mBtns.extend.addEventListener('click', async () => {
            const m = membersCache.find(x => x.id === selectedMemberId);
            if (!m) return;
            const days = prompt('Perpanjang berapa hari?', String(m.durationDays || 30));
            const d = parseInt(days, 10);
            if (!d || d <= 0) return;
            await memberAction('extend', { extendDays: d });
        });
        mBtns.revoke.addEventListener('click', () => {
            if (confirm('Cabut akses member ini? Token aktif akan invalid.')) memberAction('revoke');
        });
        mBtns.unrevoke.addEventListener('click', () => memberAction('unrevoke'));
        mBtns.forceLogout.addEventListener('click', () => {
            if (confirm('Putuskan sesi member di semua device? Member harus login ulang dengan voucher.')) {
                memberAction('force-logout');
            }
        });
        mBtns.resetv.addEventListener('click', () => {
            if (confirm('Reset voucher ke status belum dipakai? Member harus aktivasi ulang.')) memberAction('reset');
        });
        mBtns.delete.addEventListener('click', async () => {
            if (!selectedMemberId) return;
            const m = membersCache.find(x => x.id === selectedMemberId);
            if (!confirm('Hapus member "' + (m && m.name) + '" beserta vouchernya?')) return;
            const res = await fetch(apiUrl('admin/members/' + selectedMemberId), { method: 'DELETE', headers: apiHeaders() });
            const j = await res.json();
            if (!j.success) return showMemberMsg(j.error || 'Gagal hapus.', true);
            resetMemberForm();
            await loadMembers();
        });

        // Klik baris akan select; klik tombol salin/wa langsung di dalam kartu
        memberList.addEventListener('click', async (e) => {
            const t = e.target;
            if (t.classList && t.classList.contains('m-copy')) {
                e.stopPropagation();
                try { await navigator.clipboard.writeText(t.dataset.code || ''); showMemberMsg('Kode disalin.'); } catch (er) {}
            }
        });

        // Tombol toggle password (mata buka/tutup)
        document.querySelectorAll('[data-pw-toggle]').forEach(btn => {
            btn.addEventListener('click', () => {
                const target = document.getElementById(btn.dataset.pwToggle);
                if (!target) return;
                const isPw = target.type === 'password';
                target.type = isPw ? 'text' : 'password';
                btn.querySelector('.eye-show')?.classList.toggle('hidden', isPw);
                btn.querySelector('.eye-hide')?.classList.toggle('hidden', !isPw);
            });
        });

        if (currentAccessCode) {
            (async () => {
                const res = await fetch(apiUrl('admin/accounts'), { headers: apiHeaders() });
                if (res.status === 401) { sessionStorage.removeItem('adminAccessCode'); currentAccessCode = ''; return; }
                const result = await res.json();
                if (result.success) {
                    loginSection.classList.add('hidden'); adminSection.classList.remove('hidden');
                    accountsCache = result.data; renderList();
                }
            })();
        }
    });
})();
JS;
}

function inbox_html(string $script, array $missing): void {
    ?><!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Universal Inbox — Hide My Email</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
    body {
        font-family: 'Inter', sans-serif;
    }
    .custom-scrollbar::-webkit-scrollbar {
        width: 6px;
        height: 6px;
    }
    .custom-scrollbar::-webkit-scrollbar-track {
        background: transparent;
    }
    .custom-scrollbar::-webkit-scrollbar-thumb {
        background: #cbd5e1;
        border-radius: 3px;
    }
    .custom-scrollbar::-webkit-scrollbar-thumb:hover {
        background: #94a3b8;
    }
    /* Robust desktop sidebar styling override for pre-compiled Tailwind */
    @media (min-width: 768px) {
        .md-w-380 {
            width: 380px !important;
        }
    }
</style>
</head>
<body class="bg-gray-50 text-gray-950 h-screen overflow-hidden flex flex-col">

<!-- LOGIN OVERLAY -->
<div id="login-overlay" class="hidden fixed inset-0 z-50 bg-gray-100 flex items-center justify-center p-4">
    <div class="bg-white p-8 rounded-xl shadow-lg w-full max-w-md border border-gray-100">
        <div class="flex justify-center mb-6">
            <div class="w-16 h-16 bg-blue-50 text-blue-500 rounded-full flex items-center justify-center shadow-inner">
                <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0-1.105.895-2 2-2s2 .895 2 2-.895 2-2 2-2-.895-2-2zM5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
            </div>
        </div>
        <h1 class="text-2xl font-bold text-center text-gray-800 mb-2">Akses Universal Inbox</h1>
        <p class="text-sm text-gray-500 text-center mb-6">Masukkan kode voucher Anda untuk memantau semua email yang masuk secara realtime.</p>
        <form id="login-form" class="space-y-4">
            <div class="relative">
                <input type="password" id="login-code" autocomplete="off" spellcheck="false"
                    class="block w-full border-gray-300 border rounded-lg shadow-sm p-3 pr-12 text-center font-mono uppercase tracking-wider focus:ring-blue-500 focus:border-blue-500 focus:outline-none"
                    placeholder="HME-XXXX-XXXX-XXXX" required>
                <button type="button" data-pw-toggle="login-code"
                    class="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition" title="Tampilkan / Sembunyikan">
                    <svg class="eye-show w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
                    <svg class="eye-hide w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.542-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.542 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>
                </button>
            </div>
            <button type="submit" id="login-submit" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-3 px-4 rounded-lg shadow-md hover:shadow-lg transition">Masuk ke Inbox</button>
        </form>
        <div id="login-message" class="hidden mt-4 p-3 rounded-lg text-sm text-center font-medium"></div>
        <p class="text-xs text-gray-400 text-center mt-6">Belum punya voucher? Hubungi admin via WhatsApp.</p>
    </div>
</div>

<!-- HEADER -->
<header class="bg-white border-b border-gray-200 flex-shrink-0 shadow-sm">
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between gap-4">
        <div class="flex items-center gap-3">
            <span class="text-2xl">📥</span>
            <div>
                <h1 class="text-lg font-bold text-gray-900 leading-tight">Universal Inbox</h1>
                <p class="text-[10px] text-gray-500 font-medium tracking-wide uppercase">iCloud Hide My Email</p>
            </div>
        </div>
        
        <div class="flex items-center gap-4">
            <!-- ACCOUNT SELECTOR -->
            <div class="hidden sm:block">
                <select id="account-picker" class="block w-full border-gray-300 border rounded-lg shadow-sm px-3 py-1.5 text-xs font-semibold text-gray-700 bg-gray-50 hover:bg-gray-100 transition focus:outline-none focus:ring-2 focus:ring-blue-500"></select>
            </div>

            <!-- SYNC STATUS -->
            <div class="flex items-center gap-1.5 bg-gray-50 px-2.5 py-1.5 rounded-full border border-gray-100 text-[11px] font-medium text-gray-600">
                <span id="sync-dot" class="w-2.5 h-2.5 rounded-full bg-green-500 animate-pulse"></span>
                <span id="sync-text">Sinkron</span>
                <span id="sync-timer" class="text-gray-400 text-[10px] font-mono ml-0.5">(5s)</span>
            </div>

            <!-- HOME BUTTON -->
            <a href="./" class="text-xs font-semibold text-blue-600 hover:text-blue-800 bg-blue-50 hover:bg-blue-100 border border-blue-200 rounded-lg px-3 py-1.5 transition flex items-center gap-1.5" title="Kembali ke Beranda / Generator">
                <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>
                <span class="hidden sm:inline">Generator</span>
            </a>

            <!-- LOGOUT BUTTON -->
            <button id="btn-logout" class="text-xs font-semibold text-red-600 hover:text-red-800 bg-red-50 hover:bg-red-100 border border-red-200 rounded-lg px-3 py-1.5 transition">
                Keluar
            </button>
        </div>
    </div>
</header>

<!-- MAIN WORKSPACE -->
<main class="flex-1 max-w-7xl w-full mx-auto px-0 sm:px-6 lg:px-8 py-0 sm:py-6 flex flex-row overflow-hidden">
    <div class="flex-1 bg-white border-0 sm:border border-gray-200 rounded-none sm:rounded-xl shadow-none sm:shadow-sm flex overflow-hidden">
        
        <!-- LEFT PANEL: THREAD LIST -->
        <div id="list-panel" class="w-full md-w-380 border-r border-gray-200 flex flex-col bg-gray-50 md:bg-white flex-shrink-0">
            <!-- SEARCH AND ACCOUNT BAR -->
            <div class="p-3 border-b border-gray-200 flex flex-col gap-2 bg-white">
                <div class="relative">
                    <input type="text" id="search-input" placeholder="Cari subjek atau pengirim..." class="w-full border border-gray-300 rounded-lg py-2 pl-9 pr-4 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
                    <svg class="w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
                </div>
                <!-- Account Selector for Mobile -->
                <div class="block sm:hidden">
                    <select id="account-picker-mobile" class="w-full border-gray-300 border rounded-lg p-2 text-xs font-semibold text-gray-700 bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"></select>
                </div>
            </div>

            <!-- THREAD LIST WRAPPER -->
            <div id="thread-list" class="flex-1 overflow-y-auto custom-scrollbar p-2 space-y-2">
                <div class="flex items-center justify-center py-12">
                    <svg class="animate-spin h-6 w-6 text-blue-500 mr-2" viewBox="0 0 24 24" fill="none">
                        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                    </svg>
                    <span class="text-sm text-gray-500 font-medium">Memuat inbox...</span>
                </div>
            </div>
        </div>

        <!-- RIGHT PANEL: DETAILS VIEW -->
        <div id="detail-panel" class="hidden md:flex flex-col flex-1 bg-white relative">
            <!-- Back Button for Mobile -->
            <div class="md:hidden flex items-center border-b border-gray-200 p-3 bg-gray-50">
                <button id="btn-back-to-list" class="flex items-center text-blue-600 hover:text-blue-800 text-sm font-semibold gap-1.5 focus:outline-none">
                    <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
                    Kembali ke Inbox
                </button>
            </div>

            <div id="detail-content" class="flex-1 flex flex-col overflow-hidden">
                <!-- EMPTY STATE -->
                <div class="flex-1 flex flex-col items-center justify-center p-8 text-center bg-gray-50 bg-opacity-50">
                    <div class="w-16 h-16 rounded-full bg-blue-50 text-blue-500 flex items-center justify-center mb-4">
                        <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 19v-8.93a2 2 0 01.89-1.664l8-4.666a2 2 0 012.22 0l8 4.666A2 2 0 0121 10.07V19M3 19a2 2 0 002 2h14a2 2 0 002-2M3 19l6.75-4.5M21 19l-6.75-4.5M3 10l6.75 4.5M21 10l-6.75 4.5m0 0l-2.25-1.5a2 2 0 00-2.22 0l-2.25 1.5"/></svg>
                    </div>
                    <h3 class="text-base font-bold text-gray-800">Tidak ada pesan yang dipilih</h3>
                    <p class="text-sm text-gray-500 mt-1 max-w-xs mx-auto">Pilih salah satu pesan dari list di sebelah kiri untuk melihat isi detail email.</p>
                </div>
            </div>
        </div>

    </div>
</main>

<script>window.HME_CFG = { script: <?= json_encode($script) ?> };</script>
<script><?php echo inbox_js(); ?></script>
</body>
</html>
<?php
}

function inbox_js(): string {
    return <<<'JS'
(function () {
    const SCRIPT = window.HME_CFG.script;
    const apiUrl = (path, qs = '') => SCRIPT + '?path=' + encodeURIComponent(path) + (qs ? '&' + qs : '');

    document.addEventListener('DOMContentLoaded', () => {
        const $ = id => document.getElementById(id);
        const loginOverlay = $('login-overlay');
        const loginForm = $('login-form');
        const loginCode = $('login-code');
        const loginSubmit = $('login-submit');
        const loginMessage = $('login-message');
        const btnLogout = $('btn-logout');
        
        const accountPicker = $('account-picker');
        const accountPickerMobile = $('account-picker-mobile');
        
        const syncDot = $('sync-dot');
        const syncText = $('sync-text');
        const syncTimer = $('sync-timer');
        
        const searchInput = $('search-input');
        const threadList = $('thread-list');
        const listPanel = $('list-panel');
        const detailPanel = $('detail-panel');
        const detailContent = $('detail-content');
        const btnBackToList = $('btn-back-to-list');

        let accountsList = [];
        let selectedAccountId = '';
        let authToken = localStorage.getItem('memberToken') || '';
        let authMeta = null;
        let threadCache = [];
        let currentThreadId = '';
        let currentThreadSubject = '';
        let searchQuery = '';
        let pollingInterval = null;
        let countdown = 5;
        let isFetchingList = false;
        let isFetchingDetails = false;

        function getOrCreateDeviceId() {
            let id = localStorage.getItem('deviceId');
            if (!id) {
                id = (crypto.randomUUID && crypto.randomUUID()) ||
                     (Date.now().toString(36) + Math.random().toString(36).slice(2, 12));
                localStorage.setItem('deviceId', id);
            }
            return id;
        }
        const deviceId = getOrCreateDeviceId();

        const apiHeaders = (extra = {}) => {
            const id = selectedAccountId || '';
            const headers = { ...extra };
            if (id) headers['x-account-id'] = id;
            if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
            if (deviceId) headers['x-device-id'] = deviceId;
            return headers;
        };

        const escapeHtml = s => String(s || '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
        
        const parseAddress = raw => {
            if (!raw) return { name: '', email: '' };
            const m = String(raw).match(/^(.*?)\s*<([^>]+)>$/);
            if (m) return { name: m[1].replace(/['"]/g, '').trim(), email: m[2].trim() };
            if (String(raw).includes('@')) return { name: '', email: String(raw).trim() };
            return { name: String(raw).trim(), email: '' };
        };

        const getInitial = (name, email) => {
            const str = name || email || '?';
            return str.trim().charAt(0).toUpperCase();
        };

        const buildSrcDoc = html => `<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style>html,body{margin:0;padding:12px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#1f2937;background:#fff}img{max-width:100%;height:auto}a{color:#2563eb}table{max-width:100%!important}pre,code{white-space:pre-wrap;word-break:break-word}</style></head><body>${html}</body></html>`;
        const autosizeIframe = iframe => { try { const doc = iframe.contentDocument || iframe.contentWindow.document; const h = Math.max(doc.body?.scrollHeight || 0, doc.documentElement?.scrollHeight || 0, 200); iframe.style.height = (h + 30) + 'px'; } catch (e) {} };

        // Formatting date
        const formatMessageDate = dateVal => {
            if (!dateVal) return '';
            const msgDate = new Date(dateVal);
            const now = new Date();
            if (msgDate.toDateString() === now.toDateString()) {
                return msgDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
            } else if (msgDate.getFullYear() === now.getFullYear()) {
                return msgDate.toLocaleDateString([], { day: 'numeric', month: 'short' }) + ' ' + msgDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
            } else {
                return msgDate.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' });
            }
        };

        const getFullDateStr = dateVal => {
            if (!dateVal) return '';
            return new Date(dateVal).toLocaleString('id-ID', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });
        };

        // UI toggles
        function showLoginScreen() {
            loginOverlay.classList.remove('hidden');
        }
        function showAppScreen() {
            loginOverlay.classList.add('hidden');
            loadAccounts();
            startPolling();
        }

        function logout(reason) {
            authToken = '';
            authMeta = null;
            localStorage.removeItem('memberToken');
            stopPolling();
            showLoginScreen();
            if (reason) {
                loginMessage.textContent = reason;
                loginMessage.className = 'mt-4 p-3 rounded-lg text-sm text-center font-medium block bg-red-100 text-red-700';
                setTimeout(() => loginMessage.classList.add('hidden'), 5000);
            }
        }

        async function fetchMe() {
            const r = await fetch(apiUrl('auth/me'), { headers: apiHeaders() });
            if (r.status === 401 || r.status === 403) {
                const j = await r.json().catch(() => ({}));
                throw new Error(j.error || 'Sesi tidak valid');
            }
            const j = await r.json();
            if (!j.success) throw new Error(j.error || 'Sesi tidak valid');
            return j.data;
        }

        async function gateOrLoad() {
            if (!authToken) { showLoginScreen(); return; }
            try {
                authMeta = await fetchMe();
                showAppScreen();
            } catch (e) {
                logout(e.message);
            }
        }

        // Login form submit
        loginForm.addEventListener('submit', async (e) => {
            e.preventDefault();
            const code = loginCode.value.trim();
            if (!code) return;
            loginSubmit.disabled = true; loginSubmit.textContent = 'Memeriksa...';
            try {
                const r = await fetch(apiUrl('auth/redeem'), {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ code, deviceId, deviceInfo: { ua: navigator.userAgent } })
                });
                const j = await r.json();
                if (!j.success) {
                    loginMessage.textContent = j.error || 'Gagal memvalidasi voucher';
                    loginMessage.className = 'mt-4 p-3 rounded-lg text-sm text-center font-medium block bg-red-100 text-red-700';
                    return;
                }
                authToken = j.data.token;
                localStorage.setItem('memberToken', authToken);
                authMeta = await fetchMe();
                setTimeout(() => { showAppScreen(); }, 500);
            } catch (err) {
                loginMessage.textContent = 'Gangguan jaringan. Coba lagi.';
                loginMessage.className = 'mt-4 p-3 rounded-lg text-sm text-center font-medium block bg-red-100 text-red-700';
            } finally {
                loginSubmit.disabled = false; loginSubmit.textContent = 'Masuk ke Inbox';
            }
        });

        btnLogout.addEventListener('click', () => {
            if (confirm('Keluar dari sesi inbox?')) logout('Anda telah keluar.');
        });

        // Toggle Password Visibility
        document.querySelectorAll('[data-pw-toggle]').forEach(btn => {
            btn.addEventListener('click', () => {
                const target = document.getElementById(btn.dataset.pwToggle);
                if (!target) return;
                const isPw = target.type === 'password';
                target.type = isPw ? 'text' : 'password';
                btn.querySelector('.eye-show')?.classList.toggle('hidden', isPw);
                btn.querySelector('.eye-hide')?.classList.toggle('hidden', !isPw);
            });
        });

        // Load Accounts
        async function loadAccounts() {
            try {
                const res = await fetch(apiUrl('accounts'), { headers: apiHeaders() });
                const result = await res.json();
                accountsList = (result.success && result.data) ? result.data : [];
                
                accountPicker.innerHTML = '';
                accountPickerMobile.innerHTML = '';
                
                if (!accountsList.length) {
                    const noAcc = document.createElement('option');
                    noAcc.textContent = 'Tidak ada akun iCloud';
                    accountPicker.appendChild(noAcc);
                    accountPickerMobile.appendChild(noAcc.cloneNode(true));
                    return;
                }

                const lastUsed = localStorage.getItem('lastUsedAccountId');
                accountsList.forEach((a, idx) => {
                    const opt = document.createElement('option');
                    opt.value = a.id;
                    opt.textContent = 'Server ' + (idx + 1) + ' (' + (a.name || 'iCloud') + ')';
                    accountPicker.appendChild(opt);
                    accountPickerMobile.appendChild(opt.cloneNode(true));
                });

                if (lastUsed && accountsList.some(a => a.id === lastUsed)) {
                    selectedAccountId = lastUsed;
                    accountPicker.value = lastUsed;
                    accountPickerMobile.value = lastUsed;
                } else {
                    selectedAccountId = accountsList[0].id;
                }

                loadUniversalInbox(true);
            } catch (err) {
                console.error(err);
            }
        }

        function onAccountChange(e) {
            const val = e.target.value;
            selectedAccountId = val;
            accountPicker.value = val;
            accountPickerMobile.value = val;
            localStorage.setItem('lastUsedAccountId', val);
            loadUniversalInbox(true);
        }

        accountPicker.addEventListener('change', onAccountChange);
        accountPickerMobile.addEventListener('change', onAccountChange);

        // Fetch Inbox Threads
        async function loadUniversalInbox(isManual = false) {
            if (isFetchingList || !authToken) return;
            isFetchingList = true;
            
            if (isManual) {
                threadList.innerHTML = `
                    <div class="flex items-center justify-center py-12">
                        <svg class="animate-spin h-6 w-6 text-blue-500 mr-2" viewBox="0 0 24 24" fill="none">
                            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                        </svg>
                        <span class="text-sm text-gray-500 font-medium">Memuat inbox...</span>
                    </div>`;
                syncDot.className = 'w-2.5 h-2.5 rounded-full bg-yellow-500 animate-pulse';
                syncText.textContent = 'Memuat';
            }

            try {
                const r = await fetch(apiUrl('inbox/universal'), { headers: apiHeaders() });
                if (r.status === 401 || r.status === 403) {
                    const j = await r.json().catch(() => ({}));
                    logout(j.error || 'Sesi berakhir');
                    return;
                }
                const result = await r.json();
                if (!result.success) {
                    syncDot.className = 'w-2.5 h-2.5 rounded-full bg-red-500';
                    syncText.textContent = 'Gagal Sinkron';
                    return;
                }
                
                threadCache = result.data || [];
                renderThreadList();
                
                syncDot.className = 'w-2.5 h-2.5 rounded-full bg-green-500';
                syncText.textContent = 'Sinkron';
                
                if (currentThreadId) {
                    refreshActiveThread(false);
                }
            } catch (e) {
                console.error(e);
                syncDot.className = 'w-2.5 h-2.5 rounded-full bg-red-500';
                syncText.textContent = 'Gagal Sinkron';
            } finally {
                isFetchingList = false;
            }
        }

        // Render Thread List
        function renderThreadList() {
            const query = searchQuery.trim().toLowerCase();
            const filtered = threadCache.filter(t => {
                const fromInfo = parseAddress(t.from);
                const senderName = fromInfo.name.toLowerCase();
                const senderEmail = fromInfo.email.toLowerCase();
                const subject = (t.subject || '').toLowerCase();
                const text = (t.text || '').toLowerCase();
                return senderName.includes(query) || senderEmail.includes(query) || subject.includes(query) || text.includes(query);
            });

            if (!filtered.length) {
                threadList.innerHTML = `
                    <div class="text-center py-12 px-4 bg-white rounded-lg border border-gray-150 shadow-sm">
                        <span class="text-3xl">📭</span>
                        <h4 class="text-sm font-bold text-gray-700 mt-2">Tidak ada pesan</h4>
                        <p class="text-xs text-gray-500 mt-1">Belum ada email masuk atau tidak ada yang cocok dengan pencarian.</p>
                    </div>`;
                return;
            }

            threadList.innerHTML = '';
            
            const colors = [
                'bg-blue-500', 'bg-green-500', 'bg-indigo-500', 'bg-purple-500', 
                'bg-pink-500', 'bg-yellow-600', 'bg-red-500', 'bg-teal-500', 'bg-orange-500'
            ];

            filtered.forEach(t => {
                const fromInfo = parseAddress(t.from);
                const charCodeSum = (fromInfo.email || 'a').split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
                const avatarBg = colors[charCodeSum % colors.length];
                
                const card = document.createElement('div');
                const isActive = t.id === currentThreadId;
                
                card.className = `p-3.5 rounded-xl border transition cursor-pointer flex gap-3 relative select-none ${
                    isActive 
                        ? 'bg-blue-50 border-blue-200 shadow-sm ring-1 ring-blue-100' 
                        : 'bg-white border-gray-200 hover:bg-gray-50 hover:border-gray-300'
                }`;

                const folderBadge = t.folder === 'Junk' 
                    ? '<span class="px-1.5 py-0.5 text-[9px] font-extrabold bg-yellow-100 text-yellow-800 border border-yellow-200 rounded uppercase tracking-wider flex-shrink-0">Junk</span>' 
                    : '';

                const toInfo = parseAddress(t.to);
                const recipientEmail = toInfo.email || t.to || '';
                const emailLabel = recipientEmail 
                    ? `<div class="text-[10px] text-blue-600 font-semibold mt-0.5">${t.folder === 'Junk' ? 'Junk' : 'Inbox'} Email: ${escapeHtml(recipientEmail)}</div>`
                    : `<div class="text-[10px] text-gray-400 font-medium mt-0.5">${t.folder === 'Junk' ? 'Junk' : 'Inbox'} Email: —</div>`;

                card.innerHTML = `
                    <div class="w-10 h-10 rounded-full ${avatarBg} text-white flex items-center justify-center font-bold text-sm select-none flex-shrink-0 shadow-sm">
                        ${getInitial(fromInfo.name, fromInfo.email)}
                    </div>
                    <div class="min-w-0 flex-1 flex flex-col justify-between">
                        <div class="flex items-start justify-between gap-1.5">
                            <span class="font-bold text-xs text-gray-900 truncate pr-1">${escapeHtml(fromInfo.name || fromInfo.email)}</span>
                            <span class="text-[10px] text-gray-400 font-medium whitespace-nowrap flex-shrink-0">${formatMessageDate(t.date)}</span>
                        </div>
                        <div class="flex items-center gap-1.5 mt-0.5">
                            <span class="font-semibold text-xs text-gray-800 truncate">${escapeHtml(t.subject || '(Tanpa Subjek)')}</span>
                            ${folderBadge}
                        </div>
                        ${emailLabel}
                        <div class="text-[11px] text-gray-500 truncate mt-0.5 leading-normal">${escapeHtml(t.text || '(Tidak ada pratinjau)')}</div>
                    </div>
                `;

                card.addEventListener('click', () => selectThread(t.id, t.subject));
                threadList.appendChild(card);
            });
        }

        // Search inputs
        searchInput.addEventListener('input', () => {
            searchQuery = searchInput.value;
            renderThreadList();
        });

        // Back to list on mobile
        btnBackToList.addEventListener('click', () => {
            currentThreadId = '';
            currentThreadSubject = '';
            
            detailPanel.classList.add('hidden');
            detailPanel.classList.remove('flex');
            listPanel.classList.remove('hidden');
            listPanel.classList.add('flex');
            
            renderThreadList();
        });

        // Select Thread
        function selectThread(threadId, subject) {
            currentThreadId = threadId;
            currentThreadSubject = subject;
            
            renderThreadList();

            if (window.innerWidth < 768) {
                listPanel.classList.add('hidden');
                listPanel.classList.remove('flex');
                detailPanel.classList.remove('hidden');
                detailPanel.classList.add('flex');
            } else {
                detailPanel.classList.remove('hidden');
                detailPanel.classList.add('flex');
            }

            refreshActiveThread(true);
        }

        // Fetch details of active thread
        async function refreshActiveThread(showLoader = false) {
            if (isFetchingDetails || !currentThreadId) return;
            isFetchingDetails = true;

            if (showLoader) {
                detailContent.innerHTML = `
                    <div class="flex-1 flex flex-col items-center justify-center p-8 text-center bg-gray-50 bg-opacity-50">
                        <svg class="animate-spin h-8 w-8 text-blue-500 mb-3" viewBox="0 0 24 24" fill="none">
                            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                        </svg>
                        <h4 class="text-sm font-bold text-gray-700">Memuat detail thread...</h4>
                    </div>`;
            }

            try {
                const r = await fetch(apiUrl('inbox/universal/thread'), {
                    method: 'POST',
                    headers: apiHeaders({ 'Content-Type': 'application/json' }),
                    body: JSON.stringify({ threadId: currentThreadId })
                });
                const result = await r.json();
                
                if (!currentThreadId) return; 

                if (!result.success || !result.data || !result.data.length) {
                    detailContent.innerHTML = `
                        <div class="flex-1 flex flex-col items-center justify-center p-8 text-center bg-gray-50 bg-opacity-50">
                            <span class="text-3xl">⚠️</span>
                            <h4 class="text-sm font-bold text-red-600 mt-2">Gagal memuat pesan</h4>
                            <p class="text-xs text-gray-500 mt-1">Detail thread tidak dapat ditemukan atau sesi iCloud kedaluwarsa.</p>
                        </div>`;
                    return;
                }

                detailContent.innerHTML = `
                    <!-- Thread Header -->
                    <div class="p-4 border-b border-gray-200 bg-white flex-shrink-0 flex items-center justify-between gap-4">
                        <div class="min-w-0">
                            <h2 class="text-base font-bold text-gray-900 truncate leading-snug">${escapeHtml(currentThreadSubject || '(Tanpa Subjek)')}</h2>
                            <p class="text-xs text-gray-500 mt-0.5 truncate">Total ${result.data.length} pesan dalam percakapan</p>
                        </div>
                    </div>
                    <!-- Thread Messages Scroll Area -->
                    <div class="flex-1 overflow-y-auto custom-scrollbar p-4 space-y-4 bg-gray-50 bg-opacity-50" id="messages-container"></div>
                `;

                const container = $('messages-container');
                const colors = [
                    'bg-blue-500', 'bg-green-500', 'bg-indigo-500', 'bg-purple-500', 
                    'bg-pink-500', 'bg-yellow-600', 'bg-red-500', 'bg-teal-500', 'bg-orange-500'
                ];

                result.data.forEach((msg, idx) => {
                    const fromInfo = parseAddress(msg.from);
                    const charCodeSum = (fromInfo.email || 'a').split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
                    const avatarBg = colors[charCodeSum % colors.length];
                    const isLastMsg = (idx === result.data.length - 1);

                    const card = document.createElement('div');
                    card.className = 'bg-white rounded-xl border border-gray-200 overflow-hidden shadow-sm transition duration-200';
                    card.innerHTML = `
                        <!-- Card Header -->
                        <div class="p-4 cursor-pointer hover:bg-gray-100 flex items-center justify-between border-b border-gray-100 transition header-click">
                            <div class="flex items-center min-w-0">
                                <div class="w-9 h-9 rounded-full ${avatarBg} text-white flex items-center justify-center font-bold text-xs select-none flex-shrink-0 shadow-sm">
                                    ${getInitial(fromInfo.name, fromInfo.email)}
                                </div>
                                <div class="ml-3 min-w-0">
                                    <div class="font-bold text-gray-800 text-sm truncate">${escapeHtml(fromInfo.name || fromInfo.email || 'Unknown')}</div>
                                    <div class="text-xs text-gray-400 truncate mt-0.5">Kepada: Hide My Email</div>
                                </div>
                            </div>
                            <div class="text-right flex flex-col items-end flex-shrink-0 ml-4">
                                <div class="text-xs text-gray-400 font-medium">${formatMessageDate(msg.date)}</div>
                                <div class="text-xs text-blue-600 hover:text-blue-800 font-bold cursor-pointer select-none mt-1 btn-toggle-details">Rincian</div>
                            </div>
                        </div>

                        <!-- Collapsible Details -->
                        <div class="hidden px-4 py-3 border-b border-gray-100 bg-gray-50 bg-opacity-50 text-xs text-gray-600 space-y-1 select-text details-panel">
                            <div><span class="font-semibold text-gray-700">Dari:</span> ${escapeHtml(msg.from)}</div>
                            <div><span class="font-semibold text-gray-700">Kepada:</span> ${escapeHtml(msg.to)}</div>
                            ${msg.cc ? `<div><span class="font-semibold text-gray-700">Tembusan (Cc):</span> ${escapeHtml(msg.cc)}</div>` : ''}
                            <div><span class="font-semibold text-gray-700">Tanggal:</span> ${getFullDateStr(msg.date)}</div>
                            <div><span class="font-semibold text-gray-700">Subjek:</span> ${escapeHtml(msg.subject || '(Tanpa Subjek)')}</div>
                        </div>

                        <!-- Card Body (collapsed by default unless last message) -->
                        <div class="${isLastMsg ? 'block' : 'hidden'} body-container">
                            <div class="bg-blue-50 bg-opacity-50 border-b border-blue-100 border-opacity-50 text-blue-900 text-xs px-4 py-2 flex items-center justify-between font-medium">
                                <span>Pesan ini diteruskan oleh Hide My Email. Kelola di <a href="https://www.icloud.com/icloudplus/" target="_blank" class="text-blue-600 hover:underline font-bold">iCloud+</a>.</span>
                            </div>
                            <div class="p-4 msg-mount bg-white"></div>
                        </div>
                    `;

                    const headerClick = card.querySelector('.header-click');
                    const btnToggleDetails = card.querySelector('.btn-toggle-details');
                    const detailsPanel = card.querySelector('.details-panel');
                    const bodyContainer = card.querySelector('.body-container');
                    const msgMount = card.querySelector('.msg-mount');

                    headerClick.addEventListener('click', (ev) => {
                        if (ev.target.classList.contains('btn-toggle-details')) return;
                        const isHidden = bodyContainer.classList.contains('hidden');
                        bodyContainer.classList.toggle('hidden', !isHidden);
                        if (isHidden) {
                            loadMsgBody();
                        }
                    });

                    btnToggleDetails.addEventListener('click', (ev) => {
                        ev.stopPropagation();
                        detailsPanel.classList.toggle('hidden');
                    });

                    let bodyLoaded = false;
                    function loadMsgBody() {
                        if (bodyLoaded) return;
                        bodyLoaded = true;

                        if (msg.html) {
                            const iframe = document.createElement('iframe');
                            iframe.style.cssText = 'width:100%; border:0; display:block; height:300px; background:#fff; overflow:hidden;';
                            iframe.setAttribute('scrolling', 'no');
                            iframe.srcdoc = buildSrcDoc(msg.html);
                            iframe.addEventListener('load', () => {
                                autosizeIframe(iframe);
                                setTimeout(() => autosizeIframe(iframe), 300);
                                setTimeout(() => autosizeIframe(iframe), 1000);
                                setTimeout(() => autosizeIframe(iframe), 2500);
                            });
                            msgMount.appendChild(iframe);
                        } else {
                            const pre = document.createElement('pre');
                            pre.className = 'whitespace-pre-wrap font-sans text-sm text-gray-700 bg-gray-50 bg-opacity-30 p-3 rounded-lg border border-gray-150';
                            pre.textContent = msg.text || '(Pesan Kosong)';
                            msgMount.appendChild(pre);
                        }
                    }

                    if (isLastMsg) {
                        loadMsgBody();
                    }

                    container.appendChild(card);
                });

            } catch (err) {
                console.error(err);
            } finally {
                isFetchingDetails = false;
            }
        }

        // Polling loop manager
        function startPolling() {
            stopPolling();
            countdown = 5;
            syncTimer.textContent = `(${countdown}s)`;
            
            pollingInterval = setInterval(() => {
                countdown--;
                if (countdown <= 0) {
                    countdown = 5;
                    loadUniversalInbox(false);
                }
                syncTimer.textContent = `(${countdown}s)`;
            }, 1000);
        }

        function stopPolling() {
            if (pollingInterval) {
                clearInterval(pollingInterval);
                pollingInterval = null;
            }
        }

        gateOrLoad();
    });
})();
JS;
}
