<?php
/* ============================================================================
   WBMCZ — Task criteria

   Every tab on Activity & Task now has a rule behind it that says WHO belongs
   there, and the tab lists those people straight from the live system:

     Lead          everybody who registered
     KYC           the ones who never filled their KYC
     Subscription  the ones who never subscribed
     Refer         the ones who have not referred anybody
     Order         the ones who never bought anything
     Follow-up     leads whose call-back date has arrived

   None of that is hard-coded. Each rule is a small set of conditions saved in
   `task_criteria`, one row per (audience, tab), editable from
   Admin → Activity & Task → Criteria. Change the rule and the list changes.

   Everything is a condition on the `users` table plus a few EXISTS checks, so
   one indexed query answers any combination.
   ============================================================================ */

if (!function_exists('tcrit_ensure_schema')) {

/* ---------------------------------------------------------------------------
   WHO MAY SET THE CRITERIA

   Deciding who lands on a task tab is part of running the task board, so the
   check follows the Activity & Task module — not Staff & Roles. Any of these
   is enough:

     · the owner account, or a role holding the whole panel
     · a role that may CREATE tasks            (tasks.create in Custom Roles)
     · the older "Manage Tasks" module flag
   --------------------------------------------------------------------------- */
function tcrit_can_manage($db = null) {
    if (function_exists('tasks_can_manage') && tasks_can_manage($db)) return true;
    if (function_exists('acl_can') && acl_can('tasks.create'))        return true;
    if (function_exists('acl_has_module') && acl_has_module('tasks')) return true;
    if (function_exists('hasPermission') && hasPermission('manage_tasks')) return true;
    return false;
}

/**
 * A denial screen that actually says why, instead of a blank white page.
 * It lists what the page needs and what this account really holds, so the
 * fix is obvious: tick that box in Admin -> Custom Roles.
 */
function tcrit_deny($db = null, $what = 'change task criteria') {
    $role = function_exists('tasks_actor') ? (tasks_actor($db)['role'] ?? 'unknown') : 'unknown';
    $name = $_SESSION['user_name'] ?? 'this account';

    $mods = [];
    if (function_exists('acl_modules') && function_exists('acl_has_module')) {
        foreach (acl_modules() as $mk => $m) {
            if (acl_has_module($mk)) { $mods[] = $m['label'] ?? $mk; }
        }
    }

    http_response_code(403);
    ?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Access denied</title>
        <script src="https://cdn.tailwindcss.com"></script>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    </head>
    <body class="bg-slate-100 min-h-screen flex items-center justify-center p-6 font-sans">
        <div class="max-w-lg w-full bg-white rounded-2xl shadow-lg border border-slate-200 p-8">
            <div class="w-14 h-14 rounded-2xl bg-amber-50 text-amber-500 flex items-center justify-center text-2xl mb-4">
                <i class="fas fa-lock"></i>
            </div>
            <h1 class="text-xl font-extrabold text-slate-800 mb-2">You cannot <?php echo htmlspecialchars($what); ?> yet</h1>
            <p class="text-sm text-slate-500 leading-relaxed mb-5">
                <b><?php echo htmlspecialchars($name); ?></b> is signed in as
                <b><?php echo htmlspecialchars($role); ?></b>. This screen needs the
                <b>Activity &amp; Task</b> module with <b>Create a task</b> ticked &mdash;
                or an account with the whole panel.
            </p>

            <div class="bg-slate-50 border border-slate-200 rounded-xl p-4 mb-5">
                <p class="text-[10px] font-black uppercase tracking-widest text-slate-400 mb-2">What this account holds</p>
                <?php if (empty($mods)): ?>
                    <p class="text-[13px] text-slate-500">No modules at all — the role is empty.</p>
                <?php else: ?>
                    <p class="text-[13px] text-slate-700 leading-relaxed"><?php echo htmlspecialchars(implode(' · ', $mods)); ?></p>
                <?php endif; ?>
            </div>

            <p class="text-[12px] text-slate-500 mb-5">
                An administrator can fix this in <b>User Management &rarr; Custom Roles</b>: open this
                account's role and tick <b>Activity &amp; Task &rarr; Create a task</b>.
            </p>

            <div class="flex gap-2">
                <a href="activity_task.php" class="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-slate-900 text-white text-sm font-bold">
                    <i class="fas fa-list-check"></i> Open Activity &amp; Task
                </a>
                <a href="index.php" class="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-slate-100 text-slate-600 text-sm font-bold">
                    <i class="fas fa-arrow-left"></i> Dashboard
                </a>
            </div>
        </div>
    </body>
    </html>
    <?php
    exit;
}

function tcrit_ensure_schema($db) {
    static $done = false;
    if ($done) return;
    $done = true;
    try {
        $db->getConnection()->query("CREATE TABLE IF NOT EXISTS task_criteria (
            id INT AUTO_INCREMENT PRIMARY KEY,
            scope_role VARCHAR(20) NOT NULL DEFAULT 'user',
            module_key VARCHAR(30) NOT NULL,
            label VARCHAR(160) NULL,
            rules_json TEXT NULL,
            sort_field VARCHAR(30) NOT NULL DEFAULT 'newest',
            status TINYINT(1) NOT NULL DEFAULT 1,
            updated_by INT NULL,
            updated_by_name VARCHAR(120) NULL,
            created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NULL DEFAULT NULL,
            UNIQUE KEY uniq_crit (scope_role, module_key)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
    } catch (\Throwable $e) { /* ignore */ }
}

/* ---------------------------------------------------------------------------
   THE CONDITION CATALOGUE

   key => [ label, group, type, choices, hint ]
     type 'choice'  one of the listed values
          'days'    a whole number of days
          'text'    free text, matched exactly
   Every condition knows how to turn itself into SQL in tcrit_build().
   --------------------------------------------------------------------------- */
function tcrit_conditions() {
    return [
        /* ---- who they are ---- */
        'user_type' => ['Account type', 'Account', 'choice',
            ['' => 'Any', 'customer' => 'Customer', 'seller' => 'Seller', 'admin' => 'Staff / Admin'],
            'Members, sellers or staff'],
        'status' => ['Account status', 'Account', 'choice',
            ['' => 'Any', '1' => 'Active', '0' => 'Blocked'], ''],
        'joined_within' => ['Registered in the last', 'Account', 'days',
            [], 'Leave blank for any time'],
        'joined_before' => ['Registered more than', 'Account', 'days',
            [], 'Older than this many days — good for chasing cold sign-ups'],

        /* ---- money ---- */
        'subscribed' => ['Subscription', 'Money', 'choice',
            ['' => 'Any', 'no' => 'Never subscribed', 'yes' => 'Subscribed'], ''],
        'has_order' => ['Orders', 'Money', 'choice',
            ['' => 'Any', 'no' => 'Never bought anything', 'yes' => 'Has bought'], ''],
        'wallet' => ['Wallet', 'Money', 'choice',
            ['' => 'Any', 'zero' => 'Empty wallet', 'nonzero' => 'Has money'], ''],

        /* ---- KYC ---- */
        'kyc' => ['KYC', 'KYC', 'choice',
            ['' => 'Any', 'none' => 'Never filled', 'pending' => 'Waiting for approval',
             'approved' => 'Approved', 'rejected' => 'Rejected', 'not_approved' => 'Anything but approved'], ''],

        /* ---- referral ---- */
        'referred_by' => ['Came through a referral', 'Referral', 'choice',
            ['' => 'Any', 'yes' => 'Yes, somebody referred them', 'no' => 'No, signed up alone'], ''],
        'has_referral' => ['Has referred others', 'Referral', 'choice',
            ['' => 'Any', 'no' => 'Has referred nobody', 'yes' => 'Has referred somebody'], ''],

        /* ---- lead work ---- */
        'lead_exists' => ['Already a lead', 'Lead', 'choice',
            ['' => 'Any', 'no' => 'Not picked up yet', 'yes' => 'Already a lead'], ''],
        'lead_status' => ['Lead status', 'Lead', 'choice',
            ['' => 'Any', 'new' => 'New', 'contacted' => 'Contacted', 'interested' => 'Interested',
             'follow_up' => 'Follow-up', 'converted' => 'Converted', 'lost' => 'Not interested'], ''],
        'lead_assigned' => ['Lead owner', 'Lead', 'choice',
            ['' => 'Any', 'no' => 'Nobody owns it', 'yes' => 'Somebody owns it'], ''],
        'followup_due' => ['Call-back date', 'Lead', 'choice',
            ['' => 'Any', 'due' => 'Due today or overdue', 'week' => 'Within the next 7 days'], ''],

        /* ---- where ---- */
        'state'    => ['State',    'Location', 'text', [], ''],
        'district' => ['District', 'Location', 'text', [], ''],
        'pincode'  => ['Pin code', 'Location', 'text', [], ''],

        /* ---- open tasks ---- */
        'has_task' => ['Existing task on this tab', 'Task', 'choice',
            ['' => 'Any', 'no' => 'No open task yet', 'yes' => 'Already has an open task'],
            'Keeps people you have already handed out from showing up again'],
    ];
}

function tcrit_groups() { return ['Account', 'Money', 'KYC', 'Referral', 'Lead', 'Location', 'Task']; }

function tcrit_sorts() {
    return [
        'newest'  => 'Newest first',
        'oldest'  => 'Oldest first',
        'name'    => 'Name A-Z',
        'wallet'  => 'Biggest wallet first',
        'due'     => 'Call-back date first',
    ];
}

/* ---------------------------------------------------------------------------
   WHAT EACH TAB MEANS OUT OF THE BOX
   These are only used until somebody saves their own rule for that tab.
   --------------------------------------------------------------------------- */
function tcrit_defaults() {
    return [
        'lead'         => ['label' => 'Everybody who registered',
                           'rules' => ['user_type' => 'customer', 'has_task' => 'no']],
        'user'         => ['label' => 'All members',
                           'rules' => ['user_type' => 'customer']],
        'kyc'          => ['label' => 'KYC not done yet',
                           'rules' => ['user_type' => 'customer', 'kyc' => 'none', 'has_task' => 'no']],
        'subscription' => ['label' => 'Never subscribed',
                           'rules' => ['user_type' => 'customer', 'subscribed' => 'no', 'has_task' => 'no']],
        'refer'        => ['label' => 'Has referred nobody',
                           'rules' => ['user_type' => 'customer', 'has_referral' => 'no', 'has_task' => 'no']],
        'followup'     => ['label' => 'Call-back due today or overdue',
                           'rules' => ['lead_exists' => 'yes', 'followup_due' => 'due'],
                           'sort'  => 'due'],
        'order'        => ['label' => 'Never bought anything',
                           'rules' => ['user_type' => 'customer', 'has_order' => 'no', 'has_task' => 'no']],
        'wallet'       => ['label' => 'Empty wallet',
                           'rules' => ['user_type' => 'customer', 'wallet' => 'zero']],
        'seller'       => ['label' => 'All sellers',
                           'rules' => ['user_type' => 'seller']],
        'support'      => ['label' => 'All members',
                           'rules' => ['user_type' => 'customer']],
        'notification' => ['label' => 'All members',
                           'rules' => ['user_type' => 'customer']],
        'offer'        => ['label' => 'Subscribed members',
                           'rules' => ['user_type' => 'customer', 'subscribed' => 'yes']],
    ];
}

/* The saved rule for one tab, or the default. Always returns the same shape. */
function tcrit_get($db, $scope, $module) {
    tcrit_ensure_schema($db);
    static $cache = [];
    $k = $scope . '|' . $module;
    if (isset($cache[$k])) return $cache[$k];

    $out = ['label' => '', 'rules' => [], 'sort' => 'newest', 'status' => 1, 'saved' => false];

    try {
        $r = $db->select("SELECT * FROM task_criteria WHERE scope_role = ? AND module_key = ?", [$scope, $module]);
        if (!empty($r)) {
            $rules = json_decode((string)$r[0]['rules_json'], true);
            $out = [
                'label'  => (string)$r[0]['label'],
                'rules'  => is_array($rules) ? $rules : [],
                'sort'   => (string)$r[0]['sort_field'] ?: 'newest',
                'status' => (int)$r[0]['status'],
                'saved'  => true,
            ];
            return $cache[$k] = $out;
        }
    } catch (\Throwable $e) {}

    $def = tcrit_defaults();
    if (isset($def[$module])) {
        $out['label'] = $def[$module]['label'];
        $out['rules'] = $def[$module]['rules'];
        $out['sort']  = $def[$module]['sort'] ?? 'newest';
    }
    return $cache[$k] = $out;
}

function tcrit_save($db, $scope, $module, $label, $rules, $sort, $status, $me = null) {
    tcrit_ensure_schema($db);
    $json = json_encode(is_array($rules) ? $rules : []);
    try {
        $db->insert(
            "INSERT INTO task_criteria (scope_role, module_key, label, rules_json, sort_field, status, updated_by, updated_by_name, updated_at)
             VALUES (?,?,?,?,?,?,?,?,NOW())
             ON DUPLICATE KEY UPDATE label = VALUES(label), rules_json = VALUES(rules_json),
                                     sort_field = VALUES(sort_field), status = VALUES(status),
                                     updated_by = VALUES(updated_by), updated_by_name = VALUES(updated_by_name),
                                     updated_at = NOW()",
            [$scope, $module, (string)$label, $json, (string)$sort, (int)$status,
             $me['id'] ?? null, $me['name'] ?? null]);
        return true;
    } catch (\Throwable $e) { return false; }
}

function tcrit_reset($db, $scope, $module) {
    tcrit_ensure_schema($db);
    try { $db->insert("DELETE FROM task_criteria WHERE scope_role = ? AND module_key = ?", [$scope, $module]); return true; }
    catch (\Throwable $e) { return false; }
}

/* ---------------------------------------------------------------------------
   RULES  ->  SQL
   Returns ['where' => '...', 'params' => [...], 'joins' => '...'] against
   `users u`. Every condition is ANDed; a blank value means "don't care".
   --------------------------------------------------------------------------- */
function tcrit_build($db, $rules, $module = '', $scope = '') {
    $w = ["u.deleted_at IS NULL"];
    $p = [];

    $v = function ($k) use ($rules) { return isset($rules[$k]) ? trim((string)$rules[$k]) : ''; };

    if ($v('user_type') !== '') { $w[] = "u.user_type = ?"; $p[] = $v('user_type'); }
    if ($v('status') !== '')    { $w[] = "COALESCE(u.status,1) = ?"; $p[] = (int)$v('status'); }

    if ($v('joined_within') !== '' && ctype_digit($v('joined_within'))) {
        $w[] = "u.created_at >= DATE_SUB(NOW(), INTERVAL ? DAY)"; $p[] = (int)$v('joined_within');
    }
    if ($v('joined_before') !== '' && ctype_digit($v('joined_before'))) {
        $w[] = "u.created_at < DATE_SUB(NOW(), INTERVAL ? DAY)"; $p[] = (int)$v('joined_before');
    }

    if ($v('subscribed') === 'yes') { $w[] = "COALESCE(u.is_subscribed,0) = 1"; }
    elseif ($v('subscribed') === 'no') { $w[] = "COALESCE(u.is_subscribed,0) = 0"; }

    if ($v('has_order') === 'yes') { $w[] = "EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)"; }
    elseif ($v('has_order') === 'no') { $w[] = "NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)"; }

    if ($v('wallet') === 'zero') { $w[] = "COALESCE(u.wallet_balance,0) = 0"; }
    elseif ($v('wallet') === 'nonzero') { $w[] = "COALESCE(u.wallet_balance,0) > 0"; }

    switch ($v('kyc')) {
        case 'none':
            $w[] = "NOT EXISTS (SELECT 1 FROM kyc_details k WHERE k.user_id = u.id)"; break;
        case 'not_approved':
            $w[] = "NOT EXISTS (SELECT 1 FROM kyc_details k WHERE k.user_id = u.id AND k.status = 'approved')"; break;
        case 'pending': case 'approved': case 'rejected':
            $w[] = "EXISTS (SELECT 1 FROM kyc_details k WHERE k.user_id = u.id AND k.status = ?)";
            $p[] = $v('kyc'); break;
    }

    if ($v('referred_by') === 'yes') { $w[] = "u.referred_by IS NOT NULL AND u.referred_by > 0"; }
    elseif ($v('referred_by') === 'no') { $w[] = "(u.referred_by IS NULL OR u.referred_by = 0)"; }

    if ($v('has_referral') === 'yes') { $w[] = "EXISTS (SELECT 1 FROM users r WHERE r.referred_by = u.id)"; }
    elseif ($v('has_referral') === 'no') { $w[] = "NOT EXISTS (SELECT 1 FROM users r WHERE r.referred_by = u.id)"; }

    if ($v('lead_exists') === 'yes') { $w[] = "EXISTS (SELECT 1 FROM leads l WHERE l.user_id = u.id)"; }
    elseif ($v('lead_exists') === 'no') { $w[] = "NOT EXISTS (SELECT 1 FROM leads l WHERE l.user_id = u.id)"; }

    if ($v('lead_status') !== '') {
        $w[] = "EXISTS (SELECT 1 FROM leads l WHERE l.user_id = u.id AND l.status = ?)";
        $p[] = $v('lead_status');
    }

    if ($v('lead_assigned') === 'yes') { $w[] = "EXISTS (SELECT 1 FROM leads l WHERE l.user_id = u.id AND l.assigned_to IS NOT NULL)"; }
    elseif ($v('lead_assigned') === 'no') { $w[] = "NOT EXISTS (SELECT 1 FROM leads l WHERE l.user_id = u.id AND l.assigned_to IS NOT NULL)"; }

    if ($v('followup_due') === 'due') {
        $w[] = "EXISTS (SELECT 1 FROM leads l WHERE l.user_id = u.id AND l.next_follow_up IS NOT NULL
                        AND l.next_follow_up <= CURDATE() AND l.status IN ('new','contacted','interested','follow_up'))";
    } elseif ($v('followup_due') === 'week') {
        $w[] = "EXISTS (SELECT 1 FROM leads l WHERE l.user_id = u.id AND l.next_follow_up
                        BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY))";
    }

    foreach (['state', 'district', 'pincode'] as $col) {
        if ($v($col) !== '') { $w[] = "u.`$col` = ?"; $p[] = $v($col); }
    }

    /* "already has an open task on this tab" only makes sense with a tab */
    if ($module !== '' && $v('has_task') !== '') {
        $sub = "SELECT 1 FROM tasks t WHERE t.target_user_id = u.id AND t.module_key = ?
                AND t.status IN ('pending','in_progress','followup','on_hold')";
        if ($v('has_task') === 'no') { $w[] = "NOT EXISTS ($sub)"; }
        else { $w[] = "EXISTS ($sub)"; }
        $p[] = $module;
    }

    return ['where' => implode(' AND ', $w), 'params' => $p];
}

function tcrit_order_sql($sort) {
    switch ($sort) {
        case 'oldest': return "u.id ASC";
        case 'name':   return "u.name ASC";
        case 'wallet': return "COALESCE(u.wallet_balance,0) DESC, u.id DESC";
        case 'due':    return "(SELECT MIN(l.next_follow_up) FROM leads l WHERE l.user_id = u.id) ASC, u.id DESC";
    }
    return "u.id DESC";
}

/* How many people match, and the page of rows to show. */
function tcrit_count($db, $rules, $module = '', $extra = '', $extraParams = []) {
    $b = tcrit_build($db, $rules, $module);
    $where = $b['where'] . ($extra !== '' ? ' AND ' . $extra : '');
    try {
        $r = $db->select("SELECT COUNT(*) c FROM users u WHERE $where", array_merge($b['params'], $extraParams));
        return !empty($r) ? (int)$r[0]['c'] : 0;
    } catch (\Throwable $e) { return 0; }
}

function tcrit_rows($db, $rules, $module = '', $sort = 'newest', $limit = 50, $offset = 0, $extra = '', $extraParams = []) {
    $b = tcrit_build($db, $rules, $module);
    $where = $b['where'] . ($extra !== '' ? ' AND ' . $extra : '');
    $order = tcrit_order_sql($sort);
    try {
        return $db->select(
            "SELECT u.id, u.name, u.mobile, u.email, u.user_type, u.state, u.district, u.pincode,
                    u.is_subscribed, u.wallet_balance, u.created_at, u.referred_by,
                    (SELECT k.status FROM kyc_details k WHERE k.user_id = u.id LIMIT 1)        AS kyc_status,
                    (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id)                     AS order_count,
                    (SELECT COUNT(*) FROM users r WHERE r.referred_by = u.id)                  AS referral_count,
                    (SELECT l.status FROM leads l WHERE l.user_id = u.id ORDER BY l.id DESC LIMIT 1)        AS lead_status,
                    (SELECT l.assigned_name FROM leads l WHERE l.user_id = u.id ORDER BY l.id DESC LIMIT 1) AS lead_owner,
                    (SELECT l.next_follow_up FROM leads l WHERE l.user_id = u.id ORDER BY l.id DESC LIMIT 1) AS next_follow_up
             FROM users u
             WHERE $where
             ORDER BY $order
             LIMIT " . (int)$limit . " OFFSET " . (int)$offset,
            array_merge($b['params'], $extraParams));
    } catch (\Throwable $e) { return []; }
}

/* A plain-English summary of a rule, for the chip under the tab title. */
function tcrit_summary($rules) {
    $cat = tcrit_conditions();
    $bits = [];
    foreach ((array)$rules as $k => $val) {
        $val = trim((string)$val);
        if ($val === '' || !isset($cat[$k])) continue;
        $def = $cat[$k];
        if ($def[2] === 'choice') {
            $bits[] = $def[3][$val] ?? ($def[0] . ': ' . $val);
        } elseif ($def[2] === 'days') {
            $bits[] = $def[0] . ' ' . $val . ' days';
        } else {
            $bits[] = $def[0] . ' = ' . $val;
        }
    }
    return empty($bits) ? 'Everyone' : implode(' · ', $bits);
}

} // end function_exists guard
