<?php
/**
 * ProjectMilestones — lightweight per-project timeline.
 *
 * Slice 8Y · PM sets checkpoints the PC + team must hit (kick-off, mobilisation,
 * first delivery, commissioning, handover). PC + assigned owners can flip status
 * as work advances. Kept simple — no dependencies / gantt / percent-complete,
 * just title + target date + owner + status.
 *
 * Statuses form the timeline chip colour:
 *   NOT_STARTED (grey) → IN_PROGRESS (blue) → DONE (green)
 *   BLOCKED (red), CANCELLED (grey-strike)
 */
class ProjectMilestones extends Model
{
    public const STATUSES = ['NOT_STARTED','IN_PROGRESS','DONE','BLOCKED','CANCELLED'];
    private const SLOTS   = ['PROJECT_MANAGER','PROJECTS_COORDINATOR','ACCOUNTANT','PROCUREMENT','FIELD_SUPERVISOR','CRM','EXECUTIVE'];

    public function all(int $projectId): array
    {
        $db = new DB();
        return $db->mySelect(
            "SELECT * FROM c_project_milestones WHERE project_id = :p AND deleted = 0 ORDER BY target_date IS NULL, target_date ASC, sequence ASC, c0d ASC",
            [':p' => $projectId]
        ) ?: [];
    }

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

    public function add(int $projectId, array $d, int $userId, string $userName, string $userGroup): int
    {
        $g = strtoupper(trim($userGroup));
        if (!in_array($g, ['PROJECT MANAGER','ADMINISTRATOR','PROJECTS COORDINATOR'], true)) {
            throw new RuntimeException('Only PM, PC, or Administrator can add milestones.');
        }
        $title = trim((string)($d['title'] ?? ''));
        if ($title === '') throw new InvalidArgumentException('Title required.');

        $slot = strtoupper((string)($d['owner_slot'] ?? ''));
        if ($slot !== '' && !in_array($slot, self::SLOTS, true)) $slot = '';

        $status = strtoupper((string)($d['status'] ?? 'NOT_STARTED'));
        if (!in_array($status, self::STATUSES, true)) $status = 'NOT_STARTED';

        $db = new DB();
        $nextSeq = ((int)($db->mySelect("SELECT COALESCE(MAX(sequence),0) n FROM c_project_milestones WHERE project_id=:p AND deleted=0", [':p'=>$projectId])[0]['n'] ?? 0)) + 1;

        $fb = $db->insertUpdate(
            "INSERT INTO c_project_milestones
                (project_id, sequence, title, description, target_date, owner_slot,
                 owner_user_id, owner_user_name, status, created_by, created_by_name, created_on)
             VALUES (:p, :seq, :t, :ds, :td, :sl, :oi, :on, :st, :cb, :cbn, NOW())",
            [
                ':p'  => $projectId, ':seq' => $nextSeq,
                ':t'  => $title,
                ':ds' => (string)($d['description'] ?? ''),
                ':td' => !empty($d['target_date']) ? (string)$d['target_date'] : null,
                ':sl' => $slot,
                ':oi' => (int)($d['owner_user_id'] ?? 0),
                ':on' => (string)($d['owner_user_name'] ?? ''),
                ':st' => $status,
                ':cb' => $userId, ':cbn' => $userName,
            ]
        );
        return (int)($fb['last_id'] ?? 0);
    }

    /**
     * Update — the PM/Admin can change anything; the milestone owner can only flip status + add notes.
     */
    public function update(int $id, array $d, int $userId, string $userName, string $userGroup): void
    {
        $m = $this->find($id);
        if (!$m) throw new RuntimeException('Milestone not found.');
        $g = strtoupper(trim($userGroup));
        $isFullEdit = in_array($g, ['PROJECT MANAGER','ADMINISTRATOR','PROJECTS COORDINATOR'], true);
        $isOwner    = (int)$m['owner_user_id'] === $userId && $userId > 0;
        if (!$isFullEdit && !$isOwner) {
            throw new RuntimeException('You cannot edit this milestone.');
        }

        $status = strtoupper((string)($d['status'] ?? $m['status']));
        if (!in_array($status, self::STATUSES, true)) $status = $m['status'];

        $db = new DB();
        if ($isFullEdit) {
            $slot = strtoupper((string)($d['owner_slot'] ?? ''));
            if ($slot !== '' && !in_array($slot, self::SLOTS, true)) $slot = '';
            $db->insertUpdate(
                "UPDATE c_project_milestones SET
                    title = :t, description = :ds, target_date = :td,
                    owner_slot = :sl, owner_user_id = :oi, owner_user_name = :on,
                    status = :st,
                    completed_on   = CASE WHEN :st2 = 'DONE' AND status <> 'DONE' THEN NOW() WHEN :st3 <> 'DONE' THEN NULL ELSE completed_on END,
                    completed_by   = CASE WHEN :st4 = 'DONE' AND status <> 'DONE' THEN :u    WHEN :st5 <> 'DONE' THEN NULL ELSE completed_by END,
                    completed_by_name = CASE WHEN :st6 = 'DONE' AND status <> 'DONE' THEN :un WHEN :st7 <> 'DONE' THEN NULL ELSE completed_by_name END,
                    edited_by = :u, edited_on = NOW()
                  WHERE c0d = :id",
                [
                    ':t'  => trim((string)($d['title'] ?? $m['title'])),
                    ':ds' => (string)($d['description'] ?? $m['description']),
                    ':td' => !empty($d['target_date']) ? (string)$d['target_date'] : null,
                    ':sl' => $slot,
                    ':oi' => (int)($d['owner_user_id'] ?? 0),
                    ':on' => (string)($d['owner_user_name'] ?? ''),
                    ':st' => $status, ':st2' => $status, ':st3' => $status, ':st4' => $status, ':st5' => $status, ':st6' => $status, ':st7' => $status,
                    ':u'  => $userId, ':un' => $userName,
                    ':id' => $id,
                ]
            );
        } else {
            // Owner only: status flip.
            $db->insertUpdate(
                "UPDATE c_project_milestones SET
                    status = :st,
                    completed_on = CASE WHEN :st2 = 'DONE' THEN NOW() ELSE NULL END,
                    completed_by = CASE WHEN :st3 = 'DONE' THEN :u    ELSE NULL END,
                    completed_by_name = CASE WHEN :st4 = 'DONE' THEN :un ELSE NULL END,
                    edited_by = :u, edited_on = NOW()
                  WHERE c0d = :id",
                [
                    ':st' => $status, ':st2' => $status, ':st3' => $status, ':st4' => $status,
                    ':u'  => $userId, ':un' => $userName,
                    ':id' => $id,
                ]
            );
        }
    }

    public function softDelete(int $id, int $userId, string $userGroup): void
    {
        $m = $this->find($id);
        if (!$m) throw new RuntimeException('Milestone not found.');
        $g = strtoupper(trim($userGroup));
        if (!in_array($g, ['PROJECT MANAGER','ADMINISTRATOR','PROJECTS COORDINATOR'], true)
            && (int)$m['created_by'] !== $userId) {
            throw new RuntimeException('You cannot delete this milestone.');
        }
        $db = new DB();
        $db->insertUpdate("UPDATE c_project_milestones SET deleted = 1, edited_on = NOW() WHERE c0d = :id", [':id' => $id]);
    }
}
