<?php
/**
 * MatrixEngine.php  v13 — FINAL UNIFIED VERSION
 *
 * ═══════════════════════════════════════════════════════════════════════
 * WORKING PLAN (sponsor chain — meddolic_user_set_placeholder_details):
 *
 *   pkg1 ($30)  → 100% → A1 (sponsor),  no admin
 *   pkg2 ($40)  → 50% A1 + 50% A1,      no admin
 *   pkg3 ($60)  → 60% A1 + 40% A2,      admin 10%
 *                 → AUTO-ENTER buyer into SMART pool (poolId=1) if not already in
 *   pkg4 ($200) → 60% A1 + 40% A3,      admin 10%
 *   pkg5 ($500) → 30% A1 + 30% A4 + 40% across 10 levels, admin 10%
 *   pkg6+       → same structure (30% A1, 30% A(n), 40%×10)
 *
 *   AUTO-UPGRADE trigger: 3 direct / 9 team / 27 team
 *   Gate: must manually purchase corresponding global pool before next working step
 *
 * ═══════════════════════════════════════════════════════════════════════
 * GLOBAL MATRIX (pool tree — meddolic_user_pool_entry_details):
 *
 *   Pool = 3 wide × 3 deep (YOU + L1:3 + L2:9 + L3:27 = 40 total positions)
 *   Each pool is INDEPENDENT. Pools: 1=SMART($100), 2=SILVER($500), etc.
 *
 *   PER-JOIN INCOME (paid every time someone joins under a node):
 *     leg1 (1st child joins) → $poolPrice → parent node ONLY
 *     leg2 (2nd child joins) → $poolPrice → parent + $poolPrice → parent's pool-grandparent (A2)
 *     leg3 (3rd child joins) → $poolPrice → parent + $poolPrice → parent's pool-grandparent (A2)
 *     If no grandparent exists → both shares go to parent
 *
 *   LEVEL COMPLETION BONUSES (from meddolic_config_pool_income):
 *     L1 complete (3 direct filled)   → poolLevel=1 bonus
 *     L2 complete (9 grandchildren)   → poolLevel=2 bonus
 *     L3 complete (27 great-grand)    → poolLevel=3 bonus → RE-ENTRY same pool → CHAIN to next pool
 *
 *   DIRECT REQUIREMENT (pools 2+):
 *     Must have ≥2 active direct referrals to RECEIVE income.
 *     If unqualified → income passes to pool-tree parent.
 *
 *   AUTO-ENTRY: only pkg3 auto-enters SMART pool.
 *   CHAIN: L3 complete → try auto-enter next pool (if manually purchased).
 *
 * ═══════════════════════════════════════════════════════════════════════
 */
require_once __DIR__ . '/db.php';

class MatrixEngine
{
    private PDO   $db;
    private float $adminPct;

    // Working pkg → which global pool it UNLOCKS/triggers
    public const WORKING_TRIGGERS_GLOBAL = [
        3 => 1,   // pkg3 ($60)    → SMART ($100)      [AUTO-ENTER on purchase]
        4 => 2,   // pkg4 ($200)   → SILVER ($500)     [unlock only, manual buy required]
        5 => 3,   // pkg5 ($500)   → GOLD ($2000)      [unlock only]
        6 => 4,   // pkg6 ($4000)  → ROYAL ($8000)     [unlock only]
        7 => 5,   // pkg7 ($16000) → DIAMOND ($40k)    [unlock only]
        8 => 6,   // pkg8 ($100k)  → CROWN ($200k)     [unlock only]
        9 => 7,   // pkg9          → 2L ($800k)        [unlock only]
    ];

    // Pool L3 complete → try to chain into next pool
    private const GLOBAL_CHAIN = [1=>2, 2=>3, 3=>4, 4=>5, 5=>6, 6=>7, 7=>8];

    // Pool → minimum working package level required
    private const GLOBAL_MIN_WORKING = [1=>3, 2=>4, 3=>5, 4=>6, 5=>7, 6=>8, 7=>9];

    // Pool → minimum active directs to RECEIVE income (0 = none required)
    private const GLOBAL_DIRECT_REQ = [1=>0, 2=>2, 3=>2, 4=>2, 5=>2, 6=>2, 7=>2];

    // Working plan income rules: [directPct, uplinePct, uplineAncLvl, hasLevelDist, placementAncLvl]
    private const INCOME_RULES = [
        1 => [100.0,  0.0, 1, false, 0],
        2 => [ 50.0, 50.0, 1, false, 0],
        3 => [ 60.0, 40.0, 2, false, 0],
        4 => [ 60.0, 40.0, 3, false, 0],
        5 => [ 30.0, 40.0, 0, true,  4],
        6 => [ 30.0, 40.0, 0, true,  5],
        7 => [ 30.0, 40.0, 0, true,  6],
        8 => [ 30.0, 40.0, 0, true,  7],
    ];

    public function __construct()
    {
        $this->db       = getDB();
        $this->adminPct = (float)(getMiscSetting()['adminCharge'] ?? 10.0);
    }

    // ════════════════════════════════════════════════════════════════════
    //  1. BUILD WORKING TREE  (called at registration)
    //     Finds first empty BFS slot under sponsor, saves placeholderId
    //     + legPosition, then records ancestor chain in placeholder table.
    // ════════════════════════════════════════════════════════════════════

    public function buildChildChain(int $newMemberId, int $sponsorId): void
    {
        $placement     = $this->find3x3Placement($sponsorId);
        $placeholderId = $placement['placeholderId'];
        $legPosition   = $placement['legPosition'];

        $this->db->prepare(
            "UPDATE meddolic_user_details
             SET placeholderId=?, legPosition=? WHERE member_id=?"
        )->execute([$placeholderId, $legPosition, $newMemberId]);

        // Walk up tree, recording ancestor→child rows
        $pId = $placeholderId;
        $leg = $legPosition;
        for ($lvl = 1; $lvl <= 100 && $pId > 0; $lvl++) {
            $this->db->prepare(
                "INSERT IGNORE INTO meddolic_user_set_placeholder_details
                   (member_id, child_id, legPosition, level, date_time, topup_flag)
                 VALUES (?, ?, ?, ?, NOW(), 1)"
            )->execute([$pId, $newMemberId, $leg, $lvl]);

            $q = $this->db->prepare(
                "SELECT placeholderId, legPosition FROM meddolic_user_details
                 WHERE member_id=? LIMIT 1"
            );
            $q->execute([$pId]);
            $res = $q->fetch();
            $pId = $res ? (int)$res['placeholderId'] : 0;
            $leg = $res ? (int)$res['legPosition']   : 0;
        }
    }

    private function find3x3Placement(int $sponsorId): array
    {
        $queue = [$sponsorId];
        $qi    = 0;
        while (isset($queue[$qi])) {
            $parentId = $queue[$qi];
            for ($leg = 1; $leg <= 3; $leg++) {
                $stmt = $this->db->prepare(
                    "SELECT member_id FROM meddolic_user_details
                     WHERE placeholderId=? AND legPosition=? LIMIT 1"
                );
                $stmt->execute([$parentId, $leg]);
                $occ = $stmt->fetchColumn();
                if (!$occ) {
                    return ['placeholderId' => $parentId, 'legPosition' => $leg];
                }
                $queue[] = (int)$occ;
            }
            $qi++;
            if ($qi > 10000) break;
        }
        return ['placeholderId' => $sponsorId, 'legPosition' => 1];
    }

    // ════════════════════════════════════════════════════════════════════
    //  2. VALIDATE WORKING PURCHASE
    //     Sequential + gate check (must have paid global pool first)
    // ════════════════════════════════════════════════════════════════════

    public function validateWorkingPurchase(int $memberId, int $pkgId): ?string
    {
        $user     = getUserById($memberId);
        $curPkgId = (int)($user['currentPackage'] ?? 0);

        if ($pkgId !== $curPkgId + 1) {
            return 'Sequential order required. Next step: ' . ($curPkgId + 1);
        }

        // If the CURRENT completed step unlocked a pool → user must have manually bought it
        if (isset(self::WORKING_TRIGGERS_GLOBAL[$curPkgId])) {
            $gId = self::WORKING_TRIGGERS_GLOBAL[$curPkgId];
            $chk = $this->db->prepare(
                "SELECT id FROM gm_package_purchases
                 WHERE member_id=? AND package_id=? AND buy_type=3
                   AND trx_hash NOT LIKE 'AUTO_%' AND status=1 LIMIT 1"
            );
            $chk->execute([$memberId, $gId]);
            if (!$chk->fetch()) {
                $g = $this->fetchMatrixPkg($gId);
                return 'Buy ' . ($g['poolName'] ?? 'Matrix Pool') .
                       ' ($' . number_format((float)($g['poolPrice'] ?? 0), 0) .
                       ') in Matrix Packages before this step.';
            }
        }
        return null;
    }

    // ════════════════════════════════════════════════════════════════════
    //  3. PROCESS MANUAL WORKING UPGRADE
    //     Pays working income → advances package → auto-enters SMART (pkg3 only)
    // ════════════════════════════════════════════════════════════════════

    public function processManualUpgrade(
        int $buyerId, int $sponsorId, float $pkgPrice, int $pkgId, string $txHash
    ): array {
        $rules        = self::INCOME_RULES[$pkgId] ?? [50.0, 50.0, 1, false, 0];
        $directPct    = $rules[0];
        $uplinePct    = $rules[1];
        $uplineAncLvl = $rules[2];
        $hasLevelDist = $rules[3];
        $placementAnc = $rules[4];
        $adminPct     = ($pkgId <= 2) ? 0.0 : $this->adminPct;

        // ── A. Sponsor / Direct income ────────────────────────────────
        if ($directPct > 0 && $sponsorId > 0) {
            $gross = $pkgPrice * $directPct / 100;
            $admin = round($gross * $adminPct / 100, 5);
            $net   = round($gross - $admin, 5);
            try {
                $this->db->prepare(
                    "INSERT INTO meddolic_user_referral_income
                       (memberId,childId,referralIncome,packagePrice,adminCharge,
                        netIncome,packageId,dateTime,referralPercent)
                     VALUES (?,?,?,?,?,?,?,NOW(),?)"
                )->execute([$sponsorId,$buyerId,$gross,$pkgPrice,$admin,$net,$pkgId,$directPct]);
            } catch (Throwable $t) {}
            $this->addToWallet($sponsorId, $net, 1, 0);
        }

        // ── B. Fixed upline income (pkg3→A2, pkg4→A3) ────────────────
        if (!$hasLevelDist && $uplinePct > 0 && $uplineAncLvl > 0) {
            $uplineId = $this->getNthAncestor($buyerId, $uplineAncLvl);
            if ($uplineId > 0) {
                $gross = $pkgPrice * $uplinePct / 100;
                $admin = round($gross * $adminPct / 100, 5);
                $net   = round($gross - $admin, 5);
                try {
                    $this->db->prepare(
                        "INSERT INTO meddolic_user_upgrade_income
                           (memberId,childId,upgradeIncome,level,packagePrice,adminCharge,
                            netIncome,packageId,upgradePercent,dateTime)
                         VALUES (?,?,?,?,?,?,?,?,?,NOW())"
                    )->execute([$uplineId,$buyerId,$gross,$uplineAncLvl,
                                $pkgPrice,$admin,$net,$pkgId,$uplinePct]);
                } catch (Throwable $t) {}
                $this->addToWallet($uplineId, $net, 2, 0);
            }
        }

        // ── C. Level distribution (pkg5+): 40% across 10 upline levels
        if ($hasLevelDist && $uplinePct > 0) {
            $perGross = $pkgPrice * $uplinePct / 100 / 10;
            $perAdmin = round($perGross * $adminPct / 100, 5);
            $perNet   = round($perGross - $perAdmin, 5);
            $ancId    = $sponsorId;
            for ($lvl = 1; $lvl <= 10 && $ancId > 0; $lvl++) {
                $aQ = $this->db->prepare(
                    "SELECT sponser_id, topup_flag, currentPackage
                     FROM meddolic_user_details WHERE member_id=? LIMIT 1"
                );
                $aQ->execute([$ancId]);
                $anc = $aQ->fetch();
                if ($anc && (int)$anc['topup_flag'] && (int)$anc['currentPackage'] >= $pkgId) {
                    try {
                        $this->db->prepare(
                            "INSERT INTO meddolic_user_upgrade_income
                               (memberId,childId,upgradeIncome,level,packagePrice,adminCharge,
                                netIncome,packageId,upgradePercent,dateTime)
                             VALUES (?,?,?,?,?,?,?,?,4.00,NOW())"
                        )->execute([$ancId,$buyerId,$perGross,$lvl,
                                    $pkgPrice,$perAdmin,$perNet,$pkgId]);
                    } catch (Throwable $t) {}
                    $this->addToWallet($ancId, $perNet, 2, 0);
                }
                $ancId = (int)($anc['sponser_id'] ?? 0);
            }
        }

        // ── D. Placement income (pkg5+): 30% to A(n) ─────────────────
        if ($hasLevelDist && $placementAnc > 0) {
            $plId = $this->getNthAncestor($buyerId, $placementAnc);
            if ($plId > 0) {
                $gross = $pkgPrice * 30.0 / 100;
                $admin = round($gross * $adminPct / 100, 5);
                $net   = round($gross - $admin, 5);
                try {
                    $this->db->prepare(
                        "INSERT INTO meddolic_user_upgrade_income
                           (memberId,childId,upgradeIncome,level,packagePrice,adminCharge,
                            netIncome,packageId,upgradePercent,dateTime)
                         VALUES (?,?,?,?,?,?,?,?,30.00,NOW())"
                    )->execute([$plId,$buyerId,$gross,$placementAnc,
                                $pkgPrice,$admin,$net,$pkgId]);
                } catch (Throwable $t) {}
                $this->addToWallet($plId, $net, 2, 0);
            }
        }

        // ── E. Advance buyer's working package ────────────────────────
        $this->db->prepare(
            "UPDATE meddolic_user_details SET currentPackage=? WHERE member_id=?"
        )->execute([$pkgId, $buyerId]);

        // ── F. AUTO-ENTER SMART pool only for pkg3 ───────────────────
        //    Check if user NOT already entered. If not, enter for free.
        // if ($pkgId === 3) {
        //     $smartPoolId = self::WORKING_TRIGGERS_GLOBAL[3]; // = 1
        //     $already = $this->db->prepare(
        //         "SELECT entryId FROM meddolic_user_pool_entry_details
        //          WHERE memberId=? AND poolId=? LIMIT 1"
        //     );
        //     $already->execute([$buyerId, $smartPoolId]);
        //     if (!$already->fetch()) {
        //         $this->enterGlobalPool($buyerId, $smartPoolId, 'AUTO_PKG3', $txHash);
        //     }
        // }

        return ['success' => true];
    }

    // ════════════════════════════════════════════════════════════════════
    //  4. WORKING AUTO-UPGRADE  (3/9/27 trigger)
    //     Called from buyNormalPackage.php after buildChildChain()
    // ════════════════════════════════════════════════════════════════════

    public function checkWorkingAutoUpgrade(int $memberId): void
    {
        if ($memberId <= 0) return;
        $u = getUserById($memberId);
        if (!$u || !(int)($u['topup_flag'] ?? 0)) return;

        $curId  = (int)($u['currentPackage'] ?? 0);
        if ($curId <= 0) return;
        $nextId  = $curId + 1;
        $nextPkg = $this->fetchPoolPkg($nextId);
        if (!$nextPkg) return;

        // Gate: must have manually bought the corresponding global pool
        if (isset(self::WORKING_TRIGGERS_GLOBAL[$curId])) {
            $gId = self::WORKING_TRIGGERS_GLOBAL[$curId];
            $chk = $this->db->prepare(
                "SELECT id FROM gm_package_purchases
                 WHERE member_id=? AND package_id=? AND buy_type=3
                   AND trx_hash NOT LIKE 'AUTO_%' LIMIT 1"
            );
            $chk->execute([$memberId, $gId]);
            if (!$chk->fetch()) return;
        }

        $l1  = $this->countTeamRange($memberId, 1, 1);
        $l12 = $this->countTeamRange($memberId, 1, 2);
        $l13 = $this->countTeamRange($memberId, 1, 3);

        $trigger = null;
        if      ($l13 >= 27) $trigger = '27-team';
        elseif  ($l12 >= 9)  $trigger = '9-team';
        elseif  ($l1  >= 3)  $trigger = '3-direct';
        if (!$trigger) return;

        // Duplicate guard
        $dup = $this->db->prepare(
            "SELECT id FROM gm_auto_upgrades
             WHERE member_id=? AND from_pkg_id=? AND trigger_reason=? LIMIT 1"
        );
        $dup->execute([$memberId, $curId, $trigger]);
        if ($dup->fetch()) return;

        $this->db->prepare(
            "INSERT INTO gm_auto_upgrades
               (member_id,from_pkg_id,to_pkg_id,trigger_reason,created_at)
             VALUES (?,?,?,?,NOW())"
        )->execute([$memberId, $curId, $nextId, $trigger]);

        $this->processManualUpgrade(
            $memberId,
            (int)($u['sponser_id'] ?? 0),
            (float)$nextPkg['upgradePrice'],
            $nextId,
            'AUTO_' . $trigger
        );

        // Cascade upward to sponsor
        $this->checkWorkingAutoUpgrade((int)($u['sponser_id'] ?? 0));
    }

    // Legacy alias
    public function checkAutoUpgrade(int $memberId): void
    {
        $this->checkWorkingAutoUpgrade($memberId);
    }

    // ════════════════════════════════════════════════════════════════════
    //  5. PROCESS GLOBAL POOL MANUAL PURCHASE
    //     Called from buyMatrixPackage.php
    // ════════════════════════════════════════════════════════════════════

    public function processGlobalPurchase(
        int $memberId, int $poolId, float $poolPrice, string $txHash
    ): array {
        $minPkg = self::GLOBAL_MIN_WORKING[$poolId] ?? 0;
        if ($minPkg > 0) {
            $u = getUserById($memberId);
            if ((int)($u['currentPackage'] ?? 0) < $minPkg) {
                $w = $this->fetchPoolPkg($minPkg);
                return ['error' => 'Complete Working Step ' . ($w['poolLevel'] ?? $minPkg) . ' first.'];
            }
        }

        // Already active in this pool?
        $chk = $this->db->prepare(
            "SELECT entryId FROM meddolic_user_pool_entry_details
             WHERE memberId=? AND poolId=? AND poolStatus=1 LIMIT 1"
        );
        $chk->execute([$memberId, $poolId]);
        if ($chk->fetch()) {
            return ['error' => 'Already active in this pool.'];
        }

        $entryId = $this->enterGlobalPool($memberId, $poolId, 'MANUAL', $txHash);
        return $entryId
            ? ['success' => true, 'entry_id' => $entryId]
            : ['error'   => 'Pool entry failed.'];
    }

    // ════════════════════════════════════════════════════════════════════
    //  6. ENTER GLOBAL POOL  (BFS left→right, top→bottom)
    //     Each poolId has its own completely separate BFS tree.
    // ════════════════════════════════════════════════════════════════════

    public function enterGlobalPool(
        int $memberId, int $poolId, string $via = 'MANUAL', string $txHash = ''
    ): int {
        $mp = $this->fetchMatrixPkg($poolId);
        if (!$mp) return 0;
        $poolPrice = (float)$mp['poolPrice'];

        // Find parent slot via BFS in THIS pool
        [$parentEntryId, $parentMemberId, $parentPoolLevel] = $this->bfsFindParent($poolId);

        $myPoolLevel = $parentEntryId ? ($parentPoolLevel + 1) : 1;

        // Leg position = how many children parent already has + 1
        $legPos = 1;
        if ($parentEntryId) {
            $cq = $this->db->prepare(
                "SELECT COUNT(*) AS c FROM meddolic_user_pool_entry_details
                 WHERE parent1EntryId=? AND poolId=?"
            );
            $cq->execute([$parentEntryId, $poolId]);
            $legPos = (int)($cq->fetch()['c'] ?? 0) + 1;
        }

        // Insert new member into pool entry table
        $this->db->prepare(
            "INSERT INTO meddolic_user_pool_entry_details
               (memberId, poolId, poolLevel, reEntryCount, entryDate, poolStatus,
                placeholder1Id, parent1EntryId, leg1Position)
             VALUES (?,?,?,0,NOW(),1,?,?,?)"
        )->execute([
            $memberId, $poolId, $myPoolLevel,
            $parentMemberId ?: 0,
            $parentEntryId  ?: 0,
            $legPos
        ]);
        $newEntryId = (int)$this->db->lastInsertId();

        if ($parentEntryId && $parentMemberId) {

            // Record in pool placeholder details (ancestor tracking FOR THIS POOL)
            // Walk up the pool tree and insert rows for each ancestor
            $this->recordPoolAncestors($memberId, $newEntryId, $poolId, $parentEntryId, $parentMemberId, $legPos);

            // ── PAY PER-JOIN INCOME ───────────────────────────────────
            // leg1 → $poolPrice → parent only
            // leg2 → $poolPrice → parent + $poolPrice → pool-grandparent (A2 in pool)
            // leg3 → same as leg2
            $this->payGlobalJoinIncome(
                $parentMemberId, $parentEntryId, $poolId, $poolPrice, $legPos
            );

            // ── CHECK COMPLETION for parent's entry ───────────────────
            $this->checkPoolCompletion($parentMemberId, $parentEntryId, $poolId);
        }

        // ── Purchase + activation log ─────────────────────────────────
        $u    = getUserById($memberId);
        $sId  = (int)($u['sponser_id'] ?? 0);
        $hash = ($via === 'MANUAL')
            ? $txHash
            : ('AUTO_' . $via . '_' . $memberId . '_' . time());

        try {
            $this->db->prepare(
                "INSERT INTO gm_package_purchases
                   (member_id,sponser_id,package_id,package_price,trx_amount,
                    trx_hash,wallet_address,buy_type,purchase_date,status)
                 VALUES (?,?,?,?,0,?,?,3,NOW(),1)"
            )->execute([
                $memberId,$sId,$poolId,$poolPrice,$hash,$u['walletAddress']??''
            ]);
        } catch (Throwable $t) {}

        try {
            $this->db->prepare(
                "INSERT INTO meddolic_user_activation_details
                   (member_id,sponser_id,packageId,investPrice,transHash,
                    walletAddress,date_time,status,activate_by,buyType)
                 VALUES (?,?,?,?,?,?,NOW(),1,?,3)"
            )->execute([
                $memberId,$sId,$poolId,$poolPrice,$hash,$u['walletAddress']??'',$memberId
            ]);
        } catch (Throwable $t) {}

        try {
            $this->db->prepare(
                "UPDATE meddolic_user_details
                 SET poolEntry=COALESCE(poolEntry,0)+1 WHERE member_id=?"
            )->execute([$memberId]);
        } catch (Throwable $t) {}

        return $newEntryId;
    }

    // ════════════════════════════════════════════════════════════════════
    //  7. RECORD POOL ANCESTORS
    //     Walks up pool tree from parent, inserts placeholder rows.
    //     Mirrors the old placeholderSet() logic, now using PDO.
    // ════════════════════════════════════════════════════════════════════

    private function recordPoolAncestors(
        int $childMemberId, int $childEntryId,
        int $poolId, int $parentEntryId, int $parentMemberId, int $legPos
    ): void {
        $level        = 1;
        $curMember    = $parentMemberId;
        $curEntryId   = $parentEntryId;
        $curLeg       = $legPos;

        while ($curMember > 0 && $level <= 3) {
            try {
                $this->db->prepare(
                    "INSERT IGNORE INTO meddolic_user_pool_placeholder_details
                       (poolId, member_id, child_id, leg_position, level,
                        parentEntryId, entryId, date_time)
                     VALUES (?,?,?,?,?,?,?,NOW())"
                )->execute([
                    $poolId, $curMember, $childMemberId,
                    $curLeg, $level, $curEntryId, $childEntryId
                ]);
            } catch (Throwable $t) {}

            // Move to the next ancestor in pool tree
            $pQ = $this->db->prepare(
                "SELECT placeholder1Id, leg1Position, parent1EntryId
                 FROM meddolic_user_pool_entry_details
                 WHERE entryId=? AND poolId=? LIMIT 1"
            );
            $pQ->execute([$curEntryId, $poolId]);
            $pRow = $pQ->fetch();

            if (!$pRow || !(int)$pRow['placeholder1Id']) break;

            $curMember  = (int)$pRow['placeholder1Id'];
            $curLeg     = (int)$pRow['leg1Position'];
            $curEntryId = (int)$pRow['parent1EntryId'];
            $level++;
        }
    }

    // ════════════════════════════════════════════════════════════════════
    //  8. GLOBAL POOL PER-JOIN INCOME
    //
    //  DIAGRAM RULE (confirmed by $100+$100=$200 yellow arrow):
    //    leg1 → parent gets $poolPrice (full)
    //    leg2 → parent gets $poolPrice + pool-grandparent gets $poolPrice
    //    leg3 → same as leg2
    //    If no grandparent → both amounts go to parent
    // ════════════════════════════════════════════════════════════════════

    private function payGlobalJoinIncome(
        int $parentMemberId, int $parentEntryId,
        int $poolId, float $poolPrice, int $legPos
    ): void {

        if ($legPos === 1) {
            // Only parent gets paid (full pool price)
            $this->creditPoolIncome(
                $parentMemberId, $poolId, $poolPrice, $parentEntryId
            );
        } else {
            // leg2 or leg3: parent + pool-grandparent each get FULL poolPrice
            $this->creditPoolIncome(
                $parentMemberId, $poolId, $poolPrice, $parentEntryId
            );

            // Find pool-grandparent (parent's parent in THIS pool tree)
            $gpQ = $this->db->prepare(
                "SELECT placeholder1Id FROM meddolic_user_pool_entry_details
                 WHERE entryId=? AND poolId=? LIMIT 1"
            );
            $gpQ->execute([$parentEntryId, $poolId]);
            $gpRow      = $gpQ->fetch();
            $gpMemberId = (int)($gpRow['placeholder1Id'] ?? 0);

            if ($gpMemberId > 0 && $gpMemberId !== $parentMemberId) {
                // Pay grandparent (A2 in pool) full poolPrice
                $this->creditPoolIncome(
                    $gpMemberId, $poolId, $poolPrice, $parentEntryId
                );
            } else {
                // No grandparent → extra share also goes to parent
                $this->creditPoolIncome(
                    $parentMemberId, $poolId, $poolPrice, $parentEntryId
                );
            }
        }
    }

    // ════════════════════════════════════════════════════════════════════
    //  9. CREDIT POOL INCOME
    //     Checks direct requirement → if not met, passes to pool parent.
    //     Deducts admin charge, credits net to wallet.
    // ════════════════════════════════════════════════════════════════════

    private function creditPoolIncome(
        int $memberId, int $poolId, float $amount, int $refEntryId
    ): void {
        if ($memberId <= 0 || $amount <= 0) return;

        // Direct requirement check (pools 2+)
        $dirReq = self::GLOBAL_DIRECT_REQ[$poolId] ?? 0;
        if ($dirReq > 0 && $this->countActiveDirects($memberId) < $dirReq) {
            // Pass income up to pool-tree parent
            $ppQ = $this->db->prepare(
                "SELECT placeholder1Id FROM meddolic_user_pool_entry_details
                 WHERE memberId=? AND poolId=? AND poolStatus=1
                 ORDER BY entryId DESC LIMIT 1"
            );
            $ppQ->execute([$memberId, $poolId]);
            $ppRow  = $ppQ->fetch();
            $poolPt = (int)($ppRow['placeholder1Id'] ?? 0);
            if ($poolPt > 0 && $poolPt !== $memberId) {
                $this->creditPoolIncome($poolPt, $poolId, $amount, $refEntryId);
            }
            return;
        }

        $admin = round($amount * $this->adminPct / 100, 5);
        $net   = round($amount - $admin, 5);

        try {
            $this->db->prepare(
                "INSERT INTO meddolic_user_pool_income
                   (memberId, poolId, poolLevel, poolIncome, entryId,
                    entryCount, dateTime, releaseStatus)
                 VALUES (?,?,0,?,?,1,NOW(),1)"
            )->execute([$memberId, $poolId, $net, $refEntryId]);
        } catch (Throwable $t) {}

        $this->addToWallet($memberId, $net, 3, $refEntryId);
    }

    // ════════════════════════════════════════════════════════════════════
    //  10. CHECK POOL COMPLETION
    //
    //  L1 = 3 direct children of entryId filled
    //  L2 = 9 grandchildren (children of those 3)
    //  L3 = 27 great-grandchildren (children of those 9)
    //       → L3 complete: bonus + RE-ENTRY same pool + CHAIN to next pool
    //
    //  Propagates check UP to parent entry after each join.
    // ════════════════════════════════════════════════════════════════════

    private function checkPoolCompletion(int $memberId, int $entryId, int $poolId): void
    {
        // Count direct children (L1)
        $dq = $this->db->prepare(
            "SELECT COUNT(*) AS c FROM meddolic_user_pool_entry_details
             WHERE parent1EntryId=? AND poolId=?"
        );
        $dq->execute([$entryId, $poolId]);
        $l1Count = (int)($dq->fetch()['c'] ?? 0);

        // Count grandchildren (L2)
        $gq = $this->db->prepare(
            "SELECT COUNT(*) AS c FROM meddolic_user_pool_entry_details
             WHERE poolId=? AND parent1EntryId IN (
                 SELECT entryId FROM meddolic_user_pool_entry_details
                 WHERE parent1EntryId=? AND poolId=?
             )"
        );
        $gq->execute([$poolId, $entryId, $poolId]);
        $l2Count = (int)($gq->fetch()['c'] ?? 0);

        // Count great-grandchildren (L3)
        $l3q = $this->db->prepare(
            "SELECT COUNT(*) AS c FROM meddolic_user_pool_entry_details
             WHERE poolId=? AND parent1EntryId IN (
                 SELECT entryId FROM meddolic_user_pool_entry_details
                 WHERE poolId=? AND parent1EntryId IN (
                     SELECT entryId FROM meddolic_user_pool_entry_details
                     WHERE parent1EntryId=? AND poolId=?
                 )
             )"
        );
        $l3q->execute([$poolId, $poolId, $entryId, $poolId]);
        $l3Count = (int)($l3q->fetch()['c'] ?? 0);

        // ── L1 BONUS: 3 direct ────────────────────────────────────────
        if ($l1Count >= 3) {
            $this->grantCompletionBonus($memberId, $entryId, $poolId, 1, 3);
        }

        // ── L2 BONUS: 9 grandchildren ─────────────────────────────────
        if ($l2Count >= 9) {
            $this->grantCompletionBonus($memberId, $entryId, $poolId, 2, 9);
        }

        // ── L3 BONUS: 27 great-grandchildren → RE-ENTRY + CHAIN ───────
        if ($l3Count >= 27) {
            // Duplicate guard for L3
            $dup = $this->db->prepare(
                "SELECT id FROM meddolic_user_pool_income
                 WHERE memberId=? AND poolId=? AND poolLevel=3 AND entryId=? LIMIT 1"
            );
            $dup->execute([$memberId, $poolId, $entryId]);
            if (!$dup->fetch()) {
                // Award L3 bonus
                $cfg = $this->fetchPoolIncomeCfg($poolId, 3, 27);
                if ($cfg) {
                    $bonus = (float)$cfg['userIncome'];
                    $admin = round($bonus * $this->adminPct / 100, 5);
                    $net   = round($bonus - $admin, 5);
                    try {
                        $this->db->prepare(
                            "INSERT INTO meddolic_user_pool_income
                               (memberId,poolId,poolLevel,poolIncome,entryId,
                                entryCount,dateTime,releaseStatus)
                             VALUES (?,?,3,?,?,27,NOW(),1)"
                        )->execute([$memberId, $poolId, $net, $entryId]);
                    } catch (Throwable $t) {}
                    $this->addToWallet($memberId, $net, 3, $entryId);
                }

                // Mark this entry COMPLETE
                $this->db->prepare(
                    "UPDATE meddolic_user_pool_entry_details
                     SET poolStatus=0 WHERE entryId=?"
                )->execute([$entryId]);

                // Increment re-entry counter on this entry
                $this->db->prepare(
                    "UPDATE meddolic_user_pool_entry_details
                     SET reEntryCount=reEntryCount+1
                     WHERE entryId=?"
                )->execute([$entryId]);

                // RE-ENTRY: place member back into SAME pool via BFS
                $this->enterGlobalPool($memberId, $poolId, 'REENTRY');

                // CHAIN: try to auto-enter next pool
                $this->tryPoolChain($memberId, $poolId);
            }
        }

        // Propagate completion check up to parent entry
        $pQ = $this->db->prepare(
            "SELECT parent1EntryId, placeholder1Id
             FROM meddolic_user_pool_entry_details WHERE entryId=? LIMIT 1"
        );
        $pQ->execute([$entryId]);
        $pRow = $pQ->fetch();
        if ($pRow && (int)$pRow['parent1EntryId'] > 0 && (int)$pRow['placeholder1Id'] > 0) {
            $this->checkPoolCompletion(
                (int)$pRow['placeholder1Id'],
                (int)$pRow['parent1EntryId'],
                $poolId
            );
        }
    }

    private function grantCompletionBonus(
        int $memberId, int $entryId, int $poolId, int $poolLevel, int $userNeed
    ): void {
        // Duplicate guard
        $dup = $this->db->prepare(
            "SELECT id FROM meddolic_user_pool_income
             WHERE memberId=? AND poolId=? AND poolLevel=? AND entryId=? LIMIT 1"
        );
        $dup->execute([$memberId, $poolId, $poolLevel, $entryId]);
        if ($dup->fetch()) return;

        $cfg = $this->fetchPoolIncomeCfg($poolId, $poolLevel, $userNeed);
        if (!$cfg) return;

        $bonus = (float)$cfg['userIncome'];
        $admin = round($bonus * $this->adminPct / 100, 5);
        $net   = round($bonus - $admin, 5);

        try {
            $this->db->prepare(
                "INSERT INTO meddolic_user_pool_income
                   (memberId,poolId,poolLevel,poolIncome,entryId,
                    entryCount,dateTime,releaseStatus)
                 VALUES (?,?,?,?,?,?,NOW(),1)"
            )->execute([$memberId, $poolId, $poolLevel, $net, $entryId, $userNeed]);
        } catch (Throwable $t) {}

        $this->addToWallet($memberId, $net, 3, $entryId);
    }

    // ════════════════════════════════════════════════════════════════════
    //  11. POOL CHAIN  (on L3 complete → try to enter next pool)
    // ════════════════════════════════════════════════════════════════════

    private function tryPoolChain(int $memberId, int $filledPoolId): void
    {
        if (!isset(self::GLOBAL_CHAIN[$filledPoolId])) return;
        $nextPoolId = self::GLOBAL_CHAIN[$filledPoolId];

        $minPkg = self::GLOBAL_MIN_WORKING[$nextPoolId] ?? 0;
        $u      = getUserById($memberId);
        if ($minPkg && (int)($u['currentPackage'] ?? 0) < $minPkg) return;

        $dirReq = self::GLOBAL_DIRECT_REQ[$nextPoolId] ?? 0;
        if ($dirReq && $this->countActiveDirects($memberId) < $dirReq) return;

        // Already active in next pool?
        $chk = $this->db->prepare(
            "SELECT entryId FROM meddolic_user_pool_entry_details
             WHERE memberId=? AND poolId=? AND poolStatus=1 LIMIT 1"
        );
        $chk->execute([$memberId, $nextPoolId]);
        if ($chk->fetch()) return;

        // Must have MANUALLY purchased the next pool (pools 2+)
        if ($nextPoolId > 1) {
            $paid = $this->db->prepare(
                "SELECT id FROM gm_package_purchases
                 WHERE member_id=? AND package_id=? AND buy_type=3
                   AND trx_hash NOT LIKE 'AUTO_%' AND status=1 LIMIT 1"
            );
            $paid->execute([$memberId, $nextPoolId]);
            if (!$paid->fetch()) return;
        }

        $this->enterGlobalPool($memberId, $nextPoolId, 'CHAIN_' . $filledPoolId);
    }

    // ════════════════════════════════════════════════════════════════════
    //  12. TREE GETTERS
    // ════════════════════════════════════════════════════════════════════

    /**
     * Working plan tree: returns recursive node structure + levelMap
     * Used by MatrixTree.php for display
     */
    public function getWorkingMatrixTree(int $memberId): array
    {
        $stmt = $this->db->prepare("
            SELECT DISTINCT
                ud.member_id    AS child_id,
                ud.user_id,
                ud.topup_flag,
                ud.currentPackage,
                ud.placeholderId AS parent_id,
                ud.legPosition   AS leg_pos
            FROM meddolic_user_set_placeholder_details p
            JOIN meddolic_user_details ud ON p.child_id = ud.member_id
            WHERE p.member_id = ?
            ORDER BY p.level ASC, ud.legPosition ASC
        ");
        $stmt->execute([$memberId]);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        if (empty($rows)) {
            return ['tree' => [], 'nodes' => [], 'total' => 0, 'levelMap' => []];
        }

        // Build flat node map
        $nodes = [];
        foreach ($rows as $row) {
            $cid = (int)$row['child_id'];
            $nodes[$cid] = [
                'member_id'      => $cid,
                'user_id'        => $row['user_id'],
                'topup_flag'     => (int)$row['topup_flag'],
                'currentPackage' => (int)$row['currentPackage'],
                'parent_id'      => (int)$row['parent_id'],
                'leg_pos'        => (int)$row['leg_pos'],
                'level'          => 0,
            ];
        }

        // BFS to assign levels
        $queue    = [[$memberId, 0]];
        $qi       = 0;
        $levelMap = [];
        $visited  = [];
        while (isset($queue[$qi])) {
            [$pid, $plvl] = $queue[$qi++];
            foreach ($nodes as $nid => $n) {
                if ((int)$n['parent_id'] === $pid && !isset($visited[$nid])) {
                    $visited[$nid]          = true;
                    $nodes[$nid]['level']   = $plvl + 1;
                    $levelMap[$plvl + 1][]  = $nid;
                    $queue[]                = [$nid, $plvl + 1];
                }
            }
        }

        // Root direct children sorted by leg_pos
        $rootChildren = [];
        foreach ($nodes as $nid => $n) {
            if ((int)$n['parent_id'] === $memberId) {
                $rootChildren[$n['leg_pos']] = $n;
            }
        }
        ksort($rootChildren);

        return [
            'tree'     => $rootChildren,
            'nodes'    => $nodes,
            'total'    => count($nodes),
            'levelMap' => $levelMap,
        ];
    }

    /**
     * Global pool tree: returns entry + L1/L2 nodes + re-entry count
     * Used by MatrixTree.php for pool cards display
     */
    public function getGlobalPoolTree(int $memberId, int $poolId): array
    {
        $eQ = $this->db->prepare(
            "SELECT * FROM meddolic_user_pool_entry_details
             WHERE memberId=? AND poolId=? AND poolStatus=1
             ORDER BY entryId DESC LIMIT 1"
        );
        $eQ->execute([$memberId, $poolId]);
        $entry = $eQ->fetch();
        if (!$entry) return ['entry' => null, 'levels' => [], 're_entries' => 0];
        $eid = (int)$entry['entryId'];

        // L1 (direct children)
        $l1q = $this->db->prepare(
            "SELECT ped.memberId, ped.entryId, ped.leg1Position AS legPos, ud.user_id
             FROM meddolic_user_pool_entry_details ped
             LEFT JOIN meddolic_user_details ud ON ud.member_id=ped.memberId
             WHERE ped.parent1EntryId=? AND ped.poolId=?
             ORDER BY ped.leg1Position ASC"
        );
        $l1q->execute([$eid, $poolId]);
        $l1 = $l1q->fetchAll();

        // L2 (grandchildren)
        $l2 = [];
        foreach ($l1 as $n) {
            $l2q = $this->db->prepare(
                "SELECT ped.memberId, ped.entryId, ped.leg1Position AS legPos, ud.user_id
                 FROM meddolic_user_pool_entry_details ped
                 LEFT JOIN meddolic_user_details ud ON ud.member_id=ped.memberId
                 WHERE ped.parent1EntryId=? AND ped.poolId=?
                 ORDER BY ped.leg1Position ASC"
            );
            $l2q->execute([$n['entryId'], $poolId]);
            $l2 = array_merge($l2, $l2q->fetchAll());
        }

        $rQ = $this->db->prepare(
            "SELECT COALESCE(SUM(reEntryCount),0) AS t
             FROM meddolic_user_pool_entry_details WHERE memberId=? AND poolId=?"
        );
        $rQ->execute([$memberId, $poolId]);

        return [
            'entry'      => $entry,
            're_entries' => (int)($rQ->fetch()['t'] ?? 0),
            'levels'     => [1 => $l1, 2 => $l2],
        ];
    }

    // Alias used by MatrixTree.php
    public function getGlobalMatrixTree(int $memberId, int $poolId): array
    {
        return $this->getGlobalPoolTree($memberId, $poolId);
    }

    // ════════════════════════════════════════════════════════════════════
    //  13. INCOME PREVIEW (for UI tables)
    // ════════════════════════════════════════════════════════════════════

    public function calculateIncomePreview(int $pkgId, float $pkgPrice): array
    {
        $rules  = self::INCOME_RULES[$pkgId] ?? [50.0, 50.0, 1, false, 0];
        $admin  = ($pkgId <= 2) ? 0.0 : $this->adminPct;
        $dGross = $pkgPrice * $rules[0] / 100;
        $uGross = $pkgPrice * $rules[1] / 100;
        $gId    = self::WORKING_TRIGGERS_GLOBAL[$pkgId] ?? null;
        return [
            'direct_pct'      => $rules[0],
            'upline_pct'      => $rules[1],
            'has_level_dist'  => $rules[3],
            'admin_pct'       => $admin,
            'direct_net'      => round($dGross * (1 - $admin / 100), 2),
            'upline_net'      => round($uGross * (1 - $admin / 100), 2),
            'total_admin'     => round(($dGross + $uGross) * $admin / 100, 2),
            'triggers_pool'   => $gId,
            'triggers_matrix' => $gId ? $this->fetchMatrixPkg($gId) : null,
        ];
    }

    // ════════════════════════════════════════════════════════════════════
    //  14. PRIVATE HELPERS
    // ════════════════════════════════════════════════════════════════════

    /** BFS: find first pool entry node with < 3 children in this pool */
    private function bfsFindParent(int $poolId): array
    {
        $allQ = $this->db->prepare(
            "SELECT entryId, memberId, poolLevel
             FROM meddolic_user_pool_entry_details
             WHERE poolId=? AND poolStatus=1
             ORDER BY poolLevel ASC, entryId ASC"
        );
        $allQ->execute([$poolId]);
        foreach ($allQ->fetchAll() as $row) {
            $cq = $this->db->prepare(
                "SELECT COUNT(*) AS c FROM meddolic_user_pool_entry_details
                 WHERE parent1EntryId=? AND poolId=?"
            );
            $cq->execute([$row['entryId'], $poolId]);
            if ((int)($cq->fetch()['c'] ?? 0) < 3) {
                return [(int)$row['entryId'], (int)$row['memberId'], (int)$row['poolLevel']];
            }
        }
        return [null, null, 0];
    }

    /**
     * Get Nth ancestor via sponsor chain.
     * N=1 → direct sponsor (A1)
     * N=2 → sponsor's sponsor (A2)
     * N=3 → A3, etc.
     */
    private function getNthAncestor(int $startId, int $n): int
    {
        $id = $startId;
        for ($i = 0; $i < $n && $id > 0; $i++) {
            $q = $this->db->prepare(
                "SELECT sponser_id FROM meddolic_user_details WHERE member_id=? LIMIT 1"
            );
            $q->execute([$id]);
            $id = (int)($q->fetchColumn() ?: 0);
        }
        return $id;
    }

    // Alias kept for backward compat
    private function getNthAncestorFromBuyer(int $buyerId, int $n): int
    {
        return $this->getNthAncestor($buyerId, $n);
    }

    private function countActiveDirects(int $memberId): int
    {
        $q = $this->db->prepare(
            "SELECT COUNT(*) AS c FROM meddolic_user_child_ids ci
             JOIN meddolic_user_details ud ON ud.member_id=ci.child_id
             WHERE ci.member_id=? AND ci.level=1 AND ud.topup_flag=1"
        );
        $q->execute([$memberId]);
        return (int)($q->fetch()['c'] ?? 0);
    }

    private function countTeamRange(int $memberId, int $min, int $max): int
    {
        $q = $this->db->prepare(
            "SELECT COUNT(*) AS c FROM meddolic_user_child_ids
             WHERE member_id=? AND level BETWEEN ? AND ? AND topup_status=1"
        );
        $q->execute([$memberId, $min, $max]);
        return (int)($q->fetch()['c'] ?? 0);
    }

    private function addToWallet(int $memberId, float $amount, int $type, int $trnId): void
    {
        if ($memberId <= 0 || $amount <= 0) return;
        $this->db->prepare(
            "UPDATE meddolic_user_details
             SET wallet=COALESCE(wallet,0)+? WHERE member_id=?"
        )->execute([$amount, $memberId]);
        try {
            $this->db->prepare(
                "INSERT INTO meddolic_user_wallet_statement
                   (member_id,wallet_statement_id,deb_cr,amount,date_time,trn_id)
                 VALUES (?,?,2,?,NOW(),?)"
            )->execute([$memberId, $type, $amount, $trnId]);
        } catch (Throwable $t) {}
    }

    private function fetchPoolPkg(int $id): ?array
    {
        static $c = [];
        if (!isset($c[$id])) {
            $q = $this->db->prepare(
                "SELECT * FROM meddolic_config_pool_list WHERE id=? LIMIT 1"
            );
            $q->execute([$id]);
            $c[$id] = $q->fetch() ?: null;
        }
        return $c[$id];
    }

    private function fetchMatrixPkg(int $id): ?array
    {
        static $c = [];
        if (!isset($c[$id])) {
            $q = $this->db->prepare(
                "SELECT * FROM meddolic_config_matrix_package WHERE matrixId=? LIMIT 1"
            );
            $q->execute([$id]);
            $c[$id] = $q->fetch() ?: null;
        }
        return $c[$id];
    }

    private function fetchPoolIncomeCfg(int $poolId, int $poolLevel, int $userNeed): ?array
    {
        // Support both column name variants (matrixId and poolId)
        $q = $this->db->prepare(
            "SELECT * FROM meddolic_config_pool_income
             WHERE (matrixId=? OR poolId=?) AND poolLevel=? AND userNeed=?
               AND poolStatus=1 LIMIT 1"
        );
        $q->execute([$poolId, $poolId, $poolLevel, $userNeed]);
        return $q->fetch() ?: null;
    }
}