<?php
/**
 * CashRequestsController — Slice 8Z-c · Batch 1
 *
 * The unified entry point for internal cash-out requests (Petty Cash, Imprest, Contingency,
 * Staff Advance, Product Discovery, Operational Float, Works Contingency).
 *
 * All requests become an `lpos` row with the appropriate `lpo_type` discriminator. Approval
 * routes via `Lpos::chainForAmount()` — standard chain (PM → FM) for amounts ≤ threshold,
 * large chain (PM → FM → EXEC) above.
 *
 * Endpoints:
 *   /CashRequests/new           GET  — draft form (query param ?type=... prefills)
 *   /CashRequests/saveAj        POST — creates LPO + submits into approval chain
 *   /CashRequests/queue         GET  — Accountant's cash-out queue (APPROVED internal-cash LPOs)
 *   /CashRequests/my            GET  — requester's own cash requests
 *
 * The existing PettyCashController/raiseAction stays functional for backward compatibility
 * but this is the canonical surface going forward.
 */
class CashRequestsController extends Controller
{
    /** Types the /new form allows — excludes SUPPLIER (that has its own draft flow). */
    private const REQUESTABLE_TYPES = ['PETTY_CASH','IMPREST','CONTINGENCY','STAFF_ADVANCE','PRODUCT_DISCOVERY','OPERATIONAL_FLOAT','WORKS_CONTINGENCY','DISPOSAL_REFUND'];

    /** Draft form (open to any operational role — perms gate this in the sidebar / URL). */
    public function newAction()
    {
        $db       = new DB();
        $projects = $db->mySelect(
            "SELECT c0d, project_no, project_name, project_type
               FROM c_projects
              WHERE deleted = 0 AND (crm_status = 'APPROVED' OR migrated_flag = 1)
              ORDER BY project_no DESC",
            []
        ) ?: [];
        $cats = $db->mySelect(
            "SELECT c0d, category_name, category_code FROM budget_item_categories WHERE is_active = 1 ORDER BY category_name",
            []
        ) ?: [];

        $preType = (string)($_GET['type'] ?? 'PETTY_CASH');
        if (!in_array($preType, self::REQUESTABLE_TYPES, true)) $preType = 'PETTY_CASH';
        $prePid  = (int)($_GET['project_id'] ?? 0);

        $this->view->setLayout($this->roleLayout());
        $this->view->render('CashRequests/new', [
            'page_title'       => 'New Cash Request',
            'projects'         => $projects,
            'categories'       => $cats,
            'types'            => Lpos::TYPES(),
            'requestable'      => self::REQUESTABLE_TYPES,
            'pre_type'         => $preType,
            'pre_project_id'   => $prePid,
            'threshold'        => Lpos::LARGE_CASH_THRESHOLD,
        ]);
    }

    /**
     * Draft + auto-submit into the approval chain.
     * POST: lpo_type, project_id, amount, currency, purpose, budget_category_id, [payee_*]
     */
    public function saveAjAction()
    {
        header('Content-Type: application/json');
        try {
            $type   = (string)($_POST['lpo_type'] ?? '');
            $amount = (float) ($_POST['amount']   ?? 0);
            if (!in_array($type, self::REQUESTABLE_TYPES, true)) {
                throw new InvalidArgumentException('Invalid cash request type.');
            }
            if ($amount <= 0) {
                throw new InvalidArgumentException('Amount must be > 0.');
            }

            // Slice 8Z-d · Batch 2 — blocking gate: no new cash until previous advances retired.
            // The user must close out any paid/issued cash LPOs (via /Retirements/new) before we
            // let them draft another. Prevents "collect three advances, retire none" pattern.
            $openAdvances = (new Lpos())->openAdvancesForUser($this->currentUserId());
            if (!empty($openAdvances)) {
                $list = [];
                foreach ($openAdvances as $oa) {
                    $list[] = $oa['lpo_no'] . ' (' . $oa['currency'] . ' ' . number_format((float)$oa['amount']) . ')';
                }
                throw new RuntimeException(
                    'You have ' . count($openAdvances) . ' unretired advance' . (count($openAdvances) === 1 ? '' : 's')
                    . ' — retire ' . (count($openAdvances) === 1 ? 'it' : 'them') . ' before raising a new request: '
                    . implode(', ', $list)
                );
            }
            $d = [
                'lpo_type'           => $type,
                'source_module'      => 'central',
                'project_id'         => (int)($_POST['project_id'] ?? 0),
                'payee_type'         => (string)($_POST['payee_type'] ?? 'employee'),
                'payee_id'           => (int)($_POST['payee_id'] ?? $this->currentUserId()),
                'payee_name'         => (string)($_POST['payee_name'] ?? ($_SESSION['full_name'] ?? '')),
                'amount'             => $amount,
                'currency'           => (string)($_POST['currency'] ?? 'TZS'),
                'purpose'            => (string)($_POST['purpose'] ?? ''),
                'budget_category_id' => (int)($_POST['budget_category_id'] ?? 0),
                'supplier_receipt_type' => 'NORMAL',
            ];
            $m = new Lpos();
            $r = $m->create($d, $this->currentUserId(), $_SESSION['full_name'] ?? '');
            if (!$r['ok']) { echo json_encode($r); return; }

            // Slice 8Z-k · capture requester's payment preference (optional).
            $prefMethod = (string)($_POST['preferred_method'] ?? '');
            $prefAcct   = (int)   ($_POST['preferred_account_id'] ?? 0);
            if ($prefMethod !== '' || $prefAcct > 0) {
                $m->setRequesterPayment((int)$r['id'], $prefMethod !== '' ? $prefMethod : null, $prefAcct ?: null);
            }

            // Auto-submit into the correct chain (Standard vs Large).
            $chainKey = Lpos::chainForAmount($type, $amount);
            if (!class_exists('ApprovalChain')) require_once ROOT . '/app/models/ApprovalChain.php';
            $ac  = new ApprovalChain();
            $l   = $m->byId((int)$r['id']);
            $ref = $l['lpo_no'] . ' · ' . (Lpos::TYPES()[$type]['label'] ?? $type) . ' · ' . $l['currency'] . ' ' . number_format($amount);
            $sres = $ac->submit([
                'chain_key'    => $chainKey,
                'entity_type'  => 'lpo',
                'entity_id'    => (int)$r['id'],
                'ref_label'    => $ref,
                'return_url'   => 'Lpos/view/' . (int)$r['id'],
                'user_id'      => $this->currentUserId(),
                'user_name'    => $_SESSION['full_name']       ?? '',
                'user_group'   => $_SESSION['user_group_name'] ?? '',
                'comment'      => 'Cash request submitted (' . Lpos::TYPES()[$type]['label'] . ').',
            ]);
            if (empty($sres['ok'])) {
                echo json_encode(['ok' => false, 'msg' => 'Draft saved but submit failed: ' . ($sres['msg'] ?? '')]);
                return;
            }
            $m->markSubmitted((int)$r['id'], (int)$sres['approval_id']);

            // Notification: fan out to the first-step role (Project Manager) so they know
            // there's a cash request waiting on them.
            try {
                if (!class_exists('Notifications')) require_once ROOT . '/app/models/Notifications.php';
                $subject = 'Cash request awaiting your check: ' . $l['lpo_no'];
                $body    = (Lpos::TYPES()[$type]['label'] ?? $type) . ' · ' . $l['currency'] . ' ' . number_format($amount)
                         . ($l['purpose'] ? ' — ' . $l['purpose'] : '');
                $url     = 'Lpos/view/' . (int)$r['id'];
                // If the request is project-scoped and the project has a PM, ping them directly.
                if ((int)$l['project_id'] > 0) {
                    $db = new DB();
                    $pmRow = $db->mySelect("SELECT project_manager_id FROM c_projects WHERE c0d=:p", [':p'=>(int)$l['project_id']]);
                    $pmId = (int)($pmRow[0]['project_manager_id'] ?? 0);
                    if ($pmId > 0) {
                        (new Notifications())->notify($pmId, 'CASH_REQUEST_PENDING_PM', $subject, $body, $url,
                            'lpo', (int)$r['id'], $this->currentUserId(), $_SESSION['full_name'] ?? '');
                    } else {
                        (new Notifications())->notifyGroup('Project Manager', 'CASH_REQUEST_PENDING_PM', $subject, $body, $url,
                            'lpo', (int)$r['id'], $this->currentUserId(), $_SESSION['full_name'] ?? '');
                    }
                } else {
                    (new Notifications())->notifyGroup('Project Manager', 'CASH_REQUEST_PENDING_PM', $subject, $body, $url,
                        'lpo', (int)$r['id'], $this->currentUserId(), $_SESSION['full_name'] ?? '');
                }
            } catch (\Throwable $e) { /* best-effort */ }

            echo json_encode(['ok' => true, 'id' => (int)$r['id'], 'chain_key' => $chainKey, 'msg' => 'Submitted for ' . $chainKey . '.']);
        } catch (\Throwable $e) { echo json_encode(['ok' => false, 'msg' => $e->getMessage()]); }
    }

    /**
     * Accountant's Cash-out queue.
     * Shows APPROVED internal-cash LPOs that haven't yet been disbursed (no PAID status) —
     * grouped by cash_out_status so Accountant sees "Ready for PV" / "PV drafted" / "Scheduled".
     */
    public function queueAction()
    {
        $db = new DB();
        $filter = [];
        $where  = "WHERE l.deleted = 0
                     AND l.lpo_type IN ('" . implode("','", Lpos::INTERNAL_CASH_TYPES) . "')
                     AND l.cash_out_status IN ('APPROVED','ISSUED','PV_DRAFTED','SCHEDULED')";
        if (!empty($_GET['type']) && in_array($_GET['type'], Lpos::INTERNAL_CASH_TYPES, true)) {
            $where .= " AND l.lpo_type = :t";
            $filter[':t'] = (string)$_GET['type'];
        }
        $rows = $db->mySelect(
            "SELECT l.c0d, l.lpo_no, l.lpo_type, l.amount, l.currency, l.purpose, l.cash_out_status,
                    l.requested_by_name, l.requested_on, l.payee_name, l.approval_id,
                    p.project_no, p.project_name
               FROM lpos l
          LEFT JOIN c_projects p ON p.c0d = l.project_id
               $where
              ORDER BY l.c0d DESC",
            $filter
        ) ?: [];

        // Batch 3: expose box so Accountant sees balance + can decide Path A vs Path B per row.
        $box = null;
        try { $box = (new PettyCashBox())->primary(); } catch (\Throwable $e) { /* fresh install pre-Batch3 */ }

        $this->view->setLayout($this->roleLayout());
        $this->view->render('CashRequests/queue', [
            'page_title' => 'Cash-out queue',
            'rows'       => $rows,
            'types'      => Lpos::TYPES(),
            'internal'   => Lpos::INTERNAL_CASH_TYPES,
            'filter_type'=> (string)($_GET['type'] ?? ''),
            'box'        => $box,
        ]);
    }

    /** Requester's own cash requests (any status). */
    public function myAction()
    {
        $db  = new DB();
        $uid = $this->currentUserId();
        $rows = $db->mySelect(
            "SELECT l.c0d, l.lpo_no, l.lpo_type, l.amount, l.currency, l.purpose, l.cash_out_status,
                    l.requested_on, l.approval_id,
                    p.project_no, p.project_name
               FROM lpos l
          LEFT JOIN c_projects p ON p.c0d = l.project_id
              WHERE l.deleted = 0
                AND l.requested_by = :u
                AND l.lpo_type IN ('" . implode("','", Lpos::INTERNAL_CASH_TYPES) . "')
              ORDER BY l.c0d DESC",
            [':u' => $uid]
        ) ?: [];

        $this->view->setLayout($this->roleLayout());
        $this->view->render('CashRequests/my', [
            'page_title' => 'My Cash Requests',
            'rows'       => $rows,
            'types'      => Lpos::TYPES(),
        ]);
    }
}
