<?php
/**
 * ProjectOverheads — PC's overhead carry-over surface.
 *
 * Slice 8Z-a · When PC scrubs the Client PO, they see the overheads PM captured
 * at tender time (from pricing_version_overheads). PC ticks each to include on
 * the live project + can add anything missed during site walk-through.
 *
 * Categories intentionally shorter than the tender's — this table is about
 * OPERATIONAL overheads (freight, HSE, insurance, packaging, warehousing,
 * installation, other), not the tender-time computational overheads. If a
 * tender overhead doesn't fit these, it maps to OTHER.
 */
class ProjectOverheads extends Model
{
    public const CATEGORIES = ['TRANSPORT','HSE','INSURANCE','PACKAGING','WAREHOUSING','INSTALLATION','OTHER'];

    /** Active (non-deleted) overheads on a project, optionally scoped to a PO. */
    public function listFor(int $projectId, ?int $poId = null): array
    {
        $db = new DB();
        $where  = "project_id = :p AND deleted = 0";
        $params = [':p' => $projectId];
        if ($poId !== null && $poId > 0) { $where .= " AND (po_id = :po OR po_id IS NULL)"; $params[':po'] = $poId; }
        return $db->mySelect(
            "SELECT * FROM c_project_overheads WHERE $where ORDER BY category, c0d",
            $params
        ) ?: [];
    }

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

    /**
     * Idempotent: clone every pricing_version_overheads row on the tender's approved
     * (or latest) pricing version into c_project_overheads. Skips rows already carried
     * over (uniqueness on project_id + source_overhead_id).
     */
    public function seedFromTender(int $projectId, ?int $poId, int $tenderId, int $userId, string $userName): array
    {
        if ($tenderId <= 0) return ['ok' => false, 'msg' => 'No source tender.'];
        $db = new DB();
        // Prefer APPROVED version, fallback to latest.
        $pv = $db->mySelect(
            "SELECT c0d FROM tender_pricing_versions
              WHERE tender_id = :t AND deleted = 0
              ORDER BY (status='APPROVED') DESC, c0d DESC LIMIT 1",
            [':t' => $tenderId]
        );
        if (empty($pv)) return ['ok' => false, 'msg' => 'No pricing version on the tender.'];
        $rows = $db->mySelect(
            "SELECT c0d, category, label, amount, notes
               FROM pricing_version_overheads
              WHERE version_id = :v AND deleted = 0
              ORDER BY line_no, c0d",
            [':v' => (int)$pv[0]['c0d']]
        ) ?: [];
        if (empty($rows)) return ['ok' => true, 'inserted' => 0, 'skipped' => 0, 'msg' => 'No overheads on the tender pricing to carry over.'];

        // Already-carried source ids so re-runs are idempotent.
        $existing = $db->mySelect(
            "SELECT source_overhead_id FROM c_project_overheads WHERE project_id = :p AND deleted = 0 AND source_overhead_id IS NOT NULL",
            [':p' => $projectId]
        ) ?: [];
        $seen = [];
        foreach ($existing as $e) $seen[(int)$e['source_overhead_id']] = true;

        $inserted = 0; $skipped = 0;
        foreach ($rows as $r) {
            $sid = (int)$r['c0d'];
            if (isset($seen[$sid])) { $skipped++; continue; }
            $cat = strtoupper((string)($r['category'] ?? 'OTHER'));
            if (!in_array($cat, self::CATEGORIES, true)) $cat = 'OTHER';
            $db->insertUpdate(
                "INSERT INTO c_project_overheads
                    (project_id, po_id, category, label, amount, notes, source, source_overhead_id, active,
                     created_by, created_by_name, created_on)
                 VALUES (:p, :po, :c, :l, :a, :n, 'TENDER_CARRY_OVER', :sid, 1, :u, :un, NOW())",
                [
                    ':p' => $projectId, ':po' => $poId ?: null,
                    ':c' => $cat,
                    ':l' => (string)($r['label'] ?? ''),
                    ':a' => (float)($r['amount'] ?? 0),
                    ':n' => (string)($r['notes'] ?? ''),
                    ':sid' => $sid,
                    ':u' => $userId, ':un' => $userName,
                ]
            );
            $inserted++;
        }
        return ['ok' => true, 'inserted' => $inserted, 'skipped' => $skipped, 'msg' => "Cloned $inserted overhead(s), skipped $skipped duplicate(s)."];
    }

    /**
     * PC_ADDED overheads seed both fields with the initial value — the number PC first typed IS
     * their estimate AND their initial commitment. They can refine amount_actual later.
     */
    public function add(int $projectId, ?int $poId, array $d, int $userId, string $userName): int
    {
        $label = trim((string)($d['label'] ?? ''));
        if ($label === '') throw new InvalidArgumentException('Label required.');
        $cat = strtoupper((string)($d['category'] ?? 'OTHER'));
        if (!in_array($cat, self::CATEGORIES, true)) $cat = 'OTHER';
        $amt = (float)($d['amount'] ?? 0);

        $db = new DB();
        $fb = $db->insertUpdate(
            "INSERT INTO c_project_overheads
                (project_id, po_id, category, label, amount, amount_actual, notes, source, active,
                 created_by, created_by_name, created_on)
             VALUES (:p, :po, :c, :l, :a, :aa, :n, 'PC_ADDED', 1, :u, :un, NOW())",
            [
                ':p' => $projectId, ':po' => $poId ?: null,
                ':c' => $cat, ':l' => $label,
                ':a' => $amt, ':aa' => $amt,
                ':n' => (string)($d['notes'] ?? ''),
                ':u' => $userId, ':un' => $userName,
            ]
        );
        return (int)($fb['last_id'] ?? 0);
    }

    /**
     * Update — never touches `amount` (that's the frozen original / tender-estimate).
     * `amount_actual` is what PC/Proc refine as real quotes land.
     * `active` toggle + label/category/notes edits also flow through here.
     */
    public function update(int $id, array $d, int $userId): void
    {
        $o = $this->find($id);
        if (!$o) throw new RuntimeException('Overhead not found.');
        $cat = strtoupper((string)($d['category'] ?? $o['category']));
        if (!in_array($cat, self::CATEGORIES, true)) $cat = $o['category'];

        // amount_actual: null in payload → keep prior; empty string → wipe to NULL; number → set.
        $newActual = null;
        if (array_key_exists('amount_actual', $d)) {
            $raw = $d['amount_actual'];
            if ($raw === '' || $raw === null) $newActual = null;
            else                              $newActual = (float)$raw;
        } else {
            $newActual = $o['amount_actual'] === null ? null : (float)$o['amount_actual'];
        }

        $db = new DB();
        $db->insertUpdate(
            "UPDATE c_project_overheads SET
                category      = :c,
                label         = :l,
                amount_actual = :aa,
                notes         = :n,
                active        = :ac,
                edited_by     = :u,
                edited_on     = NOW()
              WHERE c0d = :id",
            [
                ':c'  => $cat,
                ':l'  => (string)($d['label'] ?? $o['label']),
                ':aa' => $newActual,
                ':n'  => (string)($d['notes'] ?? $o['notes']),
                ':ac' => array_key_exists('active', $d) ? ((int)$d['active'] ? 1 : 0) : (int)$o['active'],
                ':u'  => $userId, ':id' => $id,
            ]
        );
    }

    public function softDelete(int $id, int $userId): void
    {
        $db = new DB();
        $db->insertUpdate(
            "UPDATE c_project_overheads SET deleted = 1, edited_by = :u, edited_on = NOW() WHERE c0d = :id",
            [':u' => $userId, ':id' => $id]
        );
    }
}
