<?php

namespace App\Controllers\View;

class BadgesController extends PrintablesBaseController
{
    //Badges
    public function badge()
    {
        // 1) Collect & sanitize IDs
        $userIds = $this->request->getPost('user_ids') ?? [];
        if (!is_array($userIds)) $userIds = [$userIds];

        $userIds = array_values(array_filter(array_map(static function ($v) {
            $v = is_string($v) ? trim($v) : $v;
            return ($v !== '' && $v !== null) ? (int)$v : null;
        }, $userIds), static fn($v) => $v !== null));
        $userIds = array_values(array_unique($userIds));

        // 2) Consistent school_year
        $schoolYear = $this->request->getPost('school_year')
            ?? $this->request->getGet('school_year')
            ?? ($this->schoolYear ?? null);

        // 2a) Posted maps from the view
        $rolesMap   = $this->request->getPost('roles')   ?? [];
        $classesMap = $this->request->getPost('classes') ?? [];
        if (!is_array($rolesMap))   $rolesMap = [];
        if (!is_array($classesMap)) $classesMap = [];

        // 2b) Normalizer to mirror the view's formatting
        $formatRole = static function (?string $role): string {
            $role = (string)$role;
            $role = str_replace(['-', '_'], ' ', $role);
            $role = preg_replace('/\s+/', ' ', trim($role));
            if ($role === '') return '';
            $out = [];
            foreach (explode(' ', $role) as $w) {
                if ($w === '') continue;
                if (preg_match('/^[A-Za-z]{1,3}$/', $w)) {
                    $out[] = strtoupper($w);
                } else {
                    $out[] = ucfirst(strtolower($w));
                }
            }
            return implode(' ', $out);
        };

        // 3) PDF setup
        $pdf = new \FPDF('P', 'mm', 'A4');
        $pdf->SetAutoPageBreak(false);

        // Layout: 2 cols - 5 rows = 10 per page
        $badgeW = 95;
        $badgeH = 67;
        $marginL = 14;
        $marginT = 6;
        $gutterX = 0;
        $gutterY = 0;
        $cols = 2;
        $rows = 4;
        $perPage = $cols * $rows;

        $pagesAdded = 0;
        $i = 0;

        $norm = static function (?string $s): string {
            $s = (string)$s;
            $s = str_replace(["\xC2\xA0", "\xA0"], ' ', $s);
            $s = preg_replace('/\s+/u', ' ', $s) ?? $s;
            $s = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $s) ?? $s;
            return trim($s);
        };

        $resolveRole = static function (array $info) use ($norm): string {
            $candidates = [
                'role',
                'active_role',
                'role_name',
                function ($r) {
                    if (!empty($r['roles'])) {
                        $parts = array_filter(array_map('trim', explode(',', (string)$r['roles'])));
                        return $parts[0] ?? '';
                    }
                    return '';
                },
                'job_title',
                'title',
                'position',
                'staff_role',
                'department_role',
                'dept_role'
            ];
            foreach ($candidates as $key) {
                if (is_callable($key)) {
                    $v = $key($info);
                    if ($norm($v) !== '') return $norm($v);
                } else {
                    if (!empty($info[$key])) {
                        $v = $norm((string)$info[$key]);
                        if ($v !== '') return $v;
                    }
                }
            }
            return 'STAFF';
        };

        $seen = [];

        foreach ($userIds as $uid) {
            $info = $this->getUserInfoById($uid, $schoolYear);
            if (!$info || !is_array($info)) continue;

            $userId = isset($info['user_id']) ? (int)$info['user_id']
                : (isset($info['id']) ? (int)$info['id']
                    : (isset($info['users.id']) ? (int)$info['users.id'] : $uid));

            $name = $norm($info['name'] ?? (($info['firstname'] ?? '') . ' ' . ($info['lastname'] ?? '')));

            // Prefer posted formatted role; else fallback and format server-side
            $postedRole   = $rolesMap[$userId] ?? null;
            $roleResolved = $postedRole !== null ? $postedRole : $resolveRole($info);
            $roleResolved = $formatRole((string)$roleResolved);
            $info['role_resolved'] = $roleResolved;

            // Prefer posted class name if provided (keeps what the user saw)
            if (!empty($classesMap[$userId])) {
                $info['class_section_name'] = (string)$classesMap[$userId];
            }

            $class = $norm($info['class_section_name'] ?? '');

            $badgeKey = strtolower(trim($userId . '|' . $name . '|' . $roleResolved . '|' . $class));
            if (isset($seen[$badgeKey])) continue;
            $seen[$badgeKey] = true;

            if (($i % $perPage) === 0) {
                $pdf->AddPage();
                $pagesAdded++;
            }

            $indexOnPage = $i % $perPage;
            $row = intdiv($indexOnPage, $cols);
            $col = $indexOnPage % $cols;

            $x = $marginL + $col * ($badgeW + $gutterX);
            $y = $marginT + $row * ($badgeH + $gutterY);

            // drawBadgeInCell expects 'role_resolved' and (optionally) 'class_section_name'
            $this->drawBadgeInCell($pdf, $info, $x, $y, $badgeW, $badgeH);
            $i++;
        }

        if ($pagesAdded === 0) {
            $pdf->AddPage();
            $pdf->SetFont('Arial', '', 10);
            $pdf->SetXY(10, 20);
            $pdf->MultiCell(0, 6, "No valid staff selected or data not found.", 0, 'L');
        }

        if (ob_get_length()) {
            ob_end_clean();
        }
        $pdfString = $pdf->Output('S');

        // 4) Track prints (best-effort; swallow errors if table missing)
        try {
            $actorId = (int) (session()->get('user_id') ?? 0) ?: null;
            $this->badgePrintLogModel->logPrints(
                $userIds,
                $actorId,
                is_string($schoolYear) ? $schoolYear : null,
                is_array($rolesMap) ? $rolesMap : [],
                is_array($classesMap) ? $classesMap : [],
                1
            );
        } catch (\Throwable $e) {
            log_message('error', 'Failed to log badge prints: ' . $e->getMessage());
        }

        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', 'inline; filename="Staff_Badges.pdf"')
            ->setBody($pdfString);
    }

    /**
     * API: Return print status for given users (count + last print time).
     * GET: user_ids[] or CSV in user_ids, optional school_year
     * Response: { ok: true, data: { <id>: { count, last_printed_at, last_printed_by } } }
     */
    public function badgePrintStatus()
    {
        $ids = $this->request->getGet('user_ids');
        if (is_string($ids)) {
            $ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen');
        }
        if (!is_array($ids)) $ids = $ids ? [$ids] : [];
        $ids = array_values(array_unique(array_map(static fn($v) => (int)$v, $ids)));

        $schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear;

        try {
            $status = $this->badgePrintLogModel->getStatus($ids, is_string($schoolYear) ? $schoolYear : null);
        } catch (\Throwable $e) {
            return $this->response->setJSON(['ok' => false, 'error' => 'Failed to query status']);
        }

        return $this->response->setJSON([
            'ok'         => true,
            'data'       => $status,
            'csrf_token' => csrf_token(),
            'csrf_hash'  => csrf_hash(),
        ]);
    }

    /**
     * API: Log a set of prints explicitly (optional; PDF endpoint already logs).
     * POST: user_ids[], optional school_year, roles[ID], classes[ID]
     */
    public function logBadgePrint()
    {
        $ids = $this->request->getPost('user_ids') ?? [];
        if (!is_array($ids)) $ids = $ids ? [$ids] : [];
        $ids = array_values(array_unique(array_map(static fn($v) => (int)$v, $ids)));

        $roles  = $this->request->getPost('roles')   ?? [];
        $class  = $this->request->getPost('classes') ?? [];
        if (!is_array($roles)) $roles = [];
        if (!is_array($class)) $class = [];

        $schoolYear = $this->request->getPost('school_year') ?? $this->schoolYear;
        $actorId    = (int) (session()->get('user_id') ?? 0) ?: null;

        try {
            $cnt = $this->badgePrintLogModel->logPrints(
                $ids,
                $actorId,
                is_string($schoolYear) ? $schoolYear : null,
                $roles,
                $class,
                1
            );
            return $this->response->setJSON([
                'ok' => true,
                'inserted' => $cnt,
                'csrf_token' => csrf_token(),
                'csrf_hash'  => csrf_hash(),
            ]);
        } catch (\Throwable $e) {
            return $this->response->setJSON(['ok' => false, 'error' => $e->getMessage()]);
        }
    }

    protected function drawBadgeInCell(\FPDF $pdf, array $data, float $x, float $y, float $w, float $h): void
    {
        // Background & border
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, $y, $w, $h, 'F');
        $pdf->SetDrawColor(200, 200, 200);
        $pdf->Rect($x, $y, $w, $h);

        // Logos
        $logoSize = 6;
        if (!empty($data['school_logo']) && file_exists($data['school_logo'])) {
            $pdf->Image($data['school_logo'], $x + $w - 6 - $logoSize, $y + 4, $logoSize + 3, $logoSize - 1);
        }
        if (!empty($data['isgl_logo']) && file_exists($data['isgl_logo'])) {
            $pdf->Image($data['isgl_logo'], $x + 4, $y + 4, $logoSize + 3, $logoSize - 1);
        }

        // Helpers
        $padX    = 4;
        $cursorY = $y + 4 + $logoSize + 2;

        $norm = static function (?string $s): string {
            $s = (string)$s;
            $s = str_replace(["\xC2\xA0", "\xA0"], ' ', $s);
            $s = preg_replace('/\s+/u', ' ', $s) ?? $s;
            $s = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $s) ?? $s;
            return trim($s);
        };
        $toPdf = static function (string $s): string {
            if (function_exists('iconv')) {
                $o = @iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $s);
                if ($o !== false && $o !== '') return $o;
            }
            if (function_exists('mb_convert_encoding')) {
                $o = @mb_convert_encoding($s, 'ISO-8859-1', 'UTF-8');
                if ($o !== '') return $o;
            }
            $o = @utf8_decode($s);
            return $o !== '' ? $o : 'STAFF';
        };
        $fitText = static function (\FPDF $pdf, string $text, int $startSize, int $minSize, float $maxWidth): int {
            $size = $startSize;
            while ($size >= $minSize) {
                $pdf->SetFontSize($size);
                if ($pdf->GetStringWidth($text) <= $maxWidth) return $size;
                $size--;
            }
            return $minSize;
        };

        // === Role formatter (fixes "OF" -> "Of", "HEAD" -> "Head"; keeps TA/PTA/HR/KG/IT/DEPT in ALL-CAPS) ===
        $formatRole = static function (string $role): string {
            // Normalize separators & spaces
            $role = str_replace(['-', '_'], ' ', $role);
            $role = preg_replace('/\s+/u', ' ', trim($role));
            if ($role === '') return '';

            // Special-casing map (case-insensitive keys)
            // - "of" must be lower-case
            // - "head" should be Title-case
            $special = [
                'of'   => 'of',
                'head' => 'Head',
            ];

            $tokens = explode(' ', $role);
            $out = [];

            foreach ($tokens as $tok) {
                if ($tok === '') continue;

                // Strip leading/trailing NON-letters for the decision; reattach afterwards
                $core  = preg_replace('/^\P{L}+|\P{L}+$/u', '', $tok);
                $start = strpos($tok, $core);
                if ($start === false) { // no letter content
                    $out[] = ucfirst(mb_strtolower($tok, 'UTF-8'));
                    continue;
                }
                $pre = substr($tok, 0, $start);
                $suf = substr($tok, $start + strlen($core));

                $lw = mb_strtolower($core, 'UTF-8');

                if (isset($special[$lw])) {
                    // Use the exact desired casing from the map
                    $coreFmt = $special[$lw];               // "of" -> "of", "head" -> "Head"
                } elseif (preg_match('/^\p{L}{1,4}$/u', $core)) {
                    // 1-4 letters -> ALL CAPS (TA, PTA, HR, KG, IT, DEPT)
                    $coreFmt = mb_strtoupper($core, 'UTF-8');
                } else {
                    // Title-case longer words
                    $coreFmt = preg_replace_callback(
                        '/\p{L}+/u',
                        static fn($m) => mb_strtoupper(mb_substr($m[0], 0, 1, 'UTF-8'), 'UTF-8')
                            . mb_strtolower(mb_substr($m[0], 1, null, 'UTF-8'), 'UTF-8'),
                        mb_strtolower($core, 'UTF-8')
                    );
                }

                $out[] = $pre . $coreFmt . $suf;
            }

            return implode(' ', $out);
        };

        // Class label formatter (KG stays KG; Youth title-cased; otherwise pass through)
        $formatClass = static function (string $class): string {
            $c = trim($class);
            if ($c === '') return '';
            $lc = strtolower($c);
            if ($lc === 'kg' || $lc === 'kindergarten') return 'KG';
            if ($lc === 'youth') return 'Youth';
            return $c; // e.g., "1-A", "3", "HS-2"
        };

        // School name
        $schoolName = strtoupper($norm($data['school_name'] ?? 'AL RAHMA SUNDAY SCHOOL'));
        $pdf->SetFont('Arial', '', 16);
        $pdf->SetXY($x + $padX, $cursorY);
        $pdf->MultiCell($w - 2 * $padX, 5, $toPdf($schoolName), 0, 'C');

        // Name
        $pdf->Ln(9);
        $pdf->SetFont('Arial', 'B', 14);
        $pdf->SetX($x + $padX);
        $name = strtoupper($norm($data['name'] ?? (($data['firstname'] ?? '') . ' ' . ($data['lastname'] ?? 'STAFF'))));
        $pdf->Cell($w - 2 * $padX, 6, $toPdf($name), 0, 1, 'C');

        // Role (prefer preformatted; then fix with formatter to guarantee "Of"/"Head" etc.)
        $roleResolved = $norm((string)($data['role_resolved'] ?? ''));
        if ($roleResolved === '') {
            // fallback: find first available role-like field
            $candidates = [
                'role',
                'active_role',
                'role_name',
                function ($r) {
                    if (!empty($r['roles'])) {
                        $parts = array_filter(array_map('trim', explode(',', (string)$r['roles'])));
                        return $parts[0] ?? '';
                    }
                    return '';
                },
                'job_title',
                'title',
                'position',
                'staff_role',
                'department_role',
                'dept_role'
            ];
            foreach ($candidates as $key) {
                $v = '';
                if (is_callable($key)) {
                    $v = (string)$key($data);
                } elseif (!empty($data[$key])) {
                    $v = (string)$data[$key];
                }
                $v = $norm($v);
                if ($v !== '') {
                    $roleResolved = $v;
                    break;
                }
            }
        }
        // Final fix to ensure exceptions and acronyms are correct even if upstream passed "OF"
        if ($roleResolved !== '') {
            $roleResolved = $formatRole($roleResolved);
        }

        // Class
        $classRaw = $norm($data['class_section_name'] ?? '');
        $class    = $formatClass($classRaw);

        // Teacher-ish detection (lowercased source; do NOT change printed casing)
        $detectSrc = strtolower($norm(
            ($data['roles_raw'] ?? '') !== '' ? $data['roles_raw'] : (($data['role_name_raw'] ?? '') !== '' ? $data['role_name_raw'] : $roleResolved)
        ));
        $isTeacherish = (strpos($detectSrc, 'teacher') !== false) || preg_match('/\bta\b/', $detectSrc);

        // Compose final display (no re-casing beyond $formatRole)
        if ($isTeacherish && $class !== '') {
            if (strtolower($class) === 'youth') {
                $display = 'Youth ' . $roleResolved;
            } elseif ($class === 'KG') {
                $display = 'KG ' . $roleResolved;
            } else {
                $display = 'Grade ' . $class . ' ' . $roleResolved;
            }
        } elseif ($roleResolved !== '') {
            $display = $roleResolved;
        } elseif ($class !== '') {
            $display = $class;
        } else {
            $display = 'STAFF';
        }

        // Print role/class
        $pdf->Ln(6);
        $pdf->SetFont('Arial', '', 14);
        $displayOut   = $toPdf($display);
        $maxTextWidth = $w - 2 * $padX;
        $best = $fitText($pdf, $displayOut, 11, 7, $maxTextWidth);
        $pdf->SetFont('Arial', '', $best + 4);
        if ($pdf->GetStringWidth($displayOut) <= $maxTextWidth) {
            $pdf->SetX($x + $padX);
            $pdf->Cell($maxTextWidth, 5, $displayOut, 0, 1, 'C');
        } else {
            $pdf->SetX($x + $padX);
            $pdf->MultiCell($maxTextWidth, 5, $displayOut, 0, 'C');
        }

        // Footer: year
        $footerYear = $norm((string)($this->schoolYear ?? ($data['school_year'] ?? '')));
        if ($footerYear !== '') {
            $pdf->SetFont('Arial', 'I', 14);
            $pdf->SetXY($x + $padX, $y + $h - 9);
            $pdf->Cell($w - 2 * $padX, 4, $toPdf($footerYear), 0, 0, 'C');
        }
    }

    // ---- Helper: get staff rows (no joins) ----
    private function fetchStaffList(?string $schoolYear, array $selectedUserIds = []): array
    {
        // $this->userModel should be injected/constructed already
        return $this->userModel->getNoParentUsersWithRole($schoolYear, $selectedUserIds);
    }

    // ---- Helper: is this person teacher-ish? (role_name can be CSV) ----
    private static function isTeacherish(string $roleCsv): bool
    {
        $roles = array_filter(array_map('trim', explode(',', strtolower($roleCsv))));
        foreach ($roles as $r) {
            if ($r === 'teacher' || $r === 'teacher_assistant' || $r === 'teacher assistant') {
                return true;
            }
        }
        return false;
    }

    // ---- Helper: return ONE latest assignment (pref Teacher, else Assistant) and its class name ----
    private function getLatestClassForUser(int $userId, ?string $schoolYear = null): ?array
    {
        // Build a single query that considers teacher or assistant rows,
        $qb = $this->db->table('teacher_class tc')
            ->select('tc.class_section_id, cs.class_section_name')
            ->join('classSection cs', 'cs.class_section_id = tc.class_section_id', 'left')
            ->where('tc.teacher_id', $userId);

        if (!empty($schoolYear)) {
            $qb->where('tc.school_year', $schoolYear);
        }

        $qb->orderBy('FIELD(tc.position, "main", "ta")', '', false)
            ->orderBy('IFNULL(tc.updated_at,"0000-00-00 00:00:00") DESC', '', false)
            ->limit(1);


        if (!empty($schoolYear)) {
            $qb->where('tc.school_year', $schoolYear);
        }

        // Prefer a TEACHER row if one exists, then the latest update.
        // MySQL treats boolean expressions as 0/1, so we can order by it.
        $qb->orderBy('(tc.teacher_id = ' . $this->db->escape($userId) . ') DESC', '', false)
            ->orderBy('IFNULL(tc.updated_at,"0000-00-00 00:00:00") DESC, tc.id DESC', '', false)
            ->limit(1);

        $row = $qb->get()->getRowArray();
        if (!$row || empty($row['class_section_id'])) {
            return null;
        }

        return [
            'class_section_id'   => (int) $row['class_section_id'],
            'class_section_name' => $row['class_section_name'] ?? null,
        ];
    }

    // ---- Controller: badgeForm (simple + deterministic) ----
    public function badgeForm()
    {
        $request = $this->request;

        $schoolYear = $this->schoolYear;
        if ($this->schoolYear === null || $schoolYear === '') {
            $schoolYear = $this->schoolYear ?? null;
        }

        // --- Role formatting helpers ---
        $formatRole = static function (string $role): string {
            // Normalize separators & spaces
            $role = str_replace(['-', '_'], ' ', $role);
            $role = preg_replace('/\s+/u', ' ', trim($role));
            if ($role === '') return '';

            // Special-casing map (case-insensitive keys)
            // - "of" must be lower-case
            // - "head" should be Title-case
            $special = [
                'of'   => 'of',
                'head' => 'Head',
            ];

            $tokens = explode(' ', $role);
            $out = [];

            foreach ($tokens as $tok) {
                if ($tok === '') continue;

                // Strip leading/trailing NON-letters for the decision; reattach afterwards
                $core  = preg_replace('/^\P{L}+|\P{L}+$/u', '', $tok);
                $start = strpos($tok, $core);
                if ($start === false) { // no letter content
                    $out[] = ucfirst(mb_strtolower($tok, 'UTF-8'));
                    continue;
                }
                $pre = substr($tok, 0, $start);
                $suf = substr($tok, $start + strlen($core));

                $lw = mb_strtolower($core, 'UTF-8');

                if (isset($special[$lw])) {
                    // Use the exact desired casing from the map
                    $coreFmt = $special[$lw];               // "of" -> "of", "head" -> "Head"
                } elseif (preg_match('/^\p{L}{1,4}$/u', $core)) {
                    // 1-4 letters -> ALL CAPS (TA, PTA, HR, KG, IT, DEPT)
                    $coreFmt = mb_strtoupper($core, 'UTF-8');
                } else {
                    // Title-case longer words
                    $coreFmt = preg_replace_callback(
                        '/\p{L}+/u',
                        static fn($m) => mb_strtoupper(mb_substr($m[0], 0, 1, 'UTF-8'), 'UTF-8')
                            . mb_strtolower(mb_substr($m[0], 1, null, 'UTF-8'), 'UTF-8'),
                        mb_strtolower($core, 'UTF-8')
                    );
                }

                $out[] = $pre . $coreFmt . $suf;
            }

            return implode(' ', $out);
        };


        $formatRolesCsv = static function ($csv) use ($formatRole): string {
            if ($csv === null) return '';
            $parts = array_filter(array_map('trim', explode(',', (string)$csv)), 'strlen');
            if (empty($parts)) return '';
            $parts = array_map($formatRole, $parts);
            return implode(', ', $parts);
        };

        // --- 2) Selected user IDs: accept array, single value, or CSV ---
        $selectedUsers = $request->getGet('user_ids');
        if (is_string($selectedUsers)) {
            // Handle CSV like "12,34,56"
            $selectedUsers = array_filter(array_map('trim', explode(',', $selectedUsers)), 'strlen');
        }
        if (!is_array($selectedUsers)) {
            $selectedUsers = $selectedUsers ? [$selectedUsers] : [];
        }
        // Normalize to unique ints
        $selectedUserIds = array_values(array_unique(array_map(static function ($v) {
            return (int) $v;
        }, $selectedUsers)));

        // --- 3) Base staff rows (non-parent users) ---
        // fetchStaffList() should call your model method getNoParentUsersWithRole($schoolYear, $selectedUserIds)
        $users = $this->fetchStaffList($schoolYear, $selectedUserIds);

        // Normalize keys so the view logic is consistent:
        // Expect at least: id (or users.id), firstname, lastname, email, roles (CSV of non-parent roles)
        foreach ($users as &$u) {
            // Ensure user_id exists
            if (!isset($u['user_id'])) {
                if (isset($u['users.id'])) {
                    $u['user_id'] = (int) $u['users.id'];
                } elseif (isset($u['id'])) {
                    $u['user_id'] = (int) $u['id'];
                } elseif (isset($u['users_id'])) {
                    $u['user_id'] = (int) $u['users_id'];
                }
            }

            // Ensure roles string exists (model may already provide it as 'roles')
            $rolesStr = $u['roles'] ?? '';
            if ($rolesStr === '' && isset($u['role_name'])) {
                // fallback if model returned single role
                $rolesStr = (string) $u['role_name'];
            }

            // Keep raw + add formatted CSV
            $u['roles_raw'] = $rolesStr;
            $u['roles']     = $formatRolesCsv($rolesStr);

            // Provide a role_name for legacy view code (pick a representative non-parent role)
            if (empty($u['role_name'])) {
                $firstRole = '';
                if ($rolesStr !== '') {
                    $parts = array_filter(array_map('trim', explode(',', $rolesStr)));
                    $firstRole = $parts[0] ?? '';
                }
                $u['role_name'] = $firstRole;
            }

            // Keep raw + add formatted single role name
            $u['role_name_raw'] = $u['role_name'];
            $u['role_name']     = $formatRole((string)$u['role_name']);

            // Defaults for class assignment (only filled for teacherish)
            $u['class_section_id']   = $u['class_section_id']   ?? null;
            $u['class_section_name'] = $u['class_section_name'] ?? null;
        }
        unset($u);

        // --- 4) For teachers/assistants, fetch latest assignment in this year ---
        foreach ($users as &$u) {
            // Use RAW roles for detection to avoid any formatting side-effects
            $rolesDetect = strtolower($u['roles_raw'] ?? ($u['role_name_raw'] ?? ''));
            $isTeacherish = (strpos($rolesDetect, 'teacher') !== false) || preg_match('/\bta\b/', $rolesDetect);

            if ($isTeacherish && !empty($u['user_id'])) {
                $assign = $this->getLatestClassForUser((int) $u['user_id'], $schoolYear);
                if ($assign) {
                    $u['class_section_id']   = $assign['class_section_id']   ?? null;
                    $u['class_section_name'] = $assign['class_section_name'] ?? null;
                }
            }
        }
        unset($u);

        // --- 5) Build list of available school years (robust) ---
        $db = \Config\Database::connect();
        $schoolYears = [];

        // Prefer teacher_class if available
        try {
            if (method_exists($db, 'tableExists') ? $db->tableExists('teacher_class') : true) {
                $q1 = $db->table('teacher_class')
                    ->select('DISTINCT school_year', false)
                    ->where('school_year IS NOT NULL', null, false)
                    ->orderBy('school_year', 'DESC')
                    ->get()
                    ->getResultArray();
                $schoolYears = array_column($q1, 'school_year');
            }
        } catch (\Throwable $e) {
            // ignore and try fallback
        }

        // Fallback to user_roles if teacher_class didn't produce anything
        if (empty($schoolYears)) {
            try {
                if (method_exists($db, 'tableExists') ? $db->tableExists('user_roles') : true) {
                    $fields = $db->getFieldNames('user_roles');
                    if (in_array('school_year', $fields, true)) {
                        $q2 = $db->table('user_roles')
                            ->select('DISTINCT school_year', false)
                            ->where('school_year IS NOT NULL', null, false)
                            ->orderBy('school_year', 'DESC')
                            ->get()
                            ->getResultArray();
                        $schoolYears = array_column($q2, 'school_year');
                    }
                }
            } catch (\Throwable $e) {
                // swallow; leave $schoolYears empty
            }
        }

        // --- 6) Active role tab (defensive default for the view) ---
        $rolesTabs = [
            'teacher' => 'Teachers',
            'ta'      => 'Teacher Assistants',
            'admin'   => 'Admins',
            'staff'   => 'Staff',
        ];
        $requestedRole = $request->getGet('active_role') ?? $request->getPost('active_role') ?? null;
        $activeRole    = array_key_exists((string)$requestedRole, $rolesTabs) ? (string)$requestedRole : 'teacher';

        // --- 7) Pack data for the view ---
        $data = [
            'users'            => $users,
            'schoolYears'      => $schoolYears,
            'selectedYear'     => $schoolYear,
            'selectedUserIds'  => $selectedUserIds,
            'rolesTabs'        => $rolesTabs,
            'active_role'      => $activeRole,   // prevents "Undefined array key 'active_role'"
        ];

        return view('printables_reports/badge_form', $data);
    }

    protected function getUserInfoById($id, $schoolYear = null)
    {
        // ---- A) Resolve the user row
        $u = $this->db->table('users')
            ->select('id AS user_id, firstname, lastname')
            ->where('id', $id)
            ->limit(1)
            ->get()->getRowArray();

        if (!$u) {
            return null;
        }

        $userId   = (int) $u['user_id'];
        $fullname = trim(($u['firstname'] ?? '') . ' ' . ($u['lastname'] ?? ''));

        // ---- B) Get roles (non-parent) for this user
        $rolesRows = $this->db->table('user_roles ur')
            ->select('r.id AS role_id, r.name AS role_name')
            ->join('roles r', 'r.id = ur.role_id', 'inner')
            ->where('ur.user_id', $userId)
            ->get()->getResultArray();

        $allRoles = [];
        foreach ($rolesRows as $rr) {
            $name = trim((string)($rr['role_name'] ?? ''));
            if ($name !== '' && strtolower($name) !== 'parent') {
                // normalize to "Ucwords"
                $allRoles[] = ucwords(strtolower($name));
            }
        }
        $allRoles = array_values(array_unique($allRoles));
        $rolesCsv = $allRoles ? implode(', ', $allRoles) : '';

        // ---- C) Latest assignment from teacher_class
        $tc = $this->db->table('teacher_class')
            ->select('class_section_id, updated_at, id, position, school_year')
            ->where('teacher_id', $userId);

        if (!empty($schoolYear)) {
            $tc->where('school_year', $schoolYear);
        }

        $assignment = $tc->orderBy('IFNULL(updated_at, "1970-01-01 00:00:00") DESC, id DESC', '', false)
            ->limit(1)
            ->get()->getRowArray();

        $classId   = $assignment['class_section_id'] ?? null;
        $position  = strtolower(trim((string)($assignment['position'] ?? '')));
        $className = null;

        if (!empty($classId)) {
            $className = $this->resolveClassName($this->db, $classId);
        }

        // ---- D) Decide primary role label
        $roleLabel = '';
        if ($position === 'ta' || $position === 'teacher_assistant' || $position === 'assistant') {
            $roleLabel = 'Teacher Assistant';
        } elseif ($position === 'teacher') {
            $roleLabel = 'Teacher';
        } elseif (!empty($allRoles)) {
            // Use first role from normalized list
            $roleLabel = $allRoles[0];
        } else {
            $roleLabel = 'Staff';
        }

        // ---- E) Logos
        $schoolLogo = $this->firstExisting([
            FCPATH . 'assets/images/school_logo.png',
            FCPATH . 'assets/images/logo.png',
        ]);
        $isglLogo = $this->firstExisting([
            FCPATH . 'assets/images/isgl_logo.png',
            FCPATH . 'assets/images/isgl.png',
        ]);

        // ---- F) Build job title / display title
        $jobTitle = '';
        if (!empty($roleLabel) && !empty($className)) {
            $jobTitle = "{$roleLabel} - {$className}";
        } elseif (!empty($roleLabel)) {
            $jobTitle = $roleLabel;
        } elseif (!empty($className)) {
            $jobTitle = $className;
        }

        return [
            'name'               => $fullname !== '' ? $fullname : 'STAFF',
            'role'               => $roleLabel,                 // primary label
            'roles'              => $rolesCsv,                  // all roles (comma-separated)
            'class_section_id'   => $classId ? (int)$classId : null,
            'class_section_name' => $className,
            'job_title'          => $jobTitle,
            'school_name'        => 'Al Rahma Sunday School',
            'school_id'          => $userId,
            'school_logo'        => $schoolLogo,
            'isgl_logo'          => $isglLogo,
            'school_year'        => $assignment['school_year'] ?? ($schoolYear ?? null),
        ];
    }
}
