<?php
declare(strict_types=1);

@date_default_timezone_set("UTC");
if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

require_once __DIR__ . "/db/config.php";

function schema_has_column(PDO $pdo, string $table, string $column): bool
{
    $statement = $pdo->prepare(
        "SELECT COUNT(*)
         FROM INFORMATION_SCHEMA.COLUMNS
         WHERE TABLE_SCHEMA = DATABASE()
           AND TABLE_NAME = ?
           AND COLUMN_NAME = ?"
    );
    $statement->execute([$table, $column]);

    return (int) $statement->fetchColumn() > 0;
}

function ensure_schema(): void
{
    static $ensured = false;
    if ($ensured) {
        return;
    }

    $pdo = db();
    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS users (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            email VARCHAR(190) NOT NULL UNIQUE,
            password_hash VARCHAR(255) NOT NULL,
            user_code CHAR(6) NOT NULL UNIQUE,
            vip_level TINYINT UNSIGNED NOT NULL DEFAULT 0,
            email_verified_at DATETIME NULL,
            last_login_at DATETIME NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
    );

    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS wallets (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NOT NULL UNIQUE,
            available_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            frozen_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            CONSTRAINT fk_wallet_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
    );

    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS task_orders (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NOT NULL,
            task_slug VARCHAR(64) NOT NULL,
            task_title VARCHAR(120) NOT NULL,
            task_type VARCHAR(80) NOT NULL,
            platform_name VARCHAR(80) NOT NULL,
            reward_usdt DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            vip_required TINYINT UNSIGNED NOT NULL DEFAULT 0,
            countdown_seconds INT UNSIGNED NOT NULL DEFAULT 60,
            status VARCHAR(32) NOT NULL DEFAULT 'claimed',
            proof_note TEXT NULL,
            claimed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            submitted_at DATETIME NULL,
            reviewed_at DATETIME NULL,
            review_note VARCHAR(255) NULL,
            CONSTRAINT fk_task_order_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
            INDEX idx_task_orders_user_status (user_id, status),
            INDEX idx_task_orders_status_review (status, reviewed_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
    );

    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS wallet_logs (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NOT NULL,
            entry_type VARCHAR(32) NOT NULL,
            amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            frozen_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            available_after DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            frozen_after DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            note VARCHAR(255) NOT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            CONSTRAINT fk_wallet_log_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
            INDEX idx_wallet_logs_user_created (user_id, created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
    );

    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS vip_orders (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NOT NULL,
            from_level TINYINT UNSIGNED NOT NULL DEFAULT 0,
            to_level TINYINT UNSIGNED NOT NULL,
            price_usdt DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            status VARCHAR(32) NOT NULL DEFAULT 'completed',
            available_after DECIMAL(12,2) NOT NULL DEFAULT 0.00,
            note VARCHAR(255) NOT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            completed_at DATETIME NULL,
            CONSTRAINT fk_vip_order_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
            INDEX idx_vip_orders_user_created (user_id, created_at),
            INDEX idx_vip_orders_status_created (status, created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
    );

    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS email_verification_codes (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            email VARCHAR(190) NOT NULL,
            purpose VARCHAR(32) NOT NULL DEFAULT 'signup',
            password_hash VARCHAR(255) NOT NULL,
            code_hash VARCHAR(255) NOT NULL,
            attempt_count TINYINT UNSIGNED NOT NULL DEFAULT 0,
            max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 5,
            expires_at DATETIME NOT NULL,
            verified_at DATETIME NULL,
            consumed_at DATETIME NULL,
            ip_address VARCHAR(64) NULL,
            meta_note VARCHAR(255) NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_email_codes_email_purpose (email, purpose, created_at),
            INDEX idx_email_codes_status (purpose, consumed_at, expires_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
    );

    if (!schema_has_column($pdo, 'users', 'email_verified_at')) {
        $pdo->exec("ALTER TABLE users ADD COLUMN email_verified_at DATETIME NULL AFTER vip_level");
    }
    if (!schema_has_column($pdo, 'users', 'last_login_at')) {
        $pdo->exec("ALTER TABLE users ADD COLUMN last_login_at DATETIME NULL AFTER email_verified_at");
    }

    $ensured = true;
}

ensure_schema();

function h($value): string
{
    return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
}

function project_name(): string
{
    $name = trim((string) ($_SERVER["PROJECT_NAME"] ?? ""));
    return $name !== "" ? $name : "Atlas Rebate";
}

function project_description(): string
{
    $description = trim((string) ($_SERVER["PROJECT_DESCRIPTION"] ?? ""));
    return $description !== ""
        ? $description
        : "Mobile-first rebate operations slice with email onboarding, task execution, manual review, and wallet ledger.";
}

function asset_version(string $relativePath): string
{
    $absolutePath = __DIR__ . "/" . ltrim($relativePath, "/");
    return file_exists($absolutePath) ? (string) filemtime($absolutePath) : (string) time();
}

function app_url(string $path, array $query = []): string
{
    return $query ? $path . "?" . http_build_query($query) : $path;
}

function redirect(string $url): void
{
    header("Location: " . $url);
    exit;
}

function format_usdt($amount): string
{
    return number_format((float) $amount, 2) . " USDT";
}

function format_delta($amount): string
{
    $amount = (float) $amount;
    $prefix = $amount > 0 ? "+" : "";
    return $prefix . number_format($amount, 2);
}

function format_datetime(?string $value): string
{
    if (!$value) {
        return "—";
    }

    $timestamp = strtotime($value);
    if ($timestamp === false) {
        return h($value);
    }

    return date("Y-m-d H:i", $timestamp);
}

function client_ip(): string
{
    return (string) ($_SERVER["REMOTE_ADDR"] ?? "0.0.0.0");
}

function text_length(string $value): int
{
    return function_exists("mb_strlen") ? mb_strlen($value) : strlen($value);
}

function csrf_token(): string
{
    if (empty($_SESSION["csrf_token"])) {
        $_SESSION["csrf_token"] = bin2hex(random_bytes(16));
    }

    return (string) $_SESSION["csrf_token"];
}

function verify_csrf_or_fail(): void
{
    $token = (string) ($_POST["csrf_token"] ?? "");
    $sessionToken = (string) ($_SESSION["csrf_token"] ?? "");

    if ($token === "" || $sessionToken === "" || !hash_equals($sessionToken, $token)) {
        throw new RuntimeException("请求已失效，请刷新页面后重试。");
    }
}

function flash(string $tone, string $message): void
{
    $_SESSION["flash_messages"][] = [
        "tone" => $tone,
        "message" => $message,
    ];
}

function pull_flashes(): array
{
    $messages = $_SESSION["flash_messages"] ?? [];
    unset($_SESSION["flash_messages"]);
    return is_array($messages) ? $messages : [];
}

function vip_catalog(): array
{
    return [
        0 => [
            "level" => 0,
            "name" => "VIP0",
            "price" => 0,
            "reward" => 24,
            "daily_tasks" => 2,
            "benefit" => "新手演示任务、自动钱包、资金流水可视化",
        ],
        1 => [
            "level" => 1,
            "name" => "VIP1",
            "price" => 1000,
            "reward" => 200,
            "daily_tasks" => 2,
            "benefit" => "1000 USDT 开通，单任务佣金 200 USDT",
        ],
        2 => [
            "level" => 2,
            "name" => "VIP2",
            "price" => 3000,
            "reward" => 600,
            "daily_tasks" => 2,
            "benefit" => "3000 USDT 开通，单任务佣金 600 USDT",
        ],
        3 => [
            "level" => 3,
            "name" => "VIP3",
            "price" => 5000,
            "reward" => 1000,
            "daily_tasks" => 2,
            "benefit" => "5000 USDT 开通，单任务佣金 1000 USDT",
        ],
        4 => [
            "level" => 4,
            "name" => "VIP4",
            "price" => 10000,
            "reward" => 2000,
            "daily_tasks" => 2,
            "benefit" => "10000 USDT 开通，单任务佣金 2000 USDT",
        ],
    ];
}

function vip_info(int $vipLevel): array
{
    $catalog = vip_catalog();
    return $catalog[$vipLevel] ?? $catalog[0];
}

function task_catalog(): array
{
    return [
        "watch-video" => [
            "slug" => "watch-video",
            "title" => "观看 YouTube 视频",
            "platform" => "YouTube",
            "type" => "视频",
            "reward" => 18,
            "vip_required" => 0,
            "countdown" => 60,
            "summary" => "打开视频并保持活跃，倒计时结束后提交完成备注。",
            "steps" => [
                "点击开始任务后进入倒计时。",
                "保持页面停留并完成观看动作。",
                "倒计时结束后提交一句完成备注。",
            ],
        ],
        "social-like" => [
            "slug" => "social-like",
            "title" => "点赞 TikTok 视频",
            "platform" => "TikTok",
            "type" => "点赞",
            "reward" => 24,
            "vip_required" => 0,
            "countdown" => 60,
            "summary" => "适合新用户演示完整闭环：领取、计时、提交、审核入账。",
            "steps" => [
                "领取任务并进入倒计时。",
                "模拟完成点赞或互动行为。",
                "提交完成说明，等待后台审核。",
            ],
        ],
        "follow-brand" => [
            "slug" => "follow-brand",
            "title" => "关注 Instagram 博主",
            "platform" => "Instagram",
            "type" => "社媒",
            "reward" => 200,
            "vip_required" => 1,
            "countdown" => 90,
            "summary" => "VIP1 开始解锁，高佣金任务与更强审核优先级。",
            "steps" => [
                "确认当前账号已开通对应 VIP。",
                "完成关注动作并保持页面停留。",
                "提交账户备注供后台核对。",
            ],
        ],
        "comment-flow" => [
            "slug" => "comment-flow",
            "title" => "发布社媒评论",
            "platform" => "Community Feed",
            "type" => "社媒",
            "reward" => 600,
            "vip_required" => 2,
            "countdown" => 110,
            "summary" => "VIP2 解锁的中高客单价任务，用于验证冻结佣金逻辑。",
            "steps" => [
                "领取后完成评论发布。",
                "保留评论内容关键词。",
                "在系统里提交评论摘要。",
            ],
        ],
        "site-browse" => [
            "slug" => "site-browse",
            "title" => "浏览网站 60 秒",
            "platform" => "Website",
            "type" => "网站",
            "reward" => 1000,
            "vip_required" => 3,
            "countdown" => 140,
            "summary" => "VIP3 任务，适合做高佣金站点浏览与停留验证。",
            "steps" => [
                "进入指定站点浏览多个页面。",
                "保持页面活跃直到倒计时结束。",
                "填写浏览到的产品或栏目名称。",
            ],
        ],
        "app-download" => [
            "slug" => "app-download",
            "title" => "下载应用体验",
            "platform" => "App Center",
            "type" => "应用",
            "reward" => 2000,
            "vip_required" => 4,
            "countdown" => 180,
            "summary" => "VIP4 顶级任务，高收益、高风控、人工复核。",
            "steps" => [
                "下载并打开指定应用。",
                "完成首次启动与页面停留。",
                "提交设备备注或体验摘要。",
            ],
        ],
    ];
}

function task_by_slug(string $slug): ?array
{
    $catalog = task_catalog();
    return $catalog[$slug] ?? null;
}

function generate_unique_user_code(): string
{
    $pdo = db();
    $statement = $pdo->prepare("SELECT id FROM users WHERE user_code = ? LIMIT 1");

    for ($i = 0; $i < 50; $i++) {
        $candidate = str_pad((string) random_int(0, 999999), 6, "0", STR_PAD_LEFT);
        $statement->execute([$candidate]);
        if (!$statement->fetchColumn()) {
            return $candidate;
        }
    }

    throw new RuntimeException("未能生成唯一 6 位数字 ID，请重试。");
}

function fetch_user(int $userId): ?array
{
    $statement = db()->prepare(
        "SELECT u.*, w.available_balance, w.frozen_balance, w.updated_at AS wallet_updated_at
         FROM users u
         INNER JOIN wallets w ON w.user_id = u.id
         WHERE u.id = ?
         LIMIT 1"
    );
    $statement->execute([$userId]);
    $user = $statement->fetch();

    return $user ?: null;
}

function current_user(): ?array
{
    static $resolved = false;
    static $user = null;

    if ($resolved) {
        return $user;
    }

    $resolved = true;

    if (empty($_SESSION["user_id"])) {
        return null;
    }

    $user = fetch_user((int) $_SESSION["user_id"]);
    if (!$user) {
        unset($_SESSION["user_id"]);
    }

    return $user;
}

function safe_redirect_target(?string $target): string
{
    $target = trim((string) $target);
    if ($target === '') {
        return 'index.php';
    }

    $target = str_replace(["
", "
"], '', $target);

    if (preg_match('/^[a-zA-Z][a-zA-Z0-9+.-]*:/', $target) || str_starts_with($target, '//')) {
        return 'index.php';
    }

    if (str_contains($target, '..')) {
        return 'index.php';
    }

    if (str_starts_with($target, '/')) {
        $target = ltrim($target, '/');
    }

    return $target !== '' ? $target : 'index.php';
}

function current_request_target(): string
{
    return safe_redirect_target((string) ($_SERVER['REQUEST_URI'] ?? 'index.php'));
}

function auth_page_url(string $page, ?string $redirectTarget = null, array $query = []): string
{
    if ($redirectTarget !== null && trim($redirectTarget) !== '') {
        $query['redirect'] = safe_redirect_target($redirectTarget);
    }

    return app_url($page, $query);
}

function start_page_url(?string $redirectTarget = null, array $query = []): string
{
    return auth_page_url('start.php', $redirectTarget, $query);
}

function register_page_url(?string $redirectTarget = null, array $query = []): string
{
    return auth_page_url('register.php', $redirectTarget, $query);
}

function login_page_url(?string $redirectTarget = null, array $query = []): string
{
    return auth_page_url('login.php', $redirectTarget, $query);
}

function verify_page_url(string $email, ?string $redirectTarget = null, array $query = []): string
{
    $query['email'] = normalize_email($email);
    return auth_page_url('verify.php', $redirectTarget, $query);
}

function require_user(): array
{
    $user = current_user();
    if (!$user) {
        flash('warning', '请先使用邮箱登录后再继续。');
        redirect(login_page_url(current_request_target()));
    }

    return $user;
}

function set_logged_in_user(int $userId): void
{
    $_SESSION['user_id'] = $userId;
}

function logout_current_user(): void
{
    unset($_SESSION['user_id']);
}

function normalize_email(string $email): string
{
    return strtolower(trim($email));
}

function validate_email_or_fail(string $email): string
{
    $email = normalize_email($email);
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new RuntimeException('请输入有效邮箱地址。');
    }

    return $email;
}

function ensure_password_strength(string $password): void
{
    if (text_length($password) < 6) {
        throw new RuntimeException('密码至少需要 6 位字符。');
    }
}

function verification_code_ttl_minutes(): int
{
    return 10;
}

function generate_email_code(): string
{
    return str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
}

function mask_email(string $email): string
{
    $email = normalize_email($email);
    if (!str_contains($email, '@')) {
        return $email;
    }

    [$local, $domain] = explode('@', $email, 2);
    $visible = substr($local, 0, min(2, strlen($local)));
    $masked = $visible . str_repeat('*', max(2, strlen($local) - strlen($visible)));

    return $masked . '@' . $domain;
}

function send_signup_verification_email(string $email, string $code): array
{
    require_once __DIR__ . '/mail/MailService.php';

    $siteName = h(project_name());
    $safeCode = h($code);
    $ttlMinutes = verification_code_ttl_minutes();
    $subject = project_name() . ' 邮箱验证码';
    $html = '<div style="font-family:Inter,Arial,sans-serif;color:#111827;line-height:1.7">'
        . '<p style="margin:0 0 12px;font-size:14px;color:#6b7280">' . $siteName . '</p>'
        . '<h1 style="margin:0 0 12px;font-size:24px;color:#111827">你的邮箱验证码</h1>'
        . '<p style="margin:0 0 16px">请在 ' . $ttlMinutes . ' 分钟内输入下面这组 6 位数字：</p>'
        . '<div style="margin:0 0 18px;padding:18px 20px;border-radius:16px;background:#111827;color:#ffffff;font-size:30px;font-weight:800;letter-spacing:8px;text-align:center">' . $safeCode . '</div>'
        . '<p style="margin:0;color:#6b7280">如果这不是你本人操作，请直接忽略这封邮件。</p>'
        . '</div>';
    $text = project_name() . ' 邮箱验证码：' . $code . '。请在 ' . $ttlMinutes . ' 分钟内完成验证。若非本人操作请忽略。';

    return MailService::sendMail($email, $subject, $html, $text);
}

function mail_delivery_error_message(array $mailResult): string
{
    $detail = strtolower((string) ($mailResult['error'] ?? ''));
    if ($detail === '') {
        return '验证码发送失败，请稍后重试。';
    }
    if (str_contains($detail, 'recipient')) {
        return '验证码发送失败：当前收件地址无效，请重新输入邮箱。';
    }

    return '验证码发送失败：当前 SMTP 未配置或连接失败，请先在平台环境变量里配置 MAIL_/SMTP_ 参数。';
}

function create_user_account_record(PDO $pdo, string $email, string $passwordHash, ?string $emailVerifiedAt = null): int
{
    $email = validate_email_or_fail($email);

    $check = $pdo->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
    $check->execute([$email]);
    if ($check->fetchColumn()) {
        throw new RuntimeException('该邮箱已注册，请直接登录。');
    }

    $userCode = generate_unique_user_code();
    $insertUser = $pdo->prepare(
        'INSERT INTO users (email, password_hash, user_code, vip_level, email_verified_at, last_login_at) VALUES (?, ?, ?, 0, ?, NULL)'
    );
    $insertUser->execute([$email, $passwordHash, $userCode, $emailVerifiedAt]);
    $userId = (int) $pdo->lastInsertId();

    $insertWallet = $pdo->prepare(
        'INSERT INTO wallets (user_id, available_balance, frozen_balance) VALUES (?, 0.00, 0.00)'
    );
    $insertWallet->execute([$userId]);

    insert_wallet_log(
        $pdo,
        $userId,
        'wallet_opened',
        0,
        0,
        '邮箱注册成功，系统已自动创建钱包与 VIP0。',
        0,
        0
    );

    return $userId;
}

function register_user(string $email, string $password): array
{
    $email = validate_email_or_fail($email);
    ensure_password_strength($password);

    $pdo = db();
    $pdo->beginTransaction();
    try {
        $userId = create_user_account_record(
            $pdo,
            $email,
            password_hash($password, PASSWORD_DEFAULT),
            date('Y-m-d H:i:s')
        );

        $pdo->commit();
        set_logged_in_user($userId);

        return fetch_user($userId) ?? [];
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        throw $exception;
    }
}

function latest_signup_verification(string $email, bool $pendingOnly = false): ?array
{
    $email = validate_email_or_fail($email);
    $sql = 'SELECT * FROM email_verification_codes WHERE email = ? AND purpose = ?';
    if ($pendingOnly) {
        $sql .= ' AND consumed_at IS NULL';
    }
    $sql .= ' ORDER BY id DESC LIMIT 1';

    $statement = db()->prepare($sql);
    $statement->execute([$email, 'signup']);
    $record = $statement->fetch();

    return $record ?: null;
}

function issue_signup_verification_from_hash(string $email, string $passwordHash): array
{
    $email = validate_email_or_fail($email);

    $code = generate_email_code();
    $expiresAt = date('Y-m-d H:i:s', time() + (verification_code_ttl_minutes() * 60));
    $pdo = db();
    $pdo->beginTransaction();

    try {
        $resetPending = $pdo->prepare(
            "UPDATE email_verification_codes
             SET consumed_at = NOW(), meta_note = 'superseded by new signup code'
             WHERE email = ? AND purpose = 'signup' AND consumed_at IS NULL AND verified_at IS NULL"
        );
        $resetPending->execute([$email]);

        $insert = $pdo->prepare(
            'INSERT INTO email_verification_codes (
                email,
                purpose,
                password_hash,
                code_hash,
                expires_at,
                ip_address,
                meta_note
            ) VALUES (?, ?, ?, ?, ?, ?, ?)' 
        );
        $insert->execute([
            $email,
            'signup',
            $passwordHash,
            password_hash($code, PASSWORD_DEFAULT),
            $expiresAt,
            client_ip(),
            'signup verification pending',
        ]);
        $verificationId = (int) $pdo->lastInsertId();
        $pdo->commit();
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        throw $exception;
    }

    $mailResult = send_signup_verification_email($email, $code);
    if (empty($mailResult['success'])) {
        $cleanup = db()->prepare(
            "UPDATE email_verification_codes
             SET consumed_at = NOW(), meta_note = 'mail delivery failed'
             WHERE id = ? AND consumed_at IS NULL"
        );
        $cleanup->execute([$verificationId]);
        error_log('Signup verification mail failed for ' . $email . ': ' . (string) ($mailResult['error'] ?? 'unknown'));
        throw new RuntimeException(mail_delivery_error_message($mailResult));
    }

    $_SESSION['last_signup_email'] = $email;

    return [
        'id' => $verificationId,
        'email' => $email,
        'expires_at' => $expiresAt,
    ];
}

function begin_signup_verification(string $email, string $password): array
{
    $email = validate_email_or_fail($email);
    ensure_password_strength($password);

    $check = db()->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
    $check->execute([$email]);
    if ($check->fetchColumn()) {
        throw new RuntimeException('该邮箱已注册，请直接登录。');
    }

    return issue_signup_verification_from_hash($email, password_hash($password, PASSWORD_DEFAULT));
}

function resend_signup_verification(string $email): array
{
    $email = validate_email_or_fail($email);

    $check = db()->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
    $check->execute([$email]);
    if ($check->fetchColumn()) {
        throw new RuntimeException('该邮箱已注册，请直接登录。');
    }

    $latest = latest_signup_verification($email, false);
    if (!$latest || empty($latest['password_hash'])) {
        throw new RuntimeException('未找到待验证的注册记录，请先返回注册页填写邮箱和密码。');
    }

    return issue_signup_verification_from_hash($email, (string) $latest['password_hash']);
}

function complete_signup_verification(string $email, string $code): array
{
    $email = validate_email_or_fail($email);
    $code = trim($code);
    if (!preg_match('/^\d{6}$/', $code)) {
        throw new RuntimeException('请输入 6 位数字验证码。');
    }

    $pdo = db();
    $pdo->beginTransaction();
    try {
        $statement = $pdo->prepare(
            "SELECT *
             FROM email_verification_codes
             WHERE email = ?
               AND purpose = 'signup'
               AND consumed_at IS NULL
             ORDER BY id DESC
             LIMIT 1
             FOR UPDATE"
        );
        $statement->execute([$email]);
        $record = $statement->fetch();

        if (!$record) {
            throw new RuntimeException('验证码记录不存在，请返回注册页重新发送。');
        }

        if ((string) ($record['verified_at'] ?? '') !== '') {
            throw new RuntimeException('该验证码已经使用过，请直接登录。');
        }

        if (strtotime((string) $record['expires_at']) < time()) {
            $expired = $pdo->prepare(
                "UPDATE email_verification_codes
                 SET consumed_at = NOW(), meta_note = 'code expired'
                 WHERE id = ?"
            );
            $expired->execute([(int) $record['id']]);
            $pdo->commit();
            throw new RuntimeException('验证码已过期，请重新发送。');
        }

        $maxAttempts = (int) ($record['max_attempts'] ?? 5);
        $attemptCount = (int) ($record['attempt_count'] ?? 0);
        if ($attemptCount >= $maxAttempts) {
            $locked = $pdo->prepare(
                "UPDATE email_verification_codes
                 SET consumed_at = NOW(), meta_note = 'max attempts reached'
                 WHERE id = ? AND consumed_at IS NULL"
            );
            $locked->execute([(int) $record['id']]);
            $pdo->commit();
            throw new RuntimeException('验证码错误次数过多，请重新发送验证码。');
        }

        if (!password_verify($code, (string) $record['code_hash'])) {
            $attemptCount++;
            if ($attemptCount >= $maxAttempts) {
                $failed = $pdo->prepare(
                    "UPDATE email_verification_codes
                     SET attempt_count = ?, consumed_at = NOW(), meta_note = 'max attempts reached'
                     WHERE id = ?"
                );
                $failed->execute([$attemptCount, (int) $record['id']]);
                $pdo->commit();
                throw new RuntimeException('验证码错误次数过多，请重新发送验证码。');
            }

            $failed = $pdo->prepare(
                "UPDATE email_verification_codes
                 SET attempt_count = ?, meta_note = 'verification code mismatch'
                 WHERE id = ?"
            );
            $failed->execute([$attemptCount, (int) $record['id']]);
            $pdo->commit();
            throw new RuntimeException('验证码错误，请重新输入。');
        }

        $userCheck = $pdo->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
        $userCheck->execute([$email]);
        if ($userCheck->fetchColumn()) {
            $duplicate = $pdo->prepare(
                "UPDATE email_verification_codes
                 SET consumed_at = NOW(), meta_note = 'email already registered'
                 WHERE id = ?"
            );
            $duplicate->execute([(int) $record['id']]);
            $pdo->commit();
            throw new RuntimeException('该邮箱已注册，请直接登录。');
        }

        $userId = create_user_account_record(
            $pdo,
            $email,
            (string) $record['password_hash'],
            date('Y-m-d H:i:s')
        );

        $verified = $pdo->prepare(
            "UPDATE email_verification_codes
             SET verified_at = NOW(), consumed_at = NOW(), meta_note = 'signup completed'
             WHERE id = ?"
        );
        $verified->execute([(int) $record['id']]);

        $pdo->commit();
        unset($_SESSION['last_signup_email']);

        return fetch_user($userId) ?? [];
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        throw $exception;
    }
}

function authenticate_user(string $email, string $password): array
{
    $email = validate_email_or_fail($email);

    $statement = db()->prepare('SELECT * FROM users WHERE email = ? LIMIT 1');
    $statement->execute([$email]);
    $record = $statement->fetch();

    if (!$record || !password_verify($password, (string) $record['password_hash'])) {
        throw new RuntimeException('邮箱或密码错误，请重试。');
    }

    $update = db()->prepare(
        'UPDATE users
         SET last_login_at = NOW(), email_verified_at = COALESCE(email_verified_at, NOW())
         WHERE id = ?'
    );
    $update->execute([(int) $record['id']]);

    set_logged_in_user((int) $record['id']);
    return fetch_user((int) $record['id']) ?? [];
}

function wallet_snapshot(int $userId): array
{
    $statement = db()->prepare(
        "SELECT available_balance, frozen_balance FROM wallets WHERE user_id = ? LIMIT 1"
    );
    $statement->execute([$userId]);
    $wallet = $statement->fetch();

    return $wallet ?: ["available_balance" => 0, "frozen_balance" => 0];
}

function insert_wallet_log(
    PDO $pdo,
    int $userId,
    string $entryType,
    float $amount,
    float $frozenAmount,
    string $note,
    float $availableAfter,
    float $frozenAfter
): void {
    $statement = $pdo->prepare(
        "INSERT INTO wallet_logs (
            user_id,
            entry_type,
            amount,
            frozen_amount,
            available_after,
            frozen_after,
            note
        ) VALUES (?, ?, ?, ?, ?, ?, ?)"
    );
    $statement->execute([
        $userId,
        $entryType,
        number_format($amount, 2, ".", ""),
        number_format($frozenAmount, 2, ".", ""),
        number_format($availableAfter, 2, ".", ""),
        number_format($frozenAfter, 2, ".", ""),
        $note,
    ]);
}

function get_dashboard_stats(int $userId): array
{
    $wallet = wallet_snapshot($userId);
    $pdo = db();

    $todayStatement = $pdo->prepare(
        "SELECT COALESCE(SUM(amount), 0) AS today_earnings
         FROM wallet_logs
         WHERE user_id = ?
           AND entry_type = 'task_credit'
           AND DATE(created_at) = CURDATE()"
    );
    $todayStatement->execute([$userId]);
    $todayEarnings = (float) ($todayStatement->fetchColumn() ?: 0);

    $monthStatement = $pdo->prepare(
        "SELECT COALESCE(SUM(amount), 0) AS month_earnings
         FROM wallet_logs
         WHERE user_id = ?
           AND entry_type = 'task_credit'
           AND DATE_FORMAT(created_at, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m')"
    );
    $monthStatement->execute([$userId]);
    $monthEarnings = (float) ($monthStatement->fetchColumn() ?: 0);

    $todayTaskStatement = $pdo->prepare(
        "SELECT COUNT(*)
         FROM task_orders
         WHERE user_id = ?
           AND DATE(claimed_at) = CURDATE()"
    );
    $todayTaskStatement->execute([$userId]);
    $todayTasks = (int) ($todayTaskStatement->fetchColumn() ?: 0);

    $activeStatement = $pdo->prepare(
        "SELECT COUNT(*) FROM task_orders WHERE user_id = ? AND status IN ('claimed', 'pending_review')"
    );
    $activeStatement->execute([$userId]);
    $activeOrders = (int) ($activeStatement->fetchColumn() ?: 0);

    $completedStatement = $pdo->prepare(
        "SELECT COUNT(*) FROM task_orders WHERE user_id = ? AND status = 'approved'"
    );
    $completedStatement->execute([$userId]);
    $completedOrders = (int) ($completedStatement->fetchColumn() ?: 0);

    $totalBalance = (float) $wallet["available_balance"] + (float) $wallet["frozen_balance"];

    return [
        "available_balance" => (float) $wallet["available_balance"],
        "frozen_balance" => (float) $wallet["frozen_balance"],
        "total_balance" => $totalBalance,
        "today_earnings" => $todayEarnings,
        "month_earnings" => $monthEarnings,
        "today_tasks" => $todayTasks,
        "active_orders" => $activeOrders,
        "completed_orders" => $completedOrders,
    ];
}

function get_user_orders(int $userId, int $limit = 12): array
{
    $statement = db()->prepare(
        "SELECT * FROM task_orders WHERE user_id = ? ORDER BY id DESC LIMIT ?"
    );
    $statement->bindValue(1, $userId, PDO::PARAM_INT);
    $statement->bindValue(2, $limit, PDO::PARAM_INT);
    $statement->execute();
    return $statement->fetchAll() ?: [];
}

function get_user_orders_for_task(int $userId, string $taskSlug, int $limit = 6): array
{
    $statement = db()->prepare(
        "SELECT * FROM task_orders WHERE user_id = ? AND task_slug = ? ORDER BY id DESC LIMIT ?"
    );
    $statement->bindValue(1, $userId, PDO::PARAM_INT);
    $statement->bindValue(2, $taskSlug, PDO::PARAM_STR);
    $statement->bindValue(3, $limit, PDO::PARAM_INT);
    $statement->execute();
    return $statement->fetchAll() ?: [];
}

function get_wallet_logs(int $userId, int $limit = 20): array
{
    $statement = db()->prepare(
        "SELECT * FROM wallet_logs WHERE user_id = ? ORDER BY id DESC LIMIT ?"
    );
    $statement->bindValue(1, $userId, PDO::PARAM_INT);
    $statement->bindValue(2, $limit, PDO::PARAM_INT);
    $statement->execute();
    return $statement->fetchAll() ?: [];
}

function get_user_vip_orders(int $userId, int $limit = 12): array
{
    $statement = db()->prepare(
        "SELECT *
         FROM vip_orders
         WHERE user_id = ?
         ORDER BY COALESCE(completed_at, created_at) DESC, id DESC
         LIMIT ?"
    );
    $statement->bindValue(1, $userId, PDO::PARAM_INT);
    $statement->bindValue(2, $limit, PDO::PARAM_INT);
    $statement->execute();
    return $statement->fetchAll() ?: [];
}

function get_vip_order_by_id(int $vipOrderId, ?int $userId = null): ?array
{
    $sql = "SELECT * FROM vip_orders WHERE id = ?";
    $params = [$vipOrderId];

    if ($userId !== null) {
        $sql .= " AND user_id = ?";
        $params[] = $userId;
    }

    $sql .= " LIMIT 1";
    $statement = db()->prepare($sql);
    $statement->execute($params);
    $order = $statement->fetch();
    return $order ?: null;
}

function purchase_vip_upgrade(int $userId, int $targetLevel): array
{
    $catalog = vip_catalog();
    if ($targetLevel <= 0 || !isset($catalog[$targetLevel])) {
        throw new RuntimeException("请选择有效的 VIP 等级。");
    }

    $pdo = db();
    $pdo->beginTransaction();

    try {
        $userStatement = $pdo->prepare(
            "SELECT * FROM users WHERE id = ? LIMIT 1 FOR UPDATE"
        );
        $userStatement->execute([$userId]);
        $user = $userStatement->fetch();

        if (!$user) {
            throw new RuntimeException("用户不存在，请重新登录后再试。");
        }

        $currentLevel = (int) $user["vip_level"];
        if ($targetLevel <= $currentLevel) {
            throw new RuntimeException("当前账号已开通 " . vip_info($currentLevel)["name"] . "，请选择更高等级。");
        }

        $walletStatement = $pdo->prepare(
            "SELECT available_balance, frozen_balance FROM wallets WHERE user_id = ? LIMIT 1 FOR UPDATE"
        );
        $walletStatement->execute([$userId]);
        $wallet = $walletStatement->fetch();

        if (!$wallet) {
            throw new RuntimeException("钱包不存在，请重新登录后再试。");
        }

        $targetVip = $catalog[$targetLevel];
        $price = (float) $targetVip["price"];
        $availableBefore = (float) $wallet["available_balance"];
        $frozenAfter = (float) $wallet["frozen_balance"];

        if ($availableBefore < $price) {
            $gap = $price - $availableBefore;
            throw new RuntimeException("当前可用余额不足，还差 " . number_format($gap, 2) . " USDT，请先充值后再开通。");
        }

        $availableAfter = $availableBefore - $price;
        $note = vip_info($currentLevel)["name"] . " 升级到 " . $targetVip["name"] . "，系统已从可用余额扣款。";

        $updateUser = $pdo->prepare("UPDATE users SET vip_level = ? WHERE id = ?");
        $updateUser->execute([$targetLevel, $userId]);

        $updateWallet = $pdo->prepare(
            "UPDATE wallets SET available_balance = ?, updated_at = NOW() WHERE user_id = ?"
        );
        $updateWallet->execute([
            number_format($availableAfter, 2, ".", ""),
            $userId,
        ]);

        $insertOrder = $pdo->prepare(
            "INSERT INTO vip_orders (
                user_id,
                from_level,
                to_level,
                price_usdt,
                status,
                available_after,
                note,
                completed_at
            ) VALUES (?, ?, ?, ?, 'completed', ?, ?, NOW())"
        );
        $insertOrder->execute([
            $userId,
            $currentLevel,
            $targetLevel,
            number_format($price, 2, ".", ""),
            number_format($availableAfter, 2, ".", ""),
            $note,
        ]);
        $vipOrderId = (int) $pdo->lastInsertId();

        insert_wallet_log(
            $pdo,
            $userId,
            "vip_upgrade",
            -$price,
            0,
            $note,
            $availableAfter,
            $frozenAfter
        );

        $pdo->commit();

        return get_vip_order_by_id($vipOrderId, $userId) ?? [
            "id" => $vipOrderId,
            "from_level" => $currentLevel,
            "to_level" => $targetLevel,
            "price_usdt" => $price,
            "available_after" => $availableAfter,
            "status" => "completed",
            "note" => $note,
        ];
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        throw $exception;
    }
}

function active_order_for_task(int $userId, string $taskSlug): ?array
{
    $statement = db()->prepare(
        "SELECT *
         FROM task_orders
         WHERE user_id = ? AND task_slug = ? AND status IN ('claimed', 'pending_review')
         ORDER BY id DESC
         LIMIT 1"
    );
    $statement->execute([$userId, $taskSlug]);
    $order = $statement->fetch();
    return $order ?: null;
}

function get_order_by_id(int $orderId, ?int $userId = null): ?array
{
    $sql = "SELECT o.*, u.email, u.user_code, u.vip_level
            FROM task_orders o
            INNER JOIN users u ON u.id = o.user_id
            WHERE o.id = ?";

    $params = [$orderId];
    if ($userId !== null) {
        $sql .= " AND o.user_id = ?";
        $params[] = $userId;
    }

    $sql .= " LIMIT 1";
    $statement = db()->prepare($sql);
    $statement->execute($params);
    $order = $statement->fetch();
    return $order ?: null;
}

function can_access_task(array $user, array $task): bool
{
    return (int) $user["vip_level"] >= (int) $task["vip_required"];
}

function create_task_order(int $userId, array $task): array
{
    $user = fetch_user($userId);
    if (!$user) {
        throw new RuntimeException("用户不存在，请重新登录。");
    }

    if (!can_access_task($user, $task)) {
        throw new RuntimeException("当前 VIP 等级不足，暂不能领取该任务。");
    }

    if (active_order_for_task($userId, (string) $task["slug"])) {
        throw new RuntimeException("你已经有一个进行中的同类任务，请先完成当前任务。");
    }

    $statement = db()->prepare(
        "INSERT INTO task_orders (
            user_id,
            task_slug,
            task_title,
            task_type,
            platform_name,
            reward_usdt,
            vip_required,
            countdown_seconds,
            status
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'claimed')"
    );
    $statement->execute([
        $userId,
        $task["slug"],
        $task["title"],
        $task["type"],
        $task["platform"],
        number_format((float) $task["reward"], 2, ".", ""),
        (int) $task["vip_required"],
        (int) $task["countdown"],
    ]);

    $orderId = (int) db()->lastInsertId();
    return get_order_by_id($orderId, $userId) ?? [];
}

function is_submit_unlocked(array $order): bool
{
    $claimedTimestamp = strtotime((string) $order["claimed_at"]);
    if ($claimedTimestamp === false) {
        return false;
    }

    return time() >= $claimedTimestamp + (int) $order["countdown_seconds"];
}

function submit_task_order(int $userId, int $orderId, string $proofNote): array
{
    $proofNote = trim($proofNote);
    if (text_length($proofNote) < 6) {
        throw new RuntimeException("请填写至少 6 个字符的完成备注，方便后台审核。");
    }

    $pdo = db();
    $pdo->beginTransaction();

    try {
        $statement = $pdo->prepare(
            "SELECT * FROM task_orders WHERE id = ? AND user_id = ? LIMIT 1 FOR UPDATE"
        );
        $statement->execute([$orderId, $userId]);
        $order = $statement->fetch();

        if (!$order) {
            throw new RuntimeException("任务订单不存在。");
        }
        if ((string) $order["status"] !== "claimed") {
            throw new RuntimeException("当前任务状态不可提交。");
        }
        if (!is_submit_unlocked($order)) {
            throw new RuntimeException("倒计时尚未结束，请等待后再提交。");
        }

        $walletStatement = $pdo->prepare(
            "SELECT available_balance, frozen_balance FROM wallets WHERE user_id = ? LIMIT 1 FOR UPDATE"
        );
        $walletStatement->execute([$userId]);
        $wallet = $walletStatement->fetch();
        if (!$wallet) {
            throw new RuntimeException("钱包不存在，请重新登录。");
        }

        $availableAfter = (float) $wallet["available_balance"];
        $frozenAfter = (float) $wallet["frozen_balance"] + (float) $order["reward_usdt"];

        $updateOrder = $pdo->prepare(
            "UPDATE task_orders
             SET status = 'pending_review', proof_note = ?, submitted_at = NOW()
             WHERE id = ?"
        );
        $updateOrder->execute([$proofNote, $orderId]);

        $updateWallet = $pdo->prepare(
            "UPDATE wallets SET frozen_balance = ?, updated_at = NOW() WHERE user_id = ?"
        );
        $updateWallet->execute([number_format($frozenAfter, 2, ".", ""), $userId]);

        insert_wallet_log(
            $pdo,
            $userId,
            "task_freeze",
            0,
            (float) $order["reward_usdt"],
            "任务已提交审核，佣金进入冻结余额。",
            $availableAfter,
            $frozenAfter
        );

        $pdo->commit();
        return get_order_by_id($orderId, $userId) ?? [];
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        throw $exception;
    }
}

function review_task_order(int $orderId, string $decision, string $note = ""): array
{
    $decision = $decision === "approve" ? "approved" : "rejected";
    $note = trim($note);

    $pdo = db();
    $pdo->beginTransaction();

    try {
        $orderStatement = $pdo->prepare(
            "SELECT * FROM task_orders WHERE id = ? LIMIT 1 FOR UPDATE"
        );
        $orderStatement->execute([$orderId]);
        $order = $orderStatement->fetch();

        if (!$order) {
            throw new RuntimeException("待审核订单不存在。");
        }
        if ((string) $order["status"] !== "pending_review") {
            throw new RuntimeException("该订单已处理，无需重复审核。");
        }

        $walletStatement = $pdo->prepare(
            "SELECT available_balance, frozen_balance FROM wallets WHERE user_id = ? LIMIT 1 FOR UPDATE"
        );
        $walletStatement->execute([(int) $order["user_id"]]);
        $wallet = $walletStatement->fetch();
        if (!$wallet) {
            throw new RuntimeException("钱包记录不存在。");
        }

        $reward = (float) $order["reward_usdt"];
        $availableAfter = (float) $wallet["available_balance"];
        $frozenAfter = max(0, (float) $wallet["frozen_balance"] - $reward);
        $entryType = "task_reject";
        $logAmount = 0.0;
        $logFrozen = -$reward;
        $defaultNote = "任务审核未通过，冻结佣金已释放。";

        if ($decision === "approved") {
            $availableAfter += $reward;
            $entryType = "task_credit";
            $logAmount = $reward;
            $defaultNote = "任务审核通过，佣金已转入可提现余额。";
        }

        $updateOrder = $pdo->prepare(
            "UPDATE task_orders
             SET status = ?, reviewed_at = NOW(), review_note = ?
             WHERE id = ?"
        );
        $updateOrder->execute([$decision, $note !== "" ? $note : $defaultNote, $orderId]);

        $updateWallet = $pdo->prepare(
            "UPDATE wallets
             SET available_balance = ?, frozen_balance = ?, updated_at = NOW()
             WHERE user_id = ?"
        );
        $updateWallet->execute([
            number_format($availableAfter, 2, ".", ""),
            number_format($frozenAfter, 2, ".", ""),
            (int) $order["user_id"],
        ]);

        insert_wallet_log(
            $pdo,
            (int) $order["user_id"],
            $entryType,
            $logAmount,
            $logFrozen,
            $note !== "" ? $note : $defaultNote,
            $availableAfter,
            $frozenAfter
        );

        $pdo->commit();
        return get_order_by_id($orderId, null) ?? [];
    } catch (Throwable $exception) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        throw $exception;
    }
}

function get_operator_orders(string $status, int $limit = 20): array
{
    $statement = db()->prepare(
        "SELECT o.*, u.email, u.user_code, u.vip_level
         FROM task_orders o
         INNER JOIN users u ON u.id = o.user_id
         WHERE o.status = ?
         ORDER BY COALESCE(o.submitted_at, o.claimed_at) DESC
         LIMIT ?"
    );
    $statement->bindValue(1, $status, PDO::PARAM_STR);
    $statement->bindValue(2, $limit, PDO::PARAM_INT);
    $statement->execute();

    return $statement->fetchAll() ?: [];
}

function get_recent_reviewed_orders(int $limit = 12): array
{
    $statement = db()->prepare(
        "SELECT o.*, u.email, u.user_code, u.vip_level
         FROM task_orders o
         INNER JOIN users u ON u.id = o.user_id
         WHERE o.status IN ('approved', 'rejected')
         ORDER BY o.reviewed_at DESC, o.id DESC
         LIMIT ?"
    );
    $statement->bindValue(1, $limit, PDO::PARAM_INT);
    $statement->execute();

    return $statement->fetchAll() ?: [];
}

function get_operator_stats(): array
{
    $pdo = db();

    $todayUsers = (int) ($pdo->query("SELECT COUNT(*) FROM users WHERE DATE(created_at) = CURDATE()")->fetchColumn() ?: 0);
    $pending = (int) ($pdo->query("SELECT COUNT(*) FROM task_orders WHERE status = 'pending_review'")->fetchColumn() ?: 0);
    $completed = (int) ($pdo->query("SELECT COUNT(*) FROM task_orders WHERE status = 'approved'")->fetchColumn() ?: 0);
    $credited = (float) ($pdo->query("SELECT COALESCE(SUM(amount), 0) FROM wallet_logs WHERE entry_type = 'task_credit'")->fetchColumn() ?: 0);

    return [
        "today_users" => $todayUsers,
        "pending_reviews" => $pending,
        "approved_orders" => $completed,
        "credited_amount" => $credited,
    ];
}

function status_label(string $status): string
{
    return [
        "claimed" => "进行中",
        "pending_review" => "待审核",
        "approved" => "已入账",
        "rejected" => "已驳回",
    ][$status] ?? $status;
}

function status_tone(string $status): string
{
    return [
        "claimed" => "muted",
        "pending_review" => "warning",
        "approved" => "success",
        "rejected" => "danger",
    ][$status] ?? "muted";
}

function wallet_entry_label(string $entryType): string
{
    return [
        "wallet_opened" => "钱包创建",
        "task_freeze" => "冻结佣金",
        "task_credit" => "佣金到账",
        "task_reject" => "审核释放",
        "vip_upgrade" => "VIP 开通",
    ][$entryType] ?? $entryType;
}

function render_status_badge(string $status): string
{
    return '<span class="status-pill tone-' . h(status_tone($status)) . '">' . h(status_label($status)) . '</span>';
}

function front_nav_items(): array
{
    return [
        ["key" => "home", "label" => "首页", "href" => "index.php", "icon" => "home"],
        ["key" => "tasks", "label" => "任务", "href" => "task.php", "icon" => "tasks"],
        ["key" => "vip", "label" => "VIP", "href" => "vip.php", "icon" => "vip"],
        ["key" => "wallet", "label" => "钱包", "href" => "wallet.php", "icon" => "wallet"],
        ["key" => "profile", "label" => "我的", "href" => "profile.php", "icon" => "profile"],
    ];
}

function is_front_app_nav(string $active): bool
{
    foreach (front_nav_items() as $item) {
        if ($item["key"] === $active) {
            return true;
        }
    }

    return false;
}

function app_user_name(?array $user): string
{
    if (!$user) {
        return 'User';
    }

    $email = (string) ($user['email'] ?? '');
    $local = strstr($email, '@', true);
    if ($local !== false && $local !== '') {
        return $local;
    }

    return 'ID ' . (string) ($user['user_code'] ?? '000000');
}

function app_user_initial(?array $user): string
{
    $name = app_user_name($user);
    $first = strtoupper(substr($name, 0, 1));
    return $first !== '' ? $first : 'U';
}

function task_category_options(): array
{
    return [
        'all' => '全部',
        'video' => '视频',
        'like' => '点赞',
        'social' => '社媒',
        'web' => '网站',
        'app' => '应用',
    ];
}

function task_visual_meta(array $task): array
{
    $map = [
        'watch-video' => [
            'brand' => 'YouTube',
            'icon' => 'play',
            'accent' => 'accent-red',
            'category' => 'video',
            'tag' => '视频',
            'hint' => '观看视频',
        ],
        'social-like' => [
            'brand' => 'TikTok',
            'icon' => 'heart',
            'accent' => 'accent-pink',
            'category' => 'like',
            'tag' => '点赞',
            'hint' => '点赞互动',
        ],
        'follow-brand' => [
            'brand' => 'Instagram',
            'icon' => 'spark',
            'accent' => 'accent-violet',
            'category' => 'social',
            'tag' => '社媒',
            'hint' => '关注账号',
        ],
        'comment-flow' => [
            'brand' => 'Community',
            'icon' => 'chat',
            'accent' => 'accent-cyan',
            'category' => 'social',
            'tag' => '社媒',
            'hint' => '评论互动',
        ],
        'site-browse' => [
            'brand' => 'Website',
            'icon' => 'globe',
            'accent' => 'accent-blue',
            'category' => 'web',
            'tag' => '网站',
            'hint' => '网站浏览',
        ],
        'app-download' => [
            'brand' => 'App Center',
            'icon' => 'download',
            'accent' => 'accent-orange',
            'category' => 'app',
            'tag' => '应用',
            'hint' => '下载体验',
        ],
    ];

    $slug = (string) ($task['slug'] ?? '');
    $fallback = [
        'brand' => (string) ($task['platform'] ?? 'Task'),
        'icon' => 'spark',
        'accent' => 'accent-violet',
        'category' => 'all',
        'tag' => (string) ($task['type'] ?? '任务'),
        'hint' => (string) ($task['summary'] ?? '任务返佣'),
    ];

    return $map[$slug] ?? $fallback;
}

function app_icon_svg(string $icon): string
{
    $icons = [
        'home' => '<path d="M3.75 10.5 12 4l8.25 6.5" /><path d="M6 9.5v9h12v-9" /><path d="M10 18.5v-4h4v4" />',
        'tasks' => '<rect x="5" y="4.5" width="14" height="15" rx="2.5" /><path d="M9 4.5h6" /><path d="m9 12 2 2 4-4" /><path d="M9 16h6" />',
        'vip' => '<path d="M4.5 7.5 8 11l4-5 4 5 3.5-3.5-2 8H6.5l-2-8Z" /><path d="M8.5 18.5h7" />',
        'wallet' => '<path d="M4.5 7.5A2.5 2.5 0 0 1 7 5h10a2.5 2.5 0 0 1 2.5 2.5v1.25H15a2 2 0 0 0-2 2v1.5a2 2 0 0 0 2 2h4.5V16.5A2.5 2.5 0 0 1 17 19H7a2.5 2.5 0 0 1-2.5-2.5v-9Z" /><path d="M19.5 8.75v5.5" /><path d="M15.5 11.5h.01" />',
        'profile' => '<path d="M12 11a3.75 3.75 0 1 0 0-7.5A3.75 3.75 0 0 0 12 11Z" /><path d="M4.5 19.5a7.5 7.5 0 0 1 15 0" />',
        'play' => '<path d="M8 6.5v11l8-5.5-8-5.5Z" />',
        'deposit' => '<path d="M12 4.5v12" /><path d="m7.5 12 4.5 4.5 4.5-4.5" /><path d="M5 19.5h14" />',
        'withdraw' => '<path d="M12 19.5v-12" /><path d="m16.5 12-4.5-4.5L7.5 12" /><path d="M5 4.5h14" />',
        'swap' => '<path d="M6 8h10" /><path d="m12.5 4.5 3.5 3.5-3.5 3.5" /><path d="M18 16H8" /><path d="m11.5 12.5-3.5 3.5 3.5 3.5" />',
        'history' => '<path d="M4.5 12a7.5 7.5 0 1 0 2.2-5.3" /><path d="M4.5 5.5v3.5H8" /><path d="M12 8v4l2.5 1.5" />',
        'heart' => '<path d="M12 19.25 5 12.75a4.1 4.1 0 0 1 5.8-5.8L12 8.15l1.2-1.2a4.1 4.1 0 0 1 5.8 5.8L12 19.25Z" />',
        'spark' => '<path d="M12 3.5 13.8 8.2 18.5 10 13.8 11.8 12 16.5 10.2 11.8 5.5 10 10.2 8.2 12 3.5Z" /><path d="M18.5 16.5 19.2 18.3 21 19 19.2 19.7 18.5 21.5 17.8 19.7 16 19 17.8 18.3 18.5 16.5Z" />',
        'chat' => '<path d="M5 6.5A2.5 2.5 0 0 1 7.5 4h9A2.5 2.5 0 0 1 19 6.5v6A2.5 2.5 0 0 1 16.5 15H11l-4.5 4v-4H7.5A2.5 2.5 0 0 1 5 12.5v-6Z" /><path d="M9 9.5h6" /><path d="M9 12h3.5" />',
        'globe' => '<circle cx="12" cy="12" r="8.5" /><path d="M3.5 12h17" /><path d="M12 3.5a13 13 0 0 1 0 17" /><path d="M12 3.5a13 13 0 0 0 0 17" />',
        'download' => '<path d="M12 4.5v10" /><path d="m8.5 11.5 3.5 3.5 3.5-3.5" /><path d="M5 19.5h14" />',
        'check' => '<path d="m6.5 12 3.5 3.5L17.5 8" />',
        'lock' => '<rect x="6" y="10" width="12" height="9" rx="2" /><path d="M8.5 10V8a3.5 3.5 0 0 1 7 0v2" />',
        'support' => '<path d="M6 11.5a6 6 0 1 1 12 0v2.5A2.5 2.5 0 0 1 15.5 16.5H14" /><path d="M10 18h4" /><path d="M7 12.5h-.5A1.5 1.5 0 0 0 5 14v1.5A1.5 1.5 0 0 0 6.5 17H7" /><path d="M17 12.5h.5A1.5 1.5 0 0 1 19 14v1.5A1.5 1.5 0 0 1 17.5 17H17" />',
        'chevron-right' => '<path d="m10 7 5 5-5 5" />',
    ];

    $paths = $icons[$icon] ?? $icons['spark'];

    return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' . $paths . '</svg>';
}

function render_head(string $pageTitle, string $pageDescription = ""): void
{
    $siteName = project_name();
    $description = trim($pageDescription) !== "" ? $pageDescription : project_description();
    $fullTitle = trim($pageTitle) !== "" ? $pageTitle . " · " . $siteName : $siteName;
    $projectDescription = $_SERVER["PROJECT_DESCRIPTION"] ?? "";
    $projectImageUrl = $_SERVER["PROJECT_IMAGE_URL"] ?? "";
    ?>
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title><?= h($fullTitle) ?></title>
  <meta name="description" content="<?= h($description ?: $projectDescription) ?>" />
  <meta name="theme-color" content="#0b0f14" />
  <meta property="og:title" content="<?= h($fullTitle) ?>" />
  <meta property="og:description" content="<?= h($description ?: $projectDescription) ?>" />
  <meta property="og:type" content="website" />
  <meta property="twitter:card" content="summary_large_image" />
  <meta property="twitter:title" content="<?= h($fullTitle) ?>" />
  <meta property="twitter:description" content="<?= h($description ?: $projectDescription) ?>" />
<?php if ($projectImageUrl): ?>
  <meta property="og:image" content="<?= h($projectImageUrl) ?>" />
  <meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
<?php endif; ?>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
  <link href="assets/css/custom.css?v=<?= h(asset_version("assets/css/custom.css")) ?>" rel="stylesheet" />
</head>
<?php
}

function render_header(string $active = "home"): void
{
    $user = current_user();
    $front = is_front_app_nav($active);
    $links = array_merge(front_nav_items(), [
        ["key" => "ops", "label" => "运营台", "href" => "operations.php"],
        ["key" => "health", "label" => "Healthz", "href" => "healthz.php"],
    ]);
    ?>
<div class="app-shell<?= $front ? ' app-shell-front' : '' ?>">
<?php if ($front): ?>
  <div class="front-glow front-glow-one"></div>
  <div class="front-glow front-glow-two"></div>
  <header class="front-topbar">
    <div class="front-topbar-shell">
      <div class="d-flex align-items-center justify-content-between gap-3">
        <a href="index.php" class="front-brand text-decoration-none">
          <span class="brand-mark brand-mark-gradient"><?= app_icon_svg('play') ?></span>
          <div>
            <div class="brand-title">任务返佣平台</div>
            <div class="brand-subtitle">看视频 · 点赞 · 关注 · 网站浏览</div>
          </div>
        </a>
        <div class="d-flex align-items-center gap-2">
          <?php if ($user): ?>
            <span class="mini-pill"><?= h(vip_info((int) $user['vip_level'])['name']) ?></span>
          <?php endif; ?>
          <a href="operations.php" class="mini-icon-btn" aria-label="后台管理预览"><?= app_icon_svg('support') ?></a>
        </div>
      </div>
      <div class="front-user-row mt-3">
        <?php if ($user): ?>
          <div class="user-chip compact-chip">
            <span class="mono">ID <?= h($user['user_code']) ?></span>
            <span class="divider-dot"></span>
            <span><?= h($user['email']) ?></span>
          </div>
          <a href="logout.php" class="mini-link">退出</a>
        <?php else: ?>
          <a href="<?= h(start_page_url()) ?>" class="mini-link">邮箱注册 / 登录</a>
          <a href="healthz.php" class="mini-icon-btn" aria-label="健康检查"><?= app_icon_svg('check') ?></a>
        <?php endif; ?>
      </div>
    </div>
  </header>
<?php else: ?>
  <header class="topbar sticky-top">
    <div class="container py-3">
      <div class="topbar-panel">
        <div class="d-flex align-items-start justify-content-between gap-3 flex-wrap">
          <div>
            <a href="index.php" class="brand-link text-decoration-none">
              <span class="brand-mark brand-mark-gradient"><?= app_icon_svg('play') ?></span>
              <div>
                <div class="brand-title"><?= h(project_name()) ?></div>
                <div class="brand-subtitle">Task rebate operations · mobile preview</div>
              </div>
            </a>
          </div>
          <div class="d-flex flex-column align-items-start align-items-md-end gap-2 ms-auto">
            <?php if ($user): ?>
              <div class="user-chip">
                <span class="mono">ID <?= h($user['user_code']) ?></span>
                <span class="divider-dot"></span>
                <span><?= h($user['email']) ?></span>
                <span class="divider-dot"></span>
                <span><?= h(vip_info((int) $user['vip_level'])['name']) ?></span>
              </div>
              <a href="logout.php" class="btn btn-outline-light btn-sm px-3">退出</a>
            <?php else: ?>
              <a href="<?= h(start_page_url()) ?>" class="btn btn-outline-light btn-sm px-3">邮箱注册 / 登录</a>
            <?php endif; ?>
          </div>
        </div>
        <nav class="app-nav mt-3" aria-label="Primary">
          <?php foreach ($links as $link): ?>
            <a href="<?= h($link['href']) ?>" class="nav-chip<?= $active === $link['key'] ? ' active' : '' ?>"><?= h($link['label']) ?></a>
          <?php endforeach; ?>
        </nav>
      </div>
    </div>
  </header>
<?php endif; ?>
<?php
}

function render_toasts(array $flashes): void
{
    if (!$flashes) {
        return;
    }
    ?>
<div class="toast-stack position-fixed top-0 end-0 p-3">
  <?php foreach ($flashes as $flash): ?>
    <div class="toast align-items-center border-0 show mb-2" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="4500">
      <div class="toast-shell tone-<?= h((string) ($flash['tone'] ?? 'info')) ?>">
        <div class="toast-body"><?= nl2br(h((string) ($flash['message'] ?? ''))) ?></div>
        <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
      </div>
    </div>
  <?php endforeach; ?>
</div>
<?php
}

function render_layout_start(string $pageTitle, string $pageDescription, string $activeNav): void
{
    $GLOBALS['app_active_nav'] = $activeNav;
    $isFront = is_front_app_nav($activeNav);
    render_head($pageTitle, $pageDescription);
    ?>
<body class="<?= $isFront ? 'front-app-body' : '' ?>">
<?php render_header($activeNav); ?>
  <main class="<?= $isFront ? 'front-main' : 'container py-4 py-lg-5' ?>">
    <?php render_toasts(pull_flashes()); ?>
<?php
}

function render_bottom_nav(string $active): void
{
    ?>
<nav class="bottom-app-nav" aria-label="底部导航">
  <div class="bottom-app-nav-shell">
    <?php foreach (front_nav_items() as $item): ?>
      <a href="<?= h($item['href']) ?>" class="bottom-nav-item<?= $active === $item['key'] ? ' active' : '' ?>">
        <span class="bottom-nav-icon"><?= app_icon_svg((string) $item['icon']) ?></span>
        <span class="bottom-nav-label"><?= h($item['label']) ?></span>
      </a>
    <?php endforeach; ?>
  </div>
</nav>
<?php
}

function render_layout_end(): void
{
    $activeNav = (string) ($GLOBALS['app_active_nav'] ?? '');
    $isFront = is_front_app_nav($activeNav);
    ?>
  </main>
  <?php if ($isFront): ?>
    <?php render_bottom_nav($activeNav); ?>
  <?php else: ?>
  <footer class="container footer-shell pb-4 pt-2">
    <div class="footer-panel d-flex flex-column flex-lg-row justify-content-between gap-3 align-items-lg-center">
      <div>
        <div class="footer-title">第一版可用闭环</div>
        <div class="footer-copy">邮箱开户注册、任务领取、倒计时提交、运营审核、钱包流水已打通。</div>
      </div>
      <div class="d-flex flex-wrap gap-2">
        <a href="index.php" class="footer-link">首页</a>
        <a href="task.php" class="footer-link">任务</a>
        <a href="vip.php" class="footer-link">VIP</a>
        <a href="wallet.php" class="footer-link">钱包</a>
        <a href="profile.php" class="footer-link">我的</a>
        <a href="operations.php" class="footer-link">运营台</a>
      </div>
    </div>
  </footer>
  <?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="assets/js/main.js?v=<?= h(asset_version('assets/js/main.js')) ?>" defer></script>
</body>
</html>
<?php
}
