<?php
/* ============================================================================
   WBMCZ — Product share engine
   Handles the percentages configured in Admin > Product Management:
     - subscription_discount_pct : buyer discount (subscribed users only)
     - seller_share_pct          : credited to the seller's wallet
     - referral_share_pct        : credited to the buyer's referrer (level 1)
     - staff_share_pct           : recorded as payable (no staff link in DB yet)
     - company_share_pct         : recorded as company profit

   Every payout is written to `product_share_ledger` so admin can audit it,
   and the money itself moves through includes/wallet.php:
     - seller share  → MAIN wallet    (real income, withdrawable)
     - referral      → VIRTUAL wallet (spendable, not withdrawable)
   ============================================================================ */

require_once __DIR__ . '/wallet.php';
@require_once __DIR__ . '/kyc_fields.php';

/* ---- Make sure every column/table this engine needs exists (self-healing) ---- */
function shares_ensure_schema($db) {
    $conn = $db->getConnection();

    // Percentage columns on both service tables
    $cols = ['seller_share_pct','subscription_discount_pct','referral_share_pct','staff_share_pct','company_share_pct'];
    $amts = ['seller_share_amt','subscription_discount_amt','referral_share_amt','staff_share_amt','company_share_amt'];
    foreach (['seller_services','agency_services'] as $t) {
        foreach ($cols as $c) {
            try {
                $r = $conn->query("SHOW COLUMNS FROM $t LIKE '$c'");
                if ($r && $r->num_rows === 0) { $conn->query("ALTER TABLE $t ADD COLUMN $c DECIMAL(5,2) NOT NULL DEFAULT 0.00"); }
            } catch (\Throwable $e) { /* ignore */ }
        }
        // Fixed-rupee share columns + the mode switch
        foreach ($amts as $c) {
            try {
                $r = $conn->query("SHOW COLUMNS FROM $t LIKE '$c'");
                if ($r && $r->num_rows === 0) { $conn->query("ALTER TABLE $t ADD COLUMN $c DECIMAL(10,2) NOT NULL DEFAULT 0.00"); }
            } catch (\Throwable $e) { /* ignore */ }
        }
        try {
            $r = $conn->query("SHOW COLUMNS FROM $t LIKE 'share_mode'");
            if ($r && $r->num_rows === 0) { $conn->query("ALTER TABLE $t ADD COLUMN share_mode VARCHAR(10) NOT NULL DEFAULT 'percent'"); }
        } catch (\Throwable $e) { /* ignore */ }
        // Customer Data Recruitment columns
        foreach (['required_kyc' => "VARCHAR(255) NULL", 'area_mode' => "VARCHAR(20) NOT NULL DEFAULT 'all'"] as $c => $def) {
            try {
                $r = $conn->query("SHOW COLUMNS FROM $t LIKE '$c'");
                if ($r && $r->num_rows === 0) { $conn->query("ALTER TABLE $t ADD COLUMN $c $def"); }
            } catch (\Throwable $e) { /* ignore */ }
        }
    }

    // Serviceable pincodes
    try {
        $conn->query("CREATE TABLE IF NOT EXISTS product_areas (
            id INT AUTO_INCREMENT PRIMARY KEY,
            service_id INT NOT NULL,
            service_type VARCHAR(20) NOT NULL DEFAULT 'seller',
            pincode VARCHAR(20) NOT NULL,
            created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_area (service_id, service_type, pincode)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    } catch (\Throwable $e) { /* ignore */ }

    /* A product can now be limited by STATE as well as by pincode. A row
       carries either a pincode or a state — never both. */
    try {
        $r = $conn->query("SHOW COLUMNS FROM product_areas LIKE 'state'");
        if ($r && $r->num_rows === 0) { $conn->query("ALTER TABLE product_areas ADD COLUMN `state` VARCHAR(100) NULL"); }
    } catch (\Throwable $e) { /* ignore */ }
    try {
        $r = $conn->query("SHOW INDEX FROM product_areas WHERE Key_name = 'idx_pa_state'");
        if ($r && $r->num_rows === 0) { $conn->query("ALTER TABLE product_areas ADD INDEX `idx_pa_state` (`service_id`, `service_type`, `state`)"); }
    } catch (\Throwable $e) { /* ignore */ }
    try {
        $r = $conn->query("SHOW COLUMNS FROM product_areas LIKE 'pincode'");
        if ($r) { $conn->query("ALTER TABLE product_areas MODIFY COLUMN `pincode` VARCHAR(20) NULL"); }
    } catch (\Throwable $e) { /* ignore */ }

    try {
        $db->getConnection()->query("
            CREATE TABLE IF NOT EXISTS product_share_ledger (
                id INT AUTO_INCREMENT PRIMARY KEY,
                order_id INT NULL,
                service_id INT NOT NULL,
                service_type VARCHAR(20) NOT NULL DEFAULT 'seller',
                service_title VARCHAR(255) NULL,
                buyer_id INT NULL,
                share_type VARCHAR(30) NOT NULL,      -- seller | referral | staff | company | subscription_discount
                beneficiary_id INT NULL,              -- user credited (NULL = unassigned / company)
                beneficiary_name VARCHAR(120) NULL,
                percent DECIMAL(5,2) NOT NULL DEFAULT 0.00,
                amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
                base_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
                paid TINYINT(1) NOT NULL DEFAULT 0,   -- 1 = credited to a wallet
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
        ");
    } catch (\Throwable $e) { /* ignore */ }
}

/* ---- Load a service row (seller_services first, then agency_services) ---- */
function shares_get_service($db, $service_id) {
    $r = $db->select("SELECT *, 'seller' AS __type FROM seller_services WHERE id = ?", [$service_id]);
    if (!empty($r)) return $r[0];
    try {
        $r = $db->select("SELECT *, 'agency' AS __type FROM agency_services WHERE id = ?", [$service_id]);
        if (!empty($r)) return $r[0];
    } catch (\Throwable $e) {}
    return null;
}

/**
 * Which service_categories row does this service belong to?
 * Seller products carry category_id directly; agency products reach it through
 * their sub-category. Returns null when it cannot be resolved.
 *
 * Used to stamp orders.category_id so Order Management can filter by category.
 */
function shares_service_category($db, $service) {
    if (empty($service)) return null;
    if (!empty($service['category_id'])) return (int)$service['category_id'];
    if (!empty($service['sub_category_id'])) {
        try {
            $r = $db->select("SELECT category_id FROM agency_sub_categories WHERE id = ?", [(int)$service['sub_category_id']]);
            if (!empty($r) && !empty($r[0]['category_id'])) return (int)$r[0]['category_id'];
        } catch (\Throwable $e) {}
    }
    return null;
}

/* ---- Is this user an active subscriber? ---- */
function shares_is_subscribed($db, $user_id) {
    try {
        $u = $db->select("SELECT is_subscribed FROM users WHERE id = ?", [$user_id]);
        return !empty($u) && (int)$u[0]['is_subscribed'] === 1;
    } catch (\Throwable $e) { return false; }
}

/**
 * Price the buyer actually pays.
 * Returns: ['base','discount_pct','discount','final','is_subscribed']
 */
function shares_price_for_user($db, $service, $user_id, $is_sub = null) {
    $base = (float)($service['discount_price'] > 0 ? $service['discount_price'] : $service['price']);
    // $is_sub can be passed in to avoid one query per product in a listing loop
    $sub  = ($is_sub === null) ? shares_is_subscribed($db, $user_id) : (bool)$is_sub;

    // Works in both % and fixed-₹ mode
    $discount = $sub ? shares_amount_for($service, 'subscription', $base) : 0.00;
    $pct      = shares_pct_for($service, 'subscription', $base);
    $final    = max(0, round($base - $discount, 2));

    return [
        'base'          => round($base, 2),
        'discount_pct'  => $pct,
        'discount'      => $discount,
        'final'         => $final,
        'is_subscribed' => $sub,
    ];
}

/* ============================================================================
   SHARE MODE — each product is either 'percent' or 'amount'

   percent : seller_share_pct etc. hold a %
   amount  : seller_share_amt etc. hold rupees, and the % columns are kept in
             sync so every old report keeps working.
   Company share is always the remainder in both modes.
   ============================================================================ */

function shares_mode($service) {
    return (($service['share_mode'] ?? 'percent') === 'amount') ? 'amount' : 'percent';
}

/* The four editable shares (company is derived) */
function shares_editable_keys() {
    return ['seller', 'subscription', 'referral', 'staff'];
}

function shares_col($key, $kind = 'pct') {
    $map = [
        'seller'       => ['pct' => 'seller_share_pct',          'amt' => 'seller_share_amt'],
        'subscription' => ['pct' => 'subscription_discount_pct', 'amt' => 'subscription_discount_amt'],
        'referral'     => ['pct' => 'referral_share_pct',        'amt' => 'referral_share_amt'],
        'staff'        => ['pct' => 'staff_share_pct',           'amt' => 'staff_share_amt'],
        'company'      => ['pct' => 'company_share_pct',         'amt' => 'company_share_amt'],
    ];
    return $map[$key][$kind] ?? '';
}

/** Rupee value of one share for a given base price. */
function shares_amount_for($service, $key, $base) {
    $base = (float)$base;

    if ($key === 'company') {
        $used = 0.0;
        foreach (shares_editable_keys() as $k) { $used += shares_amount_for($service, $k, $base); }
        return round(max(0, $base - $used), 2);
    }

    if (shares_mode($service) === 'amount') {
        $amt = (float)($service[shares_col($key, 'amt')] ?? 0);
        return round(min(max(0, $amt), $base), 2);       // never more than the price
    }

    $pct = (float)($service[shares_col($key, 'pct')] ?? 0);
    return round(($base * $pct) / 100, 2);
}

/** Effective percentage of one share, whichever mode the product uses. */
function shares_pct_for($service, $key, $base) {
    $base = (float)$base;
    if ($base <= 0) return 0.00;
    return round((shares_amount_for($service, $key, $base) / $base) * 100, 2);
}

/**
 * What the company actually keeps out of money that has really been taken.
 *
 * shares_amount_for('company') answers a different question — it slices the
 * REGULAR price into all five parts, subscriber discount included, which is
 * what the admin breakdown table is meant to show. But once a sale happens,
 * the discount has already come off the price the buyer paid (or was never
 * given at all, because they are not a member), so deducting it a second time
 * loses that money from the profit figure. On the amount paid, the company's
 * share is simply whatever the seller, the referrer and the staff do not take.
 */
function shares_company_earned($service, $paid) {
    $paid = (float)$paid;
    if ($paid <= 0) return 0.00;
    $used = 0.0;
    foreach (['seller', 'referral', 'staff'] as $k) { $used += shares_amount_for($service, $k, $paid); }
    return round(max(0, $paid - $used), 2);
}

/** All five shares at once — used by the admin table and the edit modal. */
function shares_breakdown($service, $base) {
    $out = []; $used = 0.0;
    foreach (shares_editable_keys() as $k) {
        $a = shares_amount_for($service, $k, $base);
        $used += $a;
        $out[$k] = ['amount' => $a, 'percent' => shares_pct_for($service, $k, $base)];
    }
    $co = round(max(0, $base - $used), 2);
    $out['company'] = ['amount' => $co, 'percent' => $base > 0 ? round(($co / $base) * 100, 2) : 0.0];
    $out['__used']  = round($used, 2);
    $out['__over']  = ($used > $base + 0.001);
    return $out;
}

/* ---- internal: write one ledger row ---- */
function shares__ledger($db, $row) {
    try {
        $db->insert("INSERT INTO product_share_ledger
            (order_id, service_id, service_type, service_title, buyer_id, share_type, beneficiary_id, beneficiary_name, percent, amount, base_amount, paid)
            VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [
            $row['order_id'], $row['service_id'], $row['service_type'], $row['service_title'], $row['buyer_id'],
            $row['share_type'], $row['beneficiary_id'], $row['beneficiary_name'],
            $row['percent'], $row['amount'], $row['base_amount'], $row['paid'],
        ]);
    } catch (\Throwable $e) { /* ignore */ }
}

/**
 * Distribute all shares for a completed purchase.
 * $paid = what the buyer actually paid (after any subscription discount).
 */
function shares_distribute($db, $service, $buyer_id, $order_id, $paid, $discount_info = null) {
    shares_ensure_schema($db);

    $sid   = (int)$service['id'];
    $stype = $service['__type'] ?? 'seller';
    $title = $service['title'] ?? '';
    $base  = (float)$paid;                 // shares are calculated on what was paid
    if ($base <= 0) return;

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

    $base_row = [
        'order_id' => $order_id, 'service_id' => $sid, 'service_type' => $stype,
        'service_title' => $title, 'buyer_id' => $buyer_id, 'base_amount' => $base,
    ];

    // Record the subscription discount the buyer saved — REPORT ONLY, no money moves
    if (!empty($discount_info) && $discount_info['discount'] > 0) {
        shares__ledger($db, $base_row + [
            'share_type' => 'subscription_discount', 'beneficiary_id' => $buyer_id,
            'beneficiary_name' => $buyer_name, 'percent' => $discount_info['discount_pct'],
            'amount' => $discount_info['discount'], 'paid' => 1,
        ]);
        if (function_exists('recordOwnDiscount')) {
            recordOwnDiscount($db, $buyer_id, $discount_info['discount'],
                'Subscriber discount (' . rtrim(rtrim(number_format((float)$discount_info['discount_pct'], 2), '0'), '.') . '%) on ' . $title, [
                'reference_type' => 'order',
                'reference_id'   => $order_id,
            ]);
        }
    }

    /* ---- 1. Seller share → HELD until the order is marked Completed ----
       Nothing is credited now. The amount is recorded in product_share_ledger
       with paid = 0 so it shows in reports as "pending", and shares_release_order()
       moves it into the seller's main wallet once the order completes. */
    $seller_amt = shares_amount_for($service, 'seller', $base);
    $seller_id  = (int)($service['seller_id'] ?? 0);
    if ($seller_amt > 0 && $seller_id > 0) {
        $sn = $db->select("SELECT name FROM users WHERE id = ?", [$seller_id]);
        shares__ledger($db, $base_row + [
            'share_type' => 'seller', 'beneficiary_id' => $seller_id,
            'beneficiary_name' => !empty($sn) ? $sn[0]['name'] : '',
            'percent' => shares_pct_for($service, 'seller', $base),
            'amount' => $seller_amt, 'paid' => 0,     // 0 = pending, waiting for completion
        ]);
    }

    /* ---- 2. Referral share → referrer's MAIN wallet (withdrawable) ----
       When somebody I referred buys a service, my cut lands straight in the
       main wallet, exactly like a seller earning, so it can be withdrawn.
       (Subscription referral commission still goes to the virtual wallet —
        see distributeReferralCommission() in includes/functions.php.)        */
    $ref_pct = shares_pct_for($service, 'referral', $base);
    if ($referrer_id > 0) {
        $amt = shares_amount_for($service, 'referral', $base);
        if ($amt > 0) {
            // Service / product sale referral — its own source in the Earning Report
            wallet_credit($db, $referrer_id, WALLET_MAIN, $amt, [
                'category'        => 'ref_sale',
                'description'     => "Referral on sale ($ref_pct%) from $buyer_name — $title",
                'reference_type'  => 'order',
                'reference_id'    => $order_id,
                'counterparty_id' => $buyer_id,
            ]);
            $rn = $db->select("SELECT name FROM users WHERE id = ?", [$referrer_id]);
            shares__ledger($db, $base_row + [
                'share_type' => 'referral', 'beneficiary_id' => $referrer_id,
                'beneficiary_name' => !empty($rn) ? $rn[0]['name'] : '', 'percent' => $ref_pct,
                'amount' => $amt, 'paid' => 1,
            ]);

            /* tell the referrer that money has arrived */
            try {
                $db->insert("INSERT INTO notifications (user_id, title, message, type, link, is_read)
                             VALUES (?, ?, ?, 'earning', 'earning_report.php', 0)", [
                    $referrer_id, 'Referral earning credited',
                    '₹' . number_format($amt, 2) . ' has been added to your main wallet — '
                    . $buyer_name . ' purchased ' . $title . '.',
                ]);
            } catch (\Throwable $e) { /* notifications table may be older — ignore */ }
        }
    }

    /* ---- 3. Staff share → recorded as payable (no staff↔product link yet) ---- */
    $staff_pct = shares_pct_for($service, 'staff', $base);
    {
        $amt = shares_amount_for($service, 'staff', $base);
        if ($amt > 0) {
            shares__ledger($db, $base_row + [
                'share_type' => 'staff', 'beneficiary_id' => null, 'beneficiary_name' => 'Unassigned staff',
                'percent' => $staff_pct, 'amount' => $amt, 'paid' => 0,
            ]);
        }
    }

    /* ---- 4. Company share → profit record ----
       Whatever is left of the money actually taken. See shares_company_earned(). */
    {
        $amt    = shares_company_earned($service, $base);
        $co_pct = $base > 0 ? round(($amt / $base) * 100, 2) : 0.00;
        if ($amt > 0) {
            shares__ledger($db, $base_row + [
                'share_type' => 'company', 'beneficiary_id' => null, 'beneficiary_name' => 'Company',
                'percent' => $co_pct, 'amount' => $amt, 'paid' => 0,
            ]);
        }
    }
}

/* ============================================================================
   Company share is never entered by hand — it is whatever is left over.
   company = 100 - (seller + subscription discount + referral + staff)
   ============================================================================ */
function shares_company_pct($seller, $subscription, $referral, $staff) {
    $used = (float)$seller + (float)$subscription + (float)$referral + (float)$staff;
    return round(max(0, 100 - $used), 2);
}

/* ============================================================================
   CUSTOMER DATA RECRUITMENT — purchase gates
   ============================================================================ */

/* Required KYC section keys for a product, e.g. ['personal','address']
   ---------------------------------------------------------------------------
   HARD RULE (not admin-editable): every service in "Agency & Software"
   (category 5) requires full KYC — Personal + Address + Bank — and, for
   sellers, Business Details on top. Sellers cannot use any Agency & Software
   service until their KYC is complete and approved.
   --------------------------------------------------------------------------- */
function product_required_kyc($service, $user_type = null) {
    $raw  = trim((string)($service['required_kyc'] ?? ''));
    $need = ($raw === '') ? [] : array_values(array_filter(array_map('trim', explode(',', $raw))));

    if ((int)($service['category_id'] ?? 0) === 5) {
        $need = array_merge($need, ['personal', 'address', 'bank']);
        if ($user_type === 'seller') { $need[] = 'business'; }
    }
    return array_values(array_unique($need));
}

/* Human labels for the required keys — a single field, a whole section or
   one of the admin's own questions all come back readable. */
function product_kyc_labels($keys) {
    global $db;
    $map = ['personal' => 'Personal Details', 'address' => 'Address Details', 'bank' => 'Bank Details', 'business' => 'Business Details'];
    $out = [];
    foreach ((array)$keys as $k) {
        if (isset($map[$k])) { $out[] = $map[$k]; continue; }
        $out[] = function_exists('kycf_label') ? kycf_label($db, $k) : ucfirst((string)$k);
    }
    return $out;
}

/* The same list grouped under its section heading, for the popup checklist */
function product_kyc_grouped($keys) {
    global $db;
    if (!function_exists('kycf_group_labels')) { return []; }
    $out = [];
    foreach (kycf_group_labels($db, $keys) as $group => $items) {
        $out[] = ['group' => $group, 'items' => array_values($items)];
    }
    return $out;
}

/* Which KYC columns belong to which section (mirrors includes/kyc.php) */
function product_kyc_sections() {
    return [
        'personal' => ['full_name','gender','dob','father_name','aadhar_number','pancard_number'],
        'address'  => ['village','post_office','block_city','district','state','pincode'],
        'bank'     => ['bank_name','branch_name','account_number','ifsc_code'],
        'business' => ['business_name','business_phone','biz_district','biz_state','biz_pincode'],
    ];
}

/* ---- Per-request cache: the buyer's KYC row is read once, not once per product ---- */
function product_kyc_row($db, $user_id) {
    static $cache = [];
    $k = (int)$user_id;
    if (array_key_exists($k, $cache)) return $cache[$k];
    $row = [];
    try {
        $r = $db->select("SELECT * FROM kyc_details WHERE user_id = ?", [$k]);
        $row = !empty($r) ? $r[0] : [];
    } catch (\Throwable $e) {}
    return $cache[$k] = $row;
}

/* ---- Per-request cache: the buyer's registration pincode ---- */
function product_user_pincode($db, $user_id) {
    static $cache = [];
    $k = (int)$user_id;
    if (array_key_exists($k, $cache)) return $cache[$k];

    $pin = '';
    try {
        $u = $db->select("SELECT pincode FROM users WHERE id = ?", [$k]);
        if (!empty($u)) $pin = preg_replace('/\D/', '', (string)($u[0]['pincode'] ?? ''));
    } catch (\Throwable $e) {}

    // Fallback to the KYC address pincode if the account has none
    if ($pin === '') {
        $kyc = product_kyc_row($db, $k);
        $pin = preg_replace('/\D/', '', (string)($kyc['pincode'] ?? ''));
    }
    return $cache[$k] = $pin;
}

/**
 * Per-request cache of the area check for one product.
 * One indexed query returns both "is this product restricted?" and
 * "is the buyer's pincode in the list?" — so a bulk upload of thousands of
 * pincodes never gets pulled into PHP memory.
 */
function product_area_check($db, $service_id, $service_type, $pin, $state = '') {
    static $cache = [];
    $k = $service_type . ':' . (int)$service_id . ':' . $pin . ':' . strtolower($state);
    if (array_key_exists($k, $cache)) return $cache[$k];

    $out = ['total' => 0, 'hit' => 0, 'by' => ''];
    try {
        $r = $db->select(
            "SELECT COUNT(*) AS total,
                    SUM(pincode IS NOT NULL AND pincode <> '' AND pincode = ?) AS hit_pin,
                    SUM(state IS NOT NULL AND state <> '' AND LOWER(state) = LOWER(?)) AS hit_state
             FROM product_areas WHERE service_id = ? AND service_type = ?",
            [$pin, $state, (int)$service_id, $service_type]
        );
        if (!empty($r)) {
            $hp = (int)$r[0]['hit_pin'];
            $hs = (int)$r[0]['hit_state'];
            $out = [
                'total' => (int)$r[0]['total'],
                'hit'   => $hp + $hs,
                'by'    => $hp > 0 ? 'pincode' : ($hs > 0 ? 'state' : ''),
            ];
        }
    } catch (\Throwable $e) {
        /* an older database with no `state` column — fall back to pincode only */
        try {
            $r = $db->select("SELECT COUNT(*) AS total, SUM(pincode = ?) AS hit
                              FROM product_areas WHERE service_id = ? AND service_type = ?",
                             [$pin, (int)$service_id, $service_type]);
            if (!empty($r)) { $out = ['total' => (int)$r[0]['total'], 'hit' => (int)$r[0]['hit'], 'by' => 'pincode']; }
        } catch (\Throwable $e2) {}
    }
    return $cache[$k] = $out;
}

/* The buyer's state, from the account first and the KYC address second */
function product_user_state($db, $user_id) {
    static $cache = [];
    $k = (int)$user_id;
    if (array_key_exists($k, $cache)) return $cache[$k];

    $st = '';
    try {
        $u = $db->select("SELECT state FROM users WHERE id = ?", [$k]);
        if (!empty($u)) $st = trim((string)($u[0]['state'] ?? ''));
    } catch (\Throwable $e) {}
    if ($st === '') {
        $kyc = product_kyc_row($db, $k);
        $st  = trim((string)($kyc['state'] ?? ''));
    }
    return $cache[$k] = $st;
}

/**
 * Is the buyer allowed to purchase, KYC-wise?
 * Every required section must be filled AND the KYC must be approved.
 */
function product_kyc_gate($db, $service, $user_id) {
    $__ut = 'customer';
    try {
        $__u = $db->select("SELECT user_type FROM users WHERE id = ?", [(int)$user_id]);
        if (!empty($__u)) $__ut = $__u[0]['user_type'];
    } catch (\Throwable $e) {}

    $need_raw = product_required_kyc($service, $__ut);
    if (empty($need_raw)) {
        return ['ok' => true, 'missing' => [], 'filled' => [], 'required' => [], 'status' => 'not_required'];
    }

    /* Whole sections expand into their individual boxes, so the popup can say
       exactly which line is still blank instead of "Address Details". */
    $need = function_exists('kycf_expand') ? kycf_expand($db, $need_raw) : $need_raw;

    $row = product_kyc_row($db, $user_id);

    if (function_exists('kycf_missing')) {
        $missing = kycf_missing($db, $user_id, $need, $row);
    } else {
        $sections = product_kyc_sections();
        $missing  = [];
        foreach ($need_raw as $key) {
            $done = false;
            foreach (($sections[$key] ?? []) as $f) { if (!empty($row[$f])) { $done = true; break; } }
            if (!$done) $missing[] = $key;
        }
        $need = $need_raw;
    }
    $filled = array_values(array_diff($need, $missing));

    $status = $row['status'] ?? 'not_submitted';

    /* Approval is no longer a purchase gate. Whatever a product still needs is
       asked for on the checkout page, and the order goes through the moment it
       is filled in and paid for — the office verifies the KYC afterwards. */
    $ok     = empty($missing);

    // Why it's blocked — drives the popup message
    $reason = 'ok';
    if (!empty($missing))                 $reason = 'incomplete';   // half filled / not filled
    elseif ($status === 'rejected')       $reason = 'rejected';
    elseif ($status !== 'approved')       $reason = 'awaiting';     // filled, waiting for admin approval

    return [
        'ok'      => $ok,
        'missing' => $missing,
        'filled'  => $filled,
        'required'=> $need,
        'status'  => $status,
        'reason'  => $reason,
        'remark'  => (string)($row['admin_remark'] ?? ''),
    ];
}

/**
 * Is the product available in the buyer's area?
 * If the product has no pincodes configured it is available everywhere.
 */
function product_area_gate($db, $service, $user_id) {
    $sid   = (int)$service['id'];
    $stype = $service['__type'] ?? 'seller';
    $pin   = product_user_pincode($db, $user_id);
    $state = product_user_state($db, $user_id);

    $chk = product_area_check($db, $sid, $stype, $pin, $state);

    // Nothing configured for this product = available everywhere
    if ($chk['total'] === 0) {
        return ['ok' => true, 'restricted' => false, 'pincode' => $pin, 'state' => $state, 'count' => 0, 'by' => ''];
    }

    return [
        'ok'         => (($pin !== '' || $state !== '') && $chk['hit'] > 0),
        'restricted' => true,
        'pincode'    => $pin,
        'state'      => $state,
        'count'      => $chk['total'],
        'by'         => $chk['by'],
    ];
}

/* Combined gate used by the storefront and the checkout handler */
function product_purchase_gate($db, $service, $user_id) {
    $kyc  = product_kyc_gate($db, $service, $user_id);
    $area = product_area_gate($db, $service, $user_id);

    /* 'collect' = the only thing standing in the way is KYC the buyer has not
       given us yet, and it can be taken on the checkout page. The storefront
       shows an ordinary Buy Now for these and sends them to checkout.php. */
    return [
        'ok'      => ($kyc['ok'] && $area['ok']),
        'collect' => (!$kyc['ok'] && $area['ok']),
        'kyc'     => $kyc,
        'area'    => $area,
    ];
}

/**
 * Everything the front-end popup needs, as a plain array ready for json_encode().
 * Area is checked first — no point asking for KYC on a product they can't buy anyway.
 */
function product_gate_payload($db, $service, $user_id, $gate = null) {
    $g = $gate ?: product_purchase_gate($db, $service, $user_id);
    $kyc = $g['kyc']; $area = $g['area'];

    if (!$area['ok']) {
        $block = 'area';
    } elseif (!$kyc['ok']) {
        $block = 'kyc';
    } else {
        $block = '';
    }

    return [
        'ok'        => (bool)$g['ok'],
        'collect'   => (bool)($g['collect'] ?? false),
        'block'     => $block,
        'title'     => (string)($service['title'] ?? ''),
        'area_ok'   => (bool)$area['ok'],
        'pincode'   => (string)$area['pincode'],
        'area_count'=> (int)($area['count'] ?? 0),
        'state'     => (string)($area['state'] ?? ''),
        'kyc_ok'    => (bool)$kyc['ok'],
        'kyc_stat'  => (string)$kyc['status'],
        'reason'    => (string)($kyc['reason'] ?? ''),
        'remark'    => (string)($kyc['remark'] ?? ''),
        'missing'   => product_kyc_labels($kyc['missing'] ?? []),
        'filled'    => product_kyc_labels($kyc['filled'] ?? []),
        'required'  => product_kyc_labels($kyc['required'] ?? []),
        'groups'    => product_kyc_grouped($kyc['missing'] ?? []),
        'need_keys' => array_values($kyc['missing'] ?? []),
    ];
}

/* Short, human message for alerts / server-side blocks */
function product_gate_message($payload) {
    if (!empty($payload['ok'])) return '';
    if ($payload['block'] === 'area') {
        if ($payload['pincode'] === '' && ($payload['state'] ?? '') === '') {
            return 'Your account has no pincode or state set, so we cannot confirm your service area. Please update your profile.';
        }
        $where = $payload['pincode'] !== '' ? 'pincode ' . $payload['pincode'] : (string)$payload['state'];
        return 'This service is not available in your area (' . $where . ').';
    }
    switch ($payload['reason']) {
        case 'incomplete': return 'A few verification details are still needed (' . implode(', ', $payload['missing']) . '). You can fill them in on the checkout page.';
        case 'rejected':   return 'Your KYC was rejected. Please correct it and resubmit before purchasing.';
        case 'awaiting':   return 'Your KYC is submitted but not approved yet. You can purchase once it is approved.';
    }
    return 'You are not eligible to purchase this service yet.';
}

/* ============================================================================
   PENDING SHARES — the seller share waits for the order to be completed
   ============================================================================ */

/**
 * Move every pending share on an order into the beneficiary's wallet.
 * Called when an order becomes "Completed". Safe to call twice — rows already
 * marked paid are skipped.
 */
function shares_release_order($db, $order_id, $order_status = 'Completed') {
    shares_ensure_schema($db);
    $order_id = (int)$order_id;
    if ($order_id <= 0) return 0;
    if (strcasecmp($order_status, 'Completed') !== 0) return 0;

    $rows = [];
    try {
        $rows = $db->select("SELECT * FROM product_share_ledger
                             WHERE order_id = ? AND paid = 0 AND share_type = 'seller' AND beneficiary_id IS NOT NULL",
                            [$order_id]);
    } catch (\Throwable $e) { return 0; }

    $n = 0;
    foreach ($rows as $r) {
        $amt = (float)$r['amount'];
        if ($amt <= 0) { continue; }

        wallet_credit($db, (int)$r['beneficiary_id'], WALLET_MAIN, $amt, [
            'category'        => 'sales_earning',
            'description'     => 'Seller share released — ' . ($r['service_title'] ?: 'order') . " (order #$order_id)",
            'reference_type'  => 'order',
            'reference_id'    => $order_id,
            'counterparty_id' => (int)$r['buyer_id'],
        ]);

        try { $db->insert("UPDATE product_share_ledger SET paid = 1 WHERE id = ?", [(int)$r['id']]); } catch (\Throwable $e) {}

        try {
            $db->insert("INSERT INTO notifications (user_id, title, message, type, link, is_read) VALUES (?, ?, ?, 'earning', 'earning_report.php', 0)", [
                (int)$r['beneficiary_id'], 'Earning credited',
                '₹' . number_format($amt, 2) . ' has been added to your wallet for the completed order #' . $order_id . '.',
            ]);
        } catch (\Throwable $e) {}

        $n++;
    }
    return $n;
}

/** Pending (not yet released) seller share for one user. */
function shares_pending_total($db, $user_id) {
    shares_ensure_schema($db);
    try {
        $r = $db->select("SELECT COALESCE(SUM(amount),0) t, COUNT(*) c FROM product_share_ledger
                          WHERE beneficiary_id = ? AND paid = 0 AND share_type = 'seller'", [(int)$user_id]);
        return !empty($r) ? ['total' => (float)$r[0]['t'], 'count' => (int)$r[0]['c']] : ['total' => 0.0, 'count' => 0];
    } catch (\Throwable $e) { return ['total' => 0.0, 'count' => 0]; }
}

/** Pending share rows for one user, for the report table. */
function shares_pending_rows($db, $user_id, $limit = 200) {
    shares_ensure_schema($db);
    try {
        return $db->select("SELECT p.*, o.status AS order_status FROM product_share_ledger p
                            LEFT JOIN orders o ON p.order_id = o.id
                            WHERE p.beneficiary_id = ? AND p.paid = 0 AND p.share_type = 'seller'
                            ORDER BY p.created_at DESC LIMIT " . max(1, (int)$limit), [(int)$user_id]);
    } catch (\Throwable $e) { return []; }
}

/* ---- Totals for a service (used by the admin product page) ---- */
function shares_totals($db, $service_id) {
    try {
        $r = $db->select("SELECT share_type, SUM(amount) AS total, COUNT(*) AS cnt
                          FROM product_share_ledger WHERE service_id = ? GROUP BY share_type", [$service_id]);
        $out = [];
        foreach ($r as $x) { $out[$x['share_type']] = ['total' => (float)$x['total'], 'count' => (int)$x['cnt']]; }
        return $out;
    } catch (\Throwable $e) { return []; }
}
