<?php
/**
 * Sanitize form input before storing it.
 *
 * html_entity_decode() first makes this idempotent: a value that was already
 * saved as "Office &amp; Cyber" and then re-submitted from an edit form comes
 * back out as "Office &amp; Cyber" instead of compounding into
 * "Office &amp;amp; Cyber" each time the row is edited.
 */
function sanitize($input) {
    $input = html_entity_decode((string)$input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
}

/**
 * Safe echo for any text coming out of the database.
 *
 * Some rows were saved through forms that already ran htmlspecialchars(), so
 * the stored value literally contains "&amp;". Escaping that a second time
 * turns it into "&amp;amp;" and the page shows "&amp;" instead of "&".
 * Decoding first, then escaping once, always renders the intended character
 * and stays XSS-safe.
 */
function esc($value) {
    if ($value === null) { return ''; }
    return htmlspecialchars(
        html_entity_decode((string)$value, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
        ENT_QUOTES,
        'UTF-8'
    );
}

function redirect($url) {
    if (!headers_sent()) {
        header("Location: " . $url);
        exit;
    } else {
        echo "<script>window.location.href='" . $url . "';</script>";
        exit;
    }
}

function isLoggedIn() {
    global $db;
    if (!isset($_SESSION['user_id'])) {
        return false;
    }
    $user_id = $_SESSION['user_id'];
    $user_data = $db->select("SELECT id FROM users WHERE id = ?", [$user_id]);
    if (empty($user_data)) {
        unset($_SESSION['user_id']);
        session_destroy();
        return false;
    }
    return true;
}

function isSeller() {
    return isset($_SESSION['user_type']) && $_SESSION['user_type'] == 'seller';
}

function isAdmin() {
    return isset($_SESSION['user_type']) && $_SESSION['user_type'] == 'admin';
}

/* ---------------------------------------------------------------------------
   PERMISSION CHECKER
   ----------------------------------------------------------------------------
   Access is now kept module by module, action by action and column by column
   in includes/acl.php. Every page written before that still calls
   hasPermission('manage_users') and friends, so this function translates the
   old single key into the new detailed set: it answers yes when the account
   holds anything at all inside a module that key used to cover.

   If includes/acl.php is not on the server yet, the original behaviour is
   used, so nothing breaks half way through an upload.
   --------------------------------------------------------------------------- */
function hasPermission($permission) {
    global $db;
    if (!isset($_SESSION['user_id']) || ($_SESSION['user_type'] ?? '') !== 'admin') return false;

    if (!function_exists('acl_can') && @is_file(__DIR__ . '/acl.php')) {
        require_once __DIR__ . '/acl.php';
    }

    if (function_exists('acl_effective')) {
        $set = acl_effective();
        if (in_array('*', $set, true)) return true;
        if (!empty($set)) {
            /* a detailed key was passed straight in, e.g. 'users.edit' */
            if (strpos((string)$permission, '.') !== false) { return acl_can($permission); }
            foreach (acl_legacy_map()[$permission] ?? [] as $mk) {
                if (acl_has_module($mk)) return true;
            }
            return false;
        }
    }

    /* ---- the original check, kept as a fallback ---- */
    $user = $db->select("SELECT role_id FROM users WHERE id = ?", [$_SESSION['user_id']]);
    if (empty($user) || empty($user[0]['role_id'])) return true;

    $role = $db->select("SELECT permissions FROM roles WHERE id = ?", [$user[0]['role_id']]);
    if (empty($role)) return false;

    $permissions = json_decode($role[0]['permissions'], true);
    if (!is_array($permissions)) return false;
    if (in_array('all', $permissions)) return true;

    return in_array($permission, $permissions);
}

function getSetting($key, $default = '') {
    global $db;
    $result = $db->select("SELECT setting_value FROM settings WHERE setting_key = ?", [$key]);
    if (!empty($result)) {
        return $result[0]['setting_value'];
    }
    return $default;
}

/**
 * Banner slides for one panel, and one panel only.
 *
 *   'landing'    the big hero on the public home page (index.php)
 *   'dashboard'  the banner a member sees after logging in (dashboard.php)
 *
 * The two sets live in the same table but never mix: editing the home page
 * banner leaves the after-login banner exactly as it was, and the other way
 * round. Older databases with no `panel` column fall back to returning
 * everything, so nothing breaks before tmomigration.php has been run.
 */
function getHeroSlides($panel = 'landing') {
    global $db;
    $panel = ($panel === 'dashboard') ? 'dashboard' : 'landing';
    try {
        return $db->select(
            "SELECT * FROM hero_slides
             WHERE status = 1 AND COALESCE(NULLIF(panel, ''), 'landing') = ?
             ORDER BY slide_order ASC, id ASC", [$panel]);
    } catch (\Throwable $e) {
        return $db->select("SELECT * FROM hero_slides WHERE status = 1 ORDER BY slide_order ASC");
    }
}

function getStatistics() {
    global $db;
    return $db->select("SELECT * FROM statistics WHERE status = 1 ORDER BY stat_order ASC");
}

function getWhyChooseUs() {
    global $db;
    return $db->select("SELECT * FROM why_choose_us WHERE status = 1 ORDER BY item_order ASC");
}

function getTeamMembers() {
    global $db;
    return $db->select("SELECT * FROM team_members WHERE status = 1 ORDER BY member_order ASC");
}

function getVideos() {
    global $db;
    return $db->select("SELECT * FROM videos WHERE status = 1 ORDER BY video_order ASC");
}

// --- NEW: MULTI-LEVEL REFERRAL COMMISSION ENGINE ---
require_once __DIR__ . '/wallet.php';

/**
 * 3-level referral commission on a SUBSCRIPTION purchase.
 * Level 1 = direct referrer, Level 2 = their referrer, Level 3 = one above that.
 * Percentages come from Admin -> Refer & Earn.
 * Money lands in the VIRTUAL wallet (spendable, not withdrawable) and is
 * recorded under its own category so the Earning Report can split the levels.
 */
function distributeReferralCommission($db, $buyer_id, $order_amount, $ref_type = 'subscription', $ref_id = null) {
    $order_amount = (float)$order_amount;
    if ($order_amount <= 0) return;

    $pcts = [
        1 => floatval(getSetting('ref_level_1_comm', '10')),
        2 => floatval(getSetting('ref_level_2_comm', '5')),
        3 => floatval(getSetting('ref_level_3_comm', '2')),
    ];
    if ($pcts[1] <= 0 && $pcts[2] <= 0 && $pcts[3] <= 0) return;

    $buyer = $db->select("SELECT referred_by, name FROM users WHERE id = ?", [(int)$buyer_id]);
    if (empty($buyer)) return;
    $buyer_name = $buyer[0]['name'];

    $cats  = [1 => 'ref_sub_l1', 2 => 'ref_sub_l2', 3 => 'ref_sub_l3'];
    $upline = (int)($buyer[0]['referred_by'] ?? 0);
    $seen   = [(int)$buyer_id => true];   // guards against a circular referral chain

    for ($level = 1; $level <= 3; $level++) {
        if ($upline <= 0 || isset($seen[$upline])) break;
        $seen[$upline] = true;

        if ($pcts[$level] > 0) {
            $amt = round(($order_amount * $pcts[$level]) / 100, 2);
            if ($amt > 0) {
                wallet_credit($db, $upline, WALLET_VIRTUAL, $amt, [
                    'category'        => $cats[$level],
                    'description'     => "Level $level subscription commission ({$pcts[$level]}%) from $buyer_name",
                    'reference_type'  => $ref_type,
                    'reference_id'    => $ref_id,
                    'counterparty_id' => (int)$buyer_id,
                ]);
            }
        }

        $next = $db->select("SELECT referred_by FROM users WHERE id = ?", [$upline]);
        $upline = !empty($next) ? (int)($next[0]['referred_by'] ?? 0) : 0;
    }
}

/**
 * Record a discount the user received. REPORT ONLY — no money moves, the row
 * exists so the Earning Report can show "how much you saved".
 */
function recordOwnDiscount($db, $user_id, $amount, $description, $opts = []) {
    $amount = round((float)$amount, 2);
    if ($amount <= 0) return;
    wallet_ensure_schema($db);
    // wallet = 'report' keeps this out of the main/virtual balances and out of
    // Wallet History — it is a statistic, not a transaction.
    wallet__ledger($db, $user_id, WALLET_REPORT, 'credit', $amount, 0, array_merge([
        'category'    => 'own_discount',
        'description' => $description,
    ], $opts));
}
?>