<?php
/**
 * WorksVariations — client-instructed additions to the BOQ.
 *
 * Lifecycle: DRAFT (PC prices) → PRICED → SUBMITTED (chain) → APPROVED → applied to BOQ.
 * Approval chain: WORKS_VARIATION_APPROVAL (PC → PM → FM → Exec).
 * On terminal APPROVED, the variation's items are appended to works_boq_items with
 * origin='VARIATION' so future IPCs measure against them.
 */
class WorksVariations extends Model
{
    /* ── Header ─────────────────────────────────────────────────────── */

    public function draft(int $projectId, array $d, int $userId, string $userName): int
    {
        if ($projectId <= 0) throw new RuntimeException('project_id required.');
        $title = trim((string)($d['title'] ?? ''));
        if ($title === '') throw new RuntimeException('Variation title required.');

        if (!class_exists('WorksBoq')) require_once ROOT . '/app/models/WorksBoq.php';
        $boq = (new WorksBoq())->findByProject($projectId);
        if (!$boq) throw new RuntimeException('Project has no BOQ — variations require a contract BOQ.');

        $db = new DB();
        $seq = ((int)$db->mySelect("SELECT COUNT(*) n FROM works_variations", [])[0]['n']) + 1;
        $voNo = 'VO-' . date('Y') . '-' . str_pad((string)$seq, 4, '0', STR_PAD_LEFT);

        $fb = $db->insertUpdate(
            "INSERT INTO works_variations
                (project_id, boq_id, vo_no, title, description, engineer_ref, engineer_instruction_date,
                 status, notes, created_by, created_by_name, created_on)
             VALUES (:p, :b, :no, :t, :d, :er, :ed,
                     'DRAFT', :nt, :u, :un, NOW())",
            [
                ':p' => $projectId, ':b' => (int)$boq['c0d'], ':no' => $voNo,
                ':t' => $title, ':d' => (string)($d['description'] ?? ''),
                ':er' => (string)($d['engineer_ref'] ?? ''),
                ':ed' => !empty($d['engineer_instruction_date']) ? (string)$d['engineer_instruction_date'] : null,
                ':nt' => (string)($d['notes'] ?? ''),
                ':u' => $userId, ':un' => $userName,
            ]
        );
        return (int)($fb['last_id'] ?? 0);
    }

    public function findById(int $id): ?array
    {
        $db = new DB();
        $r = $db->mySelect("SELECT * FROM works_variations WHERE c0d = :id AND deleted = 0", [':id' => $id]);
        return $r[0] ?? null;
    }

    public function listForProject(int $projectId): array
    {
        $db = new DB();
        return $db->mySelect(
            "SELECT * FROM works_variations WHERE project_id = :p AND deleted = 0 ORDER BY c0d DESC",
            [':p' => $projectId]
        ) ?: [];
    }

    /* ── Items ──────────────────────────────────────────────────────── */

    public function addItem(int $variationId, array $d): int
    {
        $vo = $this->findById($variationId);
        if (!$vo) throw new RuntimeException('Variation not found.');
        if (!in_array($vo['status'], ['DRAFT','PRICED'], true)) {
            throw new RuntimeException('Variation is ' . $vo['status'] . ' — item edits closed.');
        }
        if (trim((string)($d['description'] ?? '')) === '') throw new RuntimeException('Item description required.');

        $qty = (float)($d['qty'] ?? 0);
        $rate = (float)($d['rate'] ?? 0);
        $amount = round($qty * $rate, 2);

        $db = new DB();
        $fb = $db->insertUpdate(
            "INSERT INTO works_variation_boq_items
                (variation_id, item_no, section, description, unit, qty, rate, amount,
                 remeasurable, notes, created_on)
             VALUES (:v, :no, :sec, :d, :u, :q, :r, :a, :rem, :nt, NOW())",
            [
                ':v' => $variationId,
                ':no' => (string)($d['item_no'] ?? ''),
                ':sec' => (string)($d['section'] ?? ''),
                ':d' => (string)$d['description'],
                ':u' => (string)($d['unit'] ?? ''),
                ':q' => $qty, ':r' => $rate, ':a' => $amount,
                ':rem' => isset($d['remeasurable']) ? ((int)$d['remeasurable'] ? 1 : 0) : 1,
                ':nt' => (string)($d['notes'] ?? ''),
            ]
        );
        $this->recomputeTotal($variationId);
        return (int)($fb['last_id'] ?? 0);
    }

    public function itemsFor(int $variationId): array
    {
        $db = new DB();
        return $db->mySelect(
            "SELECT * FROM works_variation_boq_items WHERE variation_id = :v AND deleted = 0 ORDER BY c0d",
            [':v' => $variationId]
        ) ?: [];
    }

    public function recomputeTotal(int $variationId): void
    {
        $db = new DB();
        $t = (float)($db->mySelect(
            "SELECT COALESCE(SUM(amount),0) t FROM works_variation_boq_items WHERE variation_id = :v AND deleted = 0",
            [':v' => $variationId]
        )[0]['t'] ?? 0);
        $status = 'DRAFT';
        if ($t > 0) $status = 'PRICED';
        // Only bump PRICED if we're still in DRAFT; do NOT downgrade past states.
        $current = $this->findById($variationId);
        $newStatus = $current && in_array($current['status'], ['DRAFT','PRICED'], true) ? $status : $current['status'];
        $db->insertUpdate(
            "UPDATE works_variations SET total_value = :t, status = :s, edited_on = NOW() WHERE c0d = :id",
            [':t' => $t, ':s' => $newStatus, ':id' => $variationId]
        );
    }

    /* ── Chain lifecycle ────────────────────────────────────────────── */

    public function submitForApproval(int $variationId, int $userId, string $userName, string $userGroup): array
    {
        $vo = $this->findById($variationId);
        if (!$vo) return ['ok' => false, 'msg' => 'Variation not found.'];
        if (!in_array($vo['status'], ['DRAFT','PRICED'], true)) return ['ok' => false, 'msg' => 'Variation is ' . $vo['status'] . '.'];
        if ((float)$vo['total_value'] <= 0) return ['ok' => false, 'msg' => 'Price at least one item before submitting.'];

        if (!class_exists('ApprovalChain')) require_once ROOT . '/app/models/ApprovalChain.php';
        $ac = new ApprovalChain();
        $ref = 'VO ' . $vo['vo_no'] . ' · ' . $vo['title'] . ' · TZS ' . number_format((float)$vo['total_value']);
        $res = $ac->submit([
            'chain_key' => 'WORKS_VARIATION_APPROVAL',
            'entity_type' => 'WORKS_VARIATION',
            'entity_id' => $variationId,
            'ref_label' => $ref,
            'return_url' => 'WorksVariations/view/' . $variationId,
            'user_id' => $userId, 'user_name' => $userName, 'user_group' => $userGroup,
        ]);
        if (!empty($res['ok'])) {
            $db = new DB();
            $db->insertUpdate(
                "UPDATE works_variations SET status = 'SUBMITTED', approval_id = :aid, submitted_on = NOW(), edited_on = NOW() WHERE c0d = :id",
                [':aid' => (int)$res['approval_id'], ':id' => $variationId]
            );
        }
        return $res;
    }

    /**
     * Called from FinChainsController::postDecisionHook on WORKS_VARIATION_APPROVAL terminal.
     * On APPROVED: mark APPROVED, then apply — appends rows to works_boq_items.
     */
    public function onChainDecision(int $variationId, string $finalStatus): void
    {
        $vo = $this->findById($variationId);
        if (!$vo) return;
        $db = new DB();
        if ($finalStatus === 'APPROVED') {
            $db->insertUpdate(
                "UPDATE works_variations SET status = 'APPROVED', approved_on = NOW(), edited_on = NOW() WHERE c0d = :id",
                [':id' => $variationId]
            );
            $this->applyToBoq($variationId);
        } elseif ($finalStatus === 'DECLINED') {
            $db->insertUpdate(
                "UPDATE works_variations SET status = 'DECLINED', edited_on = NOW() WHERE c0d = :id",
                [':id' => $variationId]
            );
        }
    }

    /**
     * Append the variation items to the project's BOQ. Idempotent — sets applied_on on
     * first successful run; further calls are no-ops.
     */
    public function applyToBoq(int $variationId): int
    {
        $vo = $this->findById($variationId);
        if (!$vo) return 0;
        if ($vo['status'] !== 'APPROVED') return 0;
        if (!empty($vo['applied_on'])) return 0; // Already applied.

        if (!class_exists('WorksBoq')) require_once ROOT . '/app/models/WorksBoq.php';
        $inserted = (new WorksBoq())->appendFromVariation((int)$vo['boq_id'], $variationId);
        $db = new DB();
        $db->insertUpdate(
            "UPDATE works_variations SET applied_on = NOW(), edited_on = NOW() WHERE c0d = :id",
            [':id' => $variationId]
        );
        return $inserted;
    }
}
