<?php
declare(strict_types=1);

/**
 * print_one.php
 * هر بار یک آیتم (خط vmess/vless/trojan/ss یا یک بلوک JSON متوازن) از خروجی لینک چاپ می‌کند.
 * - کش 4 دقیقه (240s)
 * - تضمین اینکه همه آیتم‌ها حداقل یکبار چاپ شوند (queue of indices)
 *
 * استفاده: php print_one.php
 */

$REMOTE_URL    = 'http://45.150.32.240/datacenter/out/misc.txt'; // <-- آدرس لینک
$CACHE_FILE    = __DIR__ . '/.cache_items.json';                 // آرایهٔ آیتم‌ها (raw)
$CACHE_AT      = __DIR__ . '/.cache_time';                      // زمان ساخت کش (timestamp)
$CACHE_TTL     = 240;                                           // ثانیه — 4 دقیقه
$QUEUE_FILE    = __DIR__ . '/.queue_indices.json';              // صف ایندکس‌ها (برای تضمین پوشش کامل)
$LOCK_FILE     = __DIR__ . '/.lock_print_one';                  // فایل قفل برای عملیات صف/ایندکس
$HTTP_TIMEOUT  = 10;                                            // ثانیه برای fetch

// --------- helper: fetch remote with timeout ----------
function fetch_remote(string $url, int $timeout): string|false {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_CONNECTTIMEOUT => max(2, (int)$timeout - 2),
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_USERAGENT => 'print_one_bot/1.0',
        CURLOPT_HTTPHEADER => ['Accept: text/plain, */*'],
    ]);
    $res = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err = curl_error($ch);
    curl_close($ch);
    if ($res === false || $code >= 400) {
        return false;
    }
    return $res;
}

// --------- helper: split raw content into items (preserve raw bytes, handle JSON blocks) ----------
function split_into_items(string $content): array {
    // Normalize newlines to \n
    $content = str_replace(["\r\n","\r"], ["\n","\n"], $content);

    $lines = preg_split("/\n/", $content);
    if ($lines === false) return [];

    $items = [];
    $i = 0;
    $total = count($lines);
    while ($i < $total) {
        $line = $lines[$i];

        // skip pure-empty lines
        if ($line === '') { $i++; continue; }

        $trim = ltrim($line);
        $first = $trim === '' ? '' : $trim[0];

        // If line starts a JSON block: { or [
        if ($first === '{' || $first === '[') {
            $buf = $line . "\n";
            $stack = [];
            // scan first line for opening brackets
            for ($k = 0, $len = strlen($line); $k < $len; $k++) {
                $ch = $line[$k];
                if ($ch === '{' || $ch === '[') $stack[] = $ch;
                if ($ch === '}' || $ch === ']') {
                    if (!empty($stack)) array_pop($stack);
                }
            }
            $i++;
            while (!empty($stack) && $i < $total) {
                $ln = $lines[$i];
                $buf .= $ln . "\n";
                // scan this line for brackets (best-effort)
                for ($k = 0, $len2 = strlen($ln); $k < $len2; $k++) {
                    $ch = $ln[$k];
                    if ($ch === '{' || $ch === '[') $stack[] = $ch;
                    if ($ch === '}' || $ch === ']') {
                        if (!empty($stack)) array_pop($stack);
                    }
                }
                $i++;
            }
            $items[] = rtrim($buf, "\n");
            continue;
        }

        // otherwise treat single line as item
        $items[] = $line;
        $i++;
    }

    // filter empties and reindex
    return array_values(array_filter($items, function($x){ return $x !== null && $x !== ''; }));
}

// --------- load or refresh cache ----------
function load_items_with_cache(string $remoteUrl, string $cacheFile, string $cacheAtFile, int $cacheTtl, int $timeout): array {
    // use fresh cache if exists
    if (is_file($cacheFile) && is_file($cacheAtFile)) {
        $ts = (int)@file_get_contents($cacheAtFile);
        if ($ts > 0 && (time() - $ts) < $cacheTtl) {
            $s = @file_get_contents($cacheFile);
            if ($s !== false) {
                $arr = json_decode($s, true);
                if (is_array($arr)) return $arr;
            }
        }
    }

    // try fetch remote
    $content = fetch_remote($remoteUrl, $timeout);
    if ($content === false) {
        // fallback to existing cache if present
        if (is_file($cacheFile)) {
            $s = @file_get_contents($cacheFile);
            if ($s !== false) {
                $arr = json_decode($s, true);
                if (is_array($arr)) return $arr;
            }
        }
        // no data
        return [];
    }

    // split to items
    $items = split_into_items($content);

    // atomic write cache
    $tmp = $cacheFile . '.tmp';
    @file_put_contents($tmp, json_encode($items, JSON_UNESCAPED_UNICODE));
    @rename($tmp, $cacheFile);
    @file_put_contents($cacheAtFile, (string)time(), LOCK_EX);

    return $items;
}

// --------- queue logic: get next index (ensures each item printed at least once) ----------
function get_next_index_from_queue(string $queueFile, string $lockFile, int $count): int {
    // open lock
    $lfh = fopen($lockFile, 'c+');
    if ($lfh === false) {
        // fallback non-locked behaviour
        if ($count <= 0) return 0;
        $q = range(0, $count - 1);
        $idx = array_shift($q);
        // try to save queue best-effort
        @file_put_contents($queueFile, json_encode($q, JSON_UNESCAPED_UNICODE), LOCK_EX);
        return $idx;
    }

    if (!flock($lfh, LOCK_EX)) {
        // cannot lock -> fallback
        if ($count <= 0) { fclose($lfh); return 0; }
        $q = range(0, $count - 1);
        $idx = array_shift($q);
        @file_put_contents($queueFile, json_encode($q, JSON_UNESCAPED_UNICODE), LOCK_EX);
        fclose($lfh);
        return $idx;
    }

    // lock acquired
    $queue = [];
    if (is_file($queueFile)) {
        $s = @file_get_contents($queueFile);
        if ($s !== false) {
            $tmp = json_decode($s, true);
            if (is_array($tmp)) $queue = $tmp;
        }
    }

    // if queue empty or invalid or count changed -> rebuild fresh queue 0..count-1
    $need_rebuild = false;
    if ($count <= 0) $need_rebuild = true;
    if (empty($queue)) $need_rebuild = true;
    else {
        // if any index in queue is out-of-range, rebuild
        foreach ($queue as $v) {
            if (!is_int($v) || $v < 0 || $v >= $count) { $need_rebuild = true; break; }
        }
    }

    if ($need_rebuild) {
        $queue = range(0, max(0, $count - 1));
    }

    // pop first element
    $idx = 0;
    if (!empty($queue)) {
        $idx = array_shift($queue);
    } else {
        // no items -> default 0
        $idx = 0;
    }

    // write back queue atomically
    $tmpq = $queueFile . '.tmp';
    @file_put_contents($tmpq, json_encode($queue, JSON_UNESCAPED_UNICODE));
    @rename($tmpq, $queueFile);

    // release lock
    flock($lfh, LOCK_UN);
    fclose($lfh);

    return $idx;
}

// --------- MAIN ----------
$items = load_items_with_cache($REMOTE_URL, $CACHE_FILE, $CACHE_AT, $CACHE_TTL, $HTTP_TIMEOUT);

if (empty($items)) {
    fwrite(STDERR, "ERROR: no items available (fetch failed and cache empty)\n");
    exit(2);
}

$count = count($items);
$idx = get_next_index_from_queue($QUEUE_FILE, $LOCK_FILE, $count);

// bounds safety
if ($idx < 0) $idx = 0;
if ($idx >= $count) $idx = $idx % $count;

// print exactly as-is
$item = $items[$idx];

if (!mb_check_encoding($item, 'UTF-8')) {
    // try binary-safe output
    echo $item . PHP_EOL;
} else {
    echo $item . PHP_EOL;
}

exit(0);