<?php
/**
 * FULL CONFIG EXTRACTOR (Links + Full V2RayNG JSON)
 * - Supports: vless, vmess, trojan, ss, hysteria, hy2, hysteria2
 * - SMART JSON detect:
 *      1) raw JSON (object/array)
 *      2) embedded "config":"{...}" strings
 *      3) multiple standalone JSON blocks in one text: {..}{..}{..} / [..][..]
 * - OUTPUT: only ONE config (equal probability)
 * - FIX: do NOT create duplicate outbound tags (prevents: existing tag found)
 * - FIX: stats must be OBJECT {} (not [])
 * - FIX: outbound.settings must be OBJECT {} (not [])
 * - FIX: dns.hosts must be OBJECT {} (not [])   <-- fixes your crash
 * - FORCE: loglevel none for JSON configs
 * - OPTIONAL: ?array=1 -> outputs [ { ... } ] for V2RayNG batch import
 */

declare(strict_types=1);

// ================= CACHE =================
$cacheFile = __DIR__ . '/cache.txt';
$cacheTime = 40;

$url = "https://lively-glade-f7a5.rezajafari0970.workers.dev";

if (is_file($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTime) {
    $response = file_get_contents($cacheFile);
} else {
    $ctx = stream_context_create([
        'http' => [
            'timeout' => 10,
            'header'  => "User-Agent: Mozilla/5.0\r\n",
        ],
        'ssl' => [
            'verify_peer'      => false,
            'verify_peer_name' => false,
        ],
    ]);

    $response = @file_get_contents($url, false, $ctx);
    if ($response === false || $response === null) {
        header('Content-Type: text/plain; charset=UTF-8');
        die("خطا در دریافت داده از API");
    }
    file_put_contents($cacheFile, $response, LOCK_EX);
}

define('CFG_NAME', '@shenvpn');

// ================= Helpers =================
function is_uuid($s): bool {
    return (bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', (string)$s);
}

function is_host_like($h): bool {
    $h = (string)$h;
    if ($h === '') return false;
    if (preg_match('/^\[[0-9a-f:]+\]$/i', $h)) return true;
    if (filter_var($h, FILTER_VALIDATE_IP)) return true;
    return (bool)preg_match('/^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i', $h);
}

function is_port($p): bool {
    $p = (string)$p;
    if (!ctype_digit($p)) return false;
    $n = (int)$p;
    return $n >= 1 && $n <= 65535;
}

function base64url_to_base64($s): string {
    $s = strtr((string)$s, '-_', '+/');
    $pad = strlen($s) % 4;
    if ($pad) $s .= str_repeat('=', 4 - $pad);
    return $s;
}

function safe_urldecode_once($s): string {
    $s = (string)$s;
    return preg_replace_callback('/%[0-9A-Fa-f]{2}/', fn($m) => rawurldecode($m[0]), $s);
}

function sanitize_link(string $u): string {
    $u = trim($u);
    $u = rtrim($u, " \t\r\n)];,.!?>");
    if (strpos($u, '#') !== false) {
        [$base, $frag] = explode('#', $u, 2);
        $frag = rawurlencode(rawurldecode($frag));
        $u = $base . '#' . $frag;
    }
    return $u;
}

function normalize_fragment_name(string $name): string {
    return rawurlencode($name);
}

function is_list_array($arr): bool {
    if (!is_array($arr)) return false;
    if ($arr === []) return true;
    return array_keys($arr) === range(0, count($arr) - 1);
}

// ================= Rename =================
function renameLinkConfig(string $cfg): string {
    $cfg = sanitize_link($cfg);

    if (stripos($cfg, 'vmess://') === 0) {
        $b64 = base64url_to_base64(trim(substr($cfg, 8)));
        $dec = base64_decode($b64, true);
        if ($dec === false) return $cfg;
        $json = json_decode($dec, true);
        if (!is_array($json)) return $cfg;
        $json['ps'] = CFG_NAME;
        return 'vmess://' . base64_encode(json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }

    if (stripos($cfg, 'ss://') === 0) {
        $x = ss_parse($cfg);
        if ($x && ss_validate_parsed($x)) {
            return ss_build_sip002($x['method'], $x['pass'], $x['host'], $x['port'], $x['query'] ?? '', normalize_fragment_name(CFG_NAME));
        }
        if (strpos($cfg, '#') !== false) return preg_replace('/#.*/', '#' . normalize_fragment_name(CFG_NAME), $cfg);
        return $cfg . '#' . normalize_fragment_name(CFG_NAME);
    }

    if (strpos($cfg, '#') !== false) return preg_replace('/#.*/', '#' . normalize_fragment_name(CFG_NAME), $cfg);
    return $cfg . '#' . normalize_fragment_name(CFG_NAME);
}

function renameJsonConfig(string $jsonRaw): string {
    $decoded = json_decode($jsonRaw, true);
    if (!is_array($decoded)) return $jsonRaw;
    if (is_list_array($decoded)) return $jsonRaw;

    $decoded['remarks'] = CFG_NAME;
    return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

function force_log_none_json(string $jsonRaw): string {
    $decoded = json_decode($jsonRaw, true);
    if (!is_array($decoded)) return $jsonRaw;
    if (is_list_array($decoded)) return $jsonRaw;

    if (!isset($decoded['log']) || !is_array($decoded['log'])) $decoded['log'] = [];
    $decoded['log']['loglevel'] = 'none';
    $decoded['log']['access'] = '';
    $decoded['log']['error']  = '';
    if (isset($decoded['log']['dnsLog'])) $decoded['log']['dnsLog'] = false;

    return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

/**
 * ✅ Normalize schema for V2RayNG parsing:
 * - stats must be OBJECT {}
 * - dns.hosts must be OBJECT {}
 * - each outbound.settings must be OBJECT {} (not [])
 * - streamSettings.sockopt must be OBJECT if wrong type
 */
function normalize_v2ray_json_schema(string $jsonRaw): string {
    $cfg = json_decode($jsonRaw, true);
    if (!is_array($cfg)) return $jsonRaw;
    if (is_list_array($cfg)) return $jsonRaw;

    // stats: [] -> {}
    if (array_key_exists('stats', $cfg)) {
        if (is_array($cfg['stats'])) {
            if (is_list_array($cfg['stats'])) $cfg['stats'] = new stdClass();
        } else {
            $cfg['stats'] = new stdClass();
        }
    }

    // dns.hosts: [] -> {}
    if (isset($cfg['dns']) && is_array($cfg['dns'])) {
        if (array_key_exists('hosts', $cfg['dns'])) {
            if (is_array($cfg['dns']['hosts'])) {
                if (is_list_array($cfg['dns']['hosts'])) $cfg['dns']['hosts'] = new stdClass();
            } else {
                $cfg['dns']['hosts'] = new stdClass();
            }
        }
    }

    // outbounds[*].settings: [] -> {}
    if (isset($cfg['outbounds']) && is_array($cfg['outbounds'])) {
        foreach ($cfg['outbounds'] as $i => $ob) {
            if (!is_array($ob)) continue;

            if (array_key_exists('settings', $ob)) {
                if (is_array($ob['settings']) && is_list_array($ob['settings'])) {
                    $cfg['outbounds'][$i]['settings'] = new stdClass();
                } elseif (!is_array($ob['settings'])) {
                    $cfg['outbounds'][$i]['settings'] = new stdClass();
                }
            }

            // streamSettings.sockopt sometimes must be object
            if (isset($ob['streamSettings']) && is_array($ob['streamSettings'])) {
                if (array_key_exists('sockopt', $ob['streamSettings'])) {
                    if (is_array($ob['streamSettings']['sockopt']) && is_list_array($ob['streamSettings']['sockopt'])) {
                        $cfg['outbounds'][$i]['streamSettings']['sockopt'] = new stdClass();
                    } elseif (!is_array($ob['streamSettings']['sockopt'])) {
                        $cfg['outbounds'][$i]['streamSettings']['sockopt'] = new stdClass();
                    }
                }
            }
        }
    }

    return json_encode($cfg, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

// ================= Validators =================
function validate_vless(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'vless') return false;

    if (empty($p['user']) || !is_uuid($p['user'])) return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (!isset($p['port']) || !is_port((string)$p['port'])) return false;

    parse_str($p['query'] ?? '', $q);
    $sec = strtolower((string)($q['security'] ?? ''));

    if ($sec === 'reality') {
        if (empty($q['pbk']) || strlen((string)$q['pbk']) < 10) return false;
        if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    } else {
        if ($sec === 'tls' && !empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    }
    return true;
}

function validate_vmess(string $url): bool {
    $url = sanitize_link($url);
    if (stripos($url, 'vmess://') !== 0) return false;

    $b64 = base64url_to_base64(trim(substr($url, 8)));
    $dec = base64_decode($b64, true);
    if ($dec === false) return false;

    $j = json_decode($dec, true);
    if (!is_array($j)) return false;

    $add  = (string)($j['add'] ?? '');
    $port = (string)($j['port'] ?? '');
    $id   = (string)($j['id'] ?? '');

    if ($add === '' || !is_host_like($add)) return false;
    if ($port === '' || !is_port($port)) return false;
    if ($id === '' || !is_uuid($id)) return false;

    $tls = strtolower((string)($j['tls'] ?? ''));
    $sni = (string)($j['sni'] ?? '');
    if ($tls !== '' && $tls !== 'none' && $sni !== '' && !is_host_like($sni)) return false;

    return true;
}

function validate_trojan(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'trojan') return false;

    if (empty($p['user']) || strlen((string)$p['user']) < 4) return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (!isset($p['port']) || !is_port((string)$p['port'])) return false;

    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;

    return true;
}

// ================= SS =================
function ss_parse(string $url): ?array {
    $url = sanitize_link($url);
    if (stripos($url, 'ss://') !== 0) return null;

    $raw = substr($url, 5);

    $frag = '';
    if (strpos($raw, '#') !== false) [$raw, $frag] = explode('#', $raw, 2);

    $query = '';
    if (strpos($raw, '?') !== false) [$raw, $query] = explode('?', $raw, 2);

    $raw = trim($raw);
    if ($raw === '') return null;

    $raw_dec = safe_urldecode_once($raw);
    if (strpos($raw_dec, '@') !== false && strpos($raw_dec, ':') !== false) {
        $p = @parse_url('ss://' . $raw_dec);
        if ($p && !empty($p['user']) && isset($p['pass']) && !empty($p['host']) && !empty($p['port'])) {
            return [
                'method' => (string)$p['user'],
                'pass'   => (string)$p['pass'],
                'host'   => (string)$p['host'],
                'port'   => (string)$p['port'],
                'query'  => $query,
                'frag'   => $frag
            ];
        }
    }

    if (strpos($raw, '@') !== false) {
        [$b64, $hp] = explode('@', $raw, 2);
        $b64 = safe_urldecode_once($b64);

        $dec = base64_decode(base64url_to_base64($b64), true);
        if ($dec !== false && preg_match('/^([^:]+):(.+)$/', $dec, $mm)) {
            $hp = safe_urldecode_once($hp);
            if (preg_match('/^(\[[0-9a-f:]+\]|[^:]+):(\d{1,5})$/i', $hp, $mh)) {
                return [
                    'method' => (string)$mm[1],
                    'pass'   => (string)$mm[2],
                    'host'   => (string)$mh[1],
                    'port'   => (string)$mh[2],
                    'query'  => $query,
                    'frag'   => $frag
                ];
            }
        }
    }

    $raw_b64 = safe_urldecode_once($raw);
    $dec = base64_decode(base64url_to_base64($raw_b64), true);
    if ($dec !== false && preg_match('/^([^:]+):(.+)@(\[[0-9a-f:]+\]|[^:]+):(\d{1,5})$/i', $dec, $m)) {
        return [
            'method' => (string)$m[1],
            'pass'   => (string)$m[2],
            'host'   => (string)$m[3],
            'port'   => (string)$m[4],
            'query'  => $query,
            'frag'   => $frag
        ];
    }

    return null;
}

function ss_validate_parsed(?array $x): bool {
    if (!$x) return false;
    if (!is_host_like((string)($x['host'] ?? ''))) return false;
    if (!is_port((string)($x['port'] ?? ''))) return false;
    if (strlen((string)($x['method'] ?? '')) < 2) return false;
    if (strlen((string)($x['pass'] ?? '')) < 1) return false;
    return true;
}

function ss_build_sip002(string $method, string $pass, string $host, string $port, string $query = '', string $frag = ''): string {
    $userinfo_b64 = rtrim(strtr(base64_encode($method . ':' . $pass), '+/', '-_'), '=');
    $out = "ss://{$userinfo_b64}@{$host}:{$port}";
    if ($query !== '') $out .= '?' . $query;
    if ($frag !== '')  $out .= '#' . $frag;
    return $out;
}

function validate_ss(string $url): bool { return ss_validate_parsed(ss_parse($url)); }

function validate_hysteria1(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'hysteria') return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (!isset($p['port']) || !is_port((string)$p['port'])) return false;
    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    return true;
}

function validate_hy2(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'hy2') return false;
    if (empty($p['host']) || !is_host_like((string)$p['host'])) return false;
    if (!isset($p['port']) || !is_port((string)$p['port'])) return false;
    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    return true;
}

function validate_hysteria2_alias(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'hysteria2') return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (!isset($p['port']) || !is_port((string)$p['port'])) return false;
    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    return true;
}

function validate_by_scheme(string $url): bool {
    $url = sanitize_link($url);
    $scheme = strtolower(parse_url($url, PHP_URL_SCHEME) ?? '');
    return match ($scheme) {
        'vless'     => validate_vless($url),
        'vmess'     => validate_vmess($url),
        'trojan'    => validate_trojan($url),
        'ss'        => validate_ss($url),
        'hysteria'  => validate_hysteria1($url),
        'hy2'       => validate_hy2($url),
        'hysteria2' => validate_hysteria2_alias($url),
        default     => false,
    };
}

// ================= Link extract =================
function extractLinkTokensPrecise(string $text): array {
    $out = [];
    $lines = preg_split("/\R/u", $text);
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '') continue;
        if (preg_match('/^(vless|vmess|trojan|ss|hysteria|hy2|hysteria2):\/\/.+/i', $line)) {
            $out[] = sanitize_link($line);
        }
    }

    $re = '/(?<![A-Za-z0-9_])(?P<scheme>vless|vmess|trojan|ss|hysteria|hy2|hysteria2):\/\/(?P<body>[^\s<>"\'`]+)/i';
    if (preg_match_all($re, $text, $m, PREG_SET_ORDER)) {
        foreach ($m as $one) {
            $scheme = strtolower($one['scheme']);
            $out[] = sanitize_link($scheme . '://' . $one['body']);
        }
    }
    return array_values(array_unique($out));
}

// ================= V2RayNG FULL JSON detect =================
function isV2rayNgFullConfig($decoded): bool {
    if (!is_array($decoded)) return false;
    if (is_list_array($decoded)) return false;
    if (!isset($decoded['outbounds']) || !is_array($decoded['outbounds']) || count($decoded['outbounds']) < 1) return false;

    foreach ($decoded['outbounds'] as $ob) {
        if (!is_array($ob)) continue;
        $p = strtolower((string)($ob['protocol'] ?? ''));
        if ($p === '') continue;
        if (in_array($p, ['vless','vmess','trojan','shadowsocks','ss','hysteria','hy2','hysteria2','socks','http','wireguard','freedom','blackhole','dns','loopback'], true)) {
            return true;
        }
    }
    return false;
}

function looksLikeJsonString(string $s): bool {
    $s = trim($s);
    if ($s === '') return false;
    if (($s[0] === '{' && substr($s, -1) === '}') || ($s[0] === '[' && substr($s, -1) === ']')) return true;
    if (str_contains($s, '\\"') && (str_contains($s, '{') || str_contains($s, '['))) return true;
    return false;
}

function extractFullConfigsFromDecoded($node, array &$out): void {
    if (is_array($node)) {
        foreach ($node as $v) extractFullConfigsFromDecoded($v, $out);
        return;
    }
    if (!is_string($node)) return;

    $s = trim($node);
    if (!looksLikeJsonString($s)) return;

    $d = json_decode($s, true);
    if ($d !== null && json_last_error() === JSON_ERROR_NONE) {
        if (isV2rayNgFullConfig($d)) {
            $out[] = json_encode($d, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            return;
        }
        if (is_array($d) && is_list_array($d)) {
            foreach ($d as $one) {
                if (is_array($one) && isV2rayNgFullConfig($one)) {
                    $out[] = json_encode($one, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                }
            }
            return;
        }
    }

    if (str_contains($s, '\\"')) {
        $s2 = stripcslashes($s);
        $d2 = json_decode($s2, true);
        if ($d2 !== null && json_last_error() === JSON_ERROR_NONE) {
            if (isV2rayNgFullConfig($d2)) {
                $out[] = json_encode($d2, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                return;
            }
            if (is_array($d2) && is_list_array($d2)) {
                foreach ($d2 as $one) {
                    if (is_array($one) && isV2rayNgFullConfig($one)) {
                        $out[] = json_encode($one, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                    }
                }
                return;
            }
        }
    }
}

function extract_balanced_json_blocks(string $text): array {
    $out = [];
    $len = strlen($text);

    $stack = [];
    $start = -1;

    $inStr = false;
    $strCh = '';
    $esc = false;

    for ($i = 0; $i < $len; $i++) {
        $ch = $text[$i];

        if ($inStr) {
            if ($esc) { $esc = false; continue; }
            if ($ch === '\\') { $esc = true; continue; }
            if ($ch === $strCh) { $inStr = false; $strCh = ''; }
            continue;
        }

        if ($ch === '"' || $ch === "'") { $inStr = true; $strCh = $ch; continue; }

        if ($ch === '{' || $ch === '[') {
            if (empty($stack)) $start = $i;
            $stack[] = $ch;
            continue;
        }

        if ($ch === '}' || $ch === ']') {
            if (empty($stack)) continue;

            $open = end($stack);
            $ok = ($open === '{' && $ch === '}') || ($open === '[' && $ch === ']');
            if (!$ok) { $stack = []; $start = -1; continue; }

            array_pop($stack);

            if (empty($stack) && $start !== -1) {
                $blk = trim(substr($text, $start, $i - $start + 1));
                if ($blk !== '') $out[] = $blk;
                $start = -1;
            }
        }
    }

    return array_values(array_unique($out));
}

function extractJsonConfigsSmart(string $text): array {
    $results = [];

    $decodedTop = json_decode($text, true);
    if ($decodedTop !== null && json_last_error() === JSON_ERROR_NONE) {
        if (isV2rayNgFullConfig($decodedTop)) {
            $results[] = json_encode($decodedTop, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        } elseif (is_array($decodedTop) && is_list_array($decodedTop)) {
            foreach ($decodedTop as $one) {
                if (is_array($one) && isV2rayNgFullConfig($one)) {
                    $results[] = json_encode($one, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                }
            }
        }
        extractFullConfigsFromDecoded($decodedTop, $results);
    }

    foreach (extract_balanced_json_blocks($text) as $blk) {
        $d = json_decode($blk, true);
        if ($d !== null && json_last_error() === JSON_ERROR_NONE) {
            if (isV2rayNgFullConfig($d)) {
                $results[] = json_encode($d, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                continue;
            }
            if (is_array($d) && is_list_array($d)) {
                foreach ($d as $one) {
                    if (is_array($one) && isV2rayNgFullConfig($one)) {
                        $results[] = json_encode($one, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                    }
                }
            }
        }
    }

    return array_values(array_unique($results));
}

// ================= Collect + Dedupe =================
$configs = [];

// Link configs
foreach (extractLinkTokensPrecise($response) as $u) {
    $u = sanitize_link($u);
    if (!validate_by_scheme($u)) continue;
    $configs[] = renameLinkConfig($u);
}

// JSON configs
foreach (extractJsonConfigsSmart($response) as $rawJson) {
    $j = renameJsonConfig($rawJson);
    $j = force_log_none_json($j);
    $j = normalize_v2ray_json_schema($j);
    $configs[] = $j;
}

if (empty($configs)) {
    header('Content-Type: text/plain; charset=UTF-8');
    die("هیچ کانفیگ معتبر پیدا نشد");
}

// Dedupe by SHA256
$unique = [];
foreach ($configs as $c) {
    $unique[hash('sha256', $c)] = $c;
}
$configs = array_values($unique);

// ================= OUTPUT: ONLY ONE =================
$selected = $configs[random_int(0, count($configs) - 1)];

header('Content-Type: text/plain; charset=UTF-8');

if (isset($_GET['array']) && $_GET['array'] == '1') {
    $trim = ltrim($selected);
    if ($trim !== '' && $trim[0] === '{') {
        echo '[' . $selected . ']';
    } else {
        echo $selected;
    }
} else {
    echo $selected;
}