<?php

namespace App\Controllers\View;

use App\Controllers\BaseController; // Models 
use App\Models\StudentModel;
use App\Models\ClassSectionModel;
use App\Models\UserModel;
use App\Models\EnrollmentModel;
use App\Models\ParentModel;
use App\Models\ConfigurationModel;
use App\Models\WhatsappGroupLinkModel;
use App\Models\WhatsappInviteLogModel;
use App\Models\WhatsappGroupMembershipModel;
use CodeIgniter\Events\Events;

/**
 * WhatsApp invites controller — rewritten to:
 *  - Build initial lists from student_class (sections) and users/parents (parents)
 *  - Parents mode: given a parent id, find their students -> classSectionIds -> links -> send
 *  - Class mode: given classSectionId, find students -> parents (primary+second) -> send link
 *  - All mode: iterate all classSectionIds in student_class, gather recipients -> send per parent
 */
class WhatsappController extends BaseController
{
    protected $db;
    protected $configModel;
    protected $semester;
    protected $schoolYear;
    /** cache for presence of roster rows per term */
    private array $rosterPresenceCache = [];
    protected ParentModel $parentsModel;
    protected StudentModel $studentModel;
    protected ClassSectionModel $classSectionModel;
    protected UserModel $userModel;
    protected EnrollmentModel $enrollmentModel;
    protected WhatsappGroupLinkModel $linkModel;
    protected WhatsappInviteLogModel $logModel;
    protected WhatsappGroupMembershipModel $membershipModel;

    public function __construct()
    {
        helper('url');
        // Load the database service
        $this->db = \Config\Database::connect();
        // Init models
        $this->userModel         = new UserModel();
        $this->studentModel      = new StudentModel();
        $this->classSectionModel = new ClassSectionModel();
        $this->linkModel         = new WhatsappGroupLinkModel();
        $this->membershipModel   = new WhatsappGroupMembershipModel();
        $this->configModel = new ConfigurationModel();

        $this->semester = $this->configModel->getConfig('semester');
        $this->schoolYear = $this->configModel->getConfig('school_year');
    }

    /**
     * Admin page: pre-load ALL parents (primaries + second-parents) and ALL sections (from student_class).
     */
    public function index()
    {
        // This page is year-only: ignore semester filters.
        $semester = '';
        [$parents, $sections] = $this->prepareIndexData($this->schoolYear, $semester);

        // Build links map for small UI hints/badges
        $links = $this->linkModel->getAllForTerm($this->schoolYear, $semester);
        $linksBySection = [];
        foreach ($links as $l) {
            $cid = (int) ($l['class_section_id'] ?? 0);
            if ($cid && !isset($linksBySection[$cid])) {
                $linksBySection[$cid] = $l;
            }
        }

        return view('whatsapp/manage_links', [
            'sections'       => $sections,        // [ ['class_section_id','class_section_name'], ... ]
            'parents'        => $parents,         // [ ['id','firstname','lastname','email','source'], ... ]
            'linksBySection' => $linksBySection,  // [class_section_id => linkRow]
            'schoolYear'     => $this->schoolYear,
            'semester'       => $semester,
        ]);
    }

    /**
     * Create/update a WhatsApp link for a class section.
     */
    public function saveLink()
    {
        $sectionId   = (int) $this->request->getPost('class_section_id');
        $sectionName = (string) ($this->request->getPost('class_section_name') ?? '');
        $inviteLink  = (string) $this->request->getPost('invite_link');
        $active      = (int) ($this->request->getPost('active') ? 1 : 0);

        if (!$sectionId || !$inviteLink) {
            return redirect()->back()->with('error', 'Class and link are required.');
        }

        // Resolve display name if not provided
        if ($sectionName === '') {
            $row = $this->linkModel->getLinkForSection($sectionId, $this->schoolYear, $this->semester);
            $sectionName = $row['class_section_name'] ?? ('Section ' . $sectionId);
        }

        $existing = $this->linkModel->where([
            'class_section_id' => $sectionId,
            'school_year'      => $this->schoolYear,
            'semester'         => $this->semester,
        ])->first();

        $payload = [
            'class_section_id'   => $sectionId,
            'class_section_name' => $sectionName,
            'school_year'        => $this->schoolYear,
            'semester'           => $this->semester,
            'invite_link'        => trim($inviteLink),
            'active'             => $active,
        ];

        if ($existing) {
            $this->linkModel->update($existing['id'], $payload);
        } else {
            $this->linkModel->insert($payload);
        }

        return redirect()->back()->with('success', 'WhatsApp group link saved.');
    }

    /**
     * Send invites in three modes: 'parents' | 'class' | 'all'
     * POST fields:
     *  - mode: 'parents' | 'class' | 'all'
     *  - class_section_id: int (required for mode='class')
     *  - parent_ids[]: array<int> (required for mode='parents'; can be primary users.id or parents.id)
     */
    // Controller method
    public function sendInvites()
    {
        // This page is year-only: ignore semester filters.
        $semester = '';
        $mode            = (string) trim((string) $this->request->getPost('mode'));
        $parentIdsPicked = array_map('intval', (array) ($this->request->getPost('parent_ids') ?? []));
        $rawClassId      = $this->request->getPost('class_section_id');
        $classSectionId  = ($rawClassId === '' || $rawClassId === null) ? 0 : (int) $rawClassId;

        if (!in_array($mode, ['parents', 'class', 'all'], true)) {
            return redirect()->back()->with('error', 'Invalid mode.');
        }
        if ($mode === 'parents' && empty(array_filter($parentIdsPicked))) {
            return redirect()->back()->with('error', 'Please select at least one parent.');
        }
        if ($mode === 'class' && $classSectionId <= 0) {
            return redirect()->back()->with('error', 'Please select a class/section.');
        }

        // Load all links for the term once
        $links = $this->linkModel->getAllForTerm($this->schoolYear, $semester);
        $linkBySection = [];
        foreach ($links as $l) {
            $cid = (int) ($l['class_section_id'] ?? 0);
            if ($cid && !isset($linkBySection[$cid])) {
                $linkBySection[$cid] = $l;
            }
        }

        // Build per-family bundles
        $payloads = [];
        if ($mode === 'parents') {
            foreach ($parentIdsPicked as $pickedId) {
                if ($pickedId <= 0) continue;
                $bundle = $this->bundleForSingleParent($pickedId, $linkBySection);
                if ($bundle && !empty($bundle['emails']) && !empty($bundle['links'])) {
                    $payloads[] = $bundle;
                }
            }
        } elseif ($mode === 'class') {
            $bundles = $this->bundleForSingleClass($classSectionId, $linkBySection); // per-parent bundles
            foreach ($bundles as $b) {
                if (!empty($b['emails']) && !empty($b['links'])) {
                    $payloads[] = $b;
                }
            }
        } else { // 'all'
            $bundles = $this->bundlesForAllParentsAllClasses($linkBySection); // per-parent bundles across all
            foreach ($bundles as $b) {
                if (!empty($b['emails']) && !empty($b['links'])) {
                    $payloads[] = $b;
                }
            }
        }

        if (empty($payloads)) {
            return redirect()->back()->with('error', 'No recipients found (missing links or emails).');
        }

        /**
         * Consolidate per-student bundles into one bundle per parent.
         * Key priority: parent id; fallback: normalized primary emails joined.
         */
        $normEmailsList = static function (array $list): array {
            $out = [];
            foreach ($list as $e) {
                $e = strtolower(trim((string) $e));
                if ($e !== '' && filter_var($e, FILTER_VALIDATE_EMAIL)) {
                    $out[$e] = true;
                }
            }
            return array_keys($out);
        };

        $mergeUniqueList = static function (array $a, array $b): array {
            // preserves scalar lists, case-sensitive for display fields
            $map = [];
            foreach (array_merge($a, $b) as $v) {
                $k = is_array($v) ? md5(json_encode($v)) : (string) $v;
                $map[$k] = $v;
            }
            return array_values($map);
        };

        /**
         * Merge "sections" as associative rows; prefer dedupe by class_section_id if present,
         * otherwise by name string.
         */
        $mergeSections = static function (array $A, array $B): array {
            $byKey = [];
            $put = static function (&$byKey, $row) {
                if (is_array($row)) {
                    $id   = $row['class_section_id'] ?? null;
                    $name = $row['class_section_name'] ?? null;
                    $key  = $id !== null ? ('id:' . (int)$id) : ('name:' . (string)$name);
                    $byKey[$key] = $row;
                } else {
                    // string fallback
                    $byKey['name:' . (string)$row] = $row;
                }
            };
            foreach ($A as $r) $put($byKey, $r);
            foreach ($B as $r) $put($byKey, $r);
            return array_values($byKey);
        };

        /**
         * Merge "links" items. Accepts mixed shapes:
         * - array of link items (recommended): ['class_section_id','class_section_name','invite_link','qr_src']
         * - string URL(s) (legacy)
         */
        $mergeLinks = static function (array $A, array $B): array {
            $index = [];
            $add = static function (&$index, $item) {
                if (is_array($item)) {
                    $key =
                        (isset($item['class_section_id']) && $item['class_section_id'] !== null)
                        ? 'id:' . (int)$item['class_section_id']
                        : ('url:' . (string)($item['invite_link'] ?? json_encode($item)));
                    $index[$key] = $item;
                } else {
                    $index['url:' . (string)$item] = $item;
                }
            };
            foreach ($A as $it) $add($index, $it);
            foreach ($B as $it) $add($index, $it);
            return array_values($index);
        };

        $grouped = [];
        foreach ($payloads as $b) {
            $parent     = $b['parent'] ?? [];
            $pid        = (int)($parent['id'] ?? 0);
            $primary    = $normEmailsList((array)($b['emails'] ?? []));
            $groupKey   = $pid > 0 ? ('pid:' . $pid) : ('mail:' . implode(',', $primary));

            if (!isset($grouped[$groupKey])) {
                $grouped[$groupKey] = [
                    'parent'   => $parent,
                    'emails'   => $primary,
                    'sections' => (array)($b['sections'] ?? []),
                    'links'    => (array)($b['links'] ?? []),
                    'teachers' => (array)($b['teachers'] ?? []),
                    // Keep any "items" shape if your listener/view uses it
                    'items'    => (array)($b['items'] ?? []),
                ];
            } else {
                // Merge
                $grouped[$groupKey]['parent']   = !empty($parent) ? $parent : $grouped[$groupKey]['parent'];
                $grouped[$groupKey]['emails']   = $normEmailsList(array_merge($grouped[$groupKey]['emails'], $primary));
                $grouped[$groupKey]['sections'] = $mergeSections($grouped[$groupKey]['sections'], (array)($b['sections'] ?? []));
                $grouped[$groupKey]['links']    = $mergeLinks($grouped[$groupKey]['links'], (array)($b['links'] ?? []));
                $grouped[$groupKey]['teachers'] = $mergeUniqueList($grouped[$groupKey]['teachers'], (array)($b['teachers'] ?? []));
                $grouped[$groupKey]['items']    = $mergeLinks($grouped[$groupKey]['items'], (array)($b['items'] ?? [])); // treat items like links
            }
        }

        // Final consolidated list
        $payloads = array_values($grouped);


        // Ensure a listener is actually registered; otherwise nothing will fire.
        $listenerCount = method_exists(\CodeIgniter\Events\Events::class, 'listeners')
            ? count(\CodeIgniter\Events\Events::listeners('whatsapp_invites.send'))
            : -1;
        log_message('debug', 'whatsapp_invites.send listener count: {c}', ['c' => $listenerCount]);
        if ($listenerCount === 0) {
            return redirect()->back()->with(
                'error',
                'No event listener registered for whatsapp_invites.send. Add one in app/Config/Events.php.'
            );
        }

        // Helpers
        $norm = static function (array $list): array {
            $out = [];
            foreach ($list as $e) {
                $e = strtolower(trim((string) $e));
                if ($e !== '' && filter_var($e, FILTER_VALIDATE_EMAIL)) {
                    $out[$e] = true;
                }
            }
            return array_keys($out);
        };

        // Extract secondary emails from bundle shapes + DB fallback
        $getSecondaryEmails = function (array $bundle) use ($norm) {
            $collected = [];

            // Explicit arrays from bundlers
            if (!empty($bundle['emails_secondary'])) {
                $collected = array_merge($collected, (array) $bundle['emails_secondary']);
            }
            if (!empty($bundle['secondary_parents'])) {
                foreach ((array) $bundle['secondary_parents'] as $sp) {
                    if (!empty($sp['email'])) $collected[] = $sp['email'];
                }
            }

            // Common single-field shapes on parent
            $p = $bundle['parent'] ?? [];
            foreach (['secondary_email', 'secondparent_email', 'sec_email', 'email_secondary'] as $k) {
                if (!empty($p[$k])) $collected[] = $p[$k];
            }
            if (!empty($p['secondparent']['email'])) $collected[] = $p['secondparent']['email'];

            // DB fallback by firstparent_id == primary parent id
            try {
                $primaryId = (int) ($p['id'] ?? 0);
                if ($primaryId > 0) {
                    $row = $this->db->table('parents')
                        ->select('secondparent_email')
                        ->where('firstparent_id', $primaryId)
                        ->orderBy('updated_at', 'DESC')
                        ->get()->getFirstRow('array');
                    if (!empty($row['secondparent_email'])) {
                        $collected[] = $row['secondparent_email'];
                    }
                }
            } catch (\Throwable $e) {
                log_message('error', 'Secondary email DB lookup failed: {err}', ['err' => $e->getMessage()]);
            }

            return $norm($collected);
        };

        // Trigger one event per family: TO = primary; CC = secondary
        $triggered = 0;

        foreach ($payloads as $b) {
            try {
                $primaryEmails   = $norm((array) ($b['emails'] ?? []));
                $secondaryEmails = $getSecondaryEmails($b);

                if (empty($secondaryEmails)) {
                    log_message('debug', 'No secondary_emails for parent_id={pid}; bundle keys={keys}', [
                        'pid'  => $b['parent']['id'] ?? null,
                        'keys' => implode(',', array_keys($b)),
                    ]);
                }

                $to = $primaryEmails;
                $cc = [];

                if (!empty($primaryEmails) && !empty($secondaryEmails)) {
                    // keep CC distinct from TO
                    $cc = array_values(array_diff($secondaryEmails, $primaryEmails));
                } elseif (empty($primaryEmails) && !empty($secondaryEmails)) {
                    $to = $secondaryEmails; // only secondary exists → send to them
                }

                if (empty($to)) {
                    log_message('warning', 'Skipping whatsapp_invites.send: no recipient email(s) for parent_id {pid}', [
                        'pid' => $b['parent']['id'] ?? null
                    ]);
                    continue;
                }

                \CodeIgniter\Events\Events::trigger('whatsapp_invites.send', [
                    'parent'      => $b['parent'] ?? null,
                    'sections'    => $b['sections'] ?? [],
                    'schoolYear'  => $this->schoolYear,
                    'semester'    => $this->semester,
                    'links'       => $b['links'] ?? [],
                    'teachers'    => $b['teachers'] ?? [],
                    'to_emails'   => $to,
                    'cc_emails'   => $cc,
                ]);

                $triggered++;
            } catch (\Throwable $e) {
                log_message('error', 'whatsapp_invites.send failed: {err}', ['err' => $e->getMessage()]);
            }
        }

        return redirect()->back()->with('success', "Invite processing started for {$triggered} recipient group(s).");
    }

    // ─────────────────────────────────────────────────────────────────────────────
    // Internal helpers (new logic)
    // ─────────────────────────────────────────────────────────────────────────────

    public function parentContacts()
    {
        // Term context (use controller properties if you already set them)
        $schoolYear = $this->schoolYear ?? (string)($this->request->getGet('school_year') ?? '');
        $semester   = $this->semester   ?? (string)($this->request->getGet('semester') ?? '');

        // parents = term-scoped table holding second parent fields + firstparent_id
        // users   = primary parent records
        $b = $this->db->table('parents sp');

        $b->select("
        sp.id                               AS parents_row_id,
        sp.firstparent_id                   AS firstparent_id,
        sp.secondparent_firstname           AS sp_firstname,
        sp.secondparent_lastname            AS sp_lastname,
        sp.secondparent_email               AS sp_email,
        sp.secondparent_phone               AS sp_phone,
        sp.school_year                      AS sp_school_year,
        sp.semester                         AS sp_semester,

        u.id                                AS u_id,
        u.firstname                         AS u_firstname,
        u.lastname                          AS u_lastname,
        u.email                             AS u_email,
        u.cellphone                         AS u_phone,
        u.school_year                       AS u_school_year,
        u.semester                          AS u_semester
    ");

        $b->join('users u', 'u.id = sp.firstparent_id', 'left');

        // Scope by term using parents table (authoritative for the pairing)
        if ($schoolYear !== '') {
            $b->where('sp.school_year', $schoolYear);
        }
        if ($semester !== '') {
            $b->where('sp.semester', $semester);
        }

        $rows = $b->get()->getResultArray();

        // Flatten into a single contacts list (primary + second parent as separate rows)
        $contacts = [];
        foreach ($rows as $r) {
            // 1) Primary (first) parent from users
            if (!empty($r['u_id'])) {
                $contacts[] = [
                    'firstname' => (string)($r['u_firstname'] ?? ''),
                    'lastname'  => (string)($r['u_lastname'] ?? ''),
                    'email'     => (string)($r['u_email'] ?? ''),
                    'phone'     => (string)($r['u_phone'] ?? ''),
                    'type'      => 'Primary Parent',
                ];
            }

            // 2) Second parent from parents (only if we have at least a name/phone/email)
            $hasSecond =
                !empty($r['sp_firstname']) ||
                !empty($r['sp_lastname'])  ||
                !empty($r['sp_email'])     ||
                !empty($r['sp_phone']);

            if ($hasSecond) {
                $contacts[] = [
                    'firstname' => (string)($r['sp_firstname'] ?? ''),
                    'lastname'  => (string)($r['sp_lastname'] ?? ''),
                    'email'     => (string)($r['sp_email'] ?? ''),
                    'phone'     => (string)($r['sp_phone'] ?? ''),
                    'type'      => 'Second Parent',
                ];
            }
        }

        // Optional: remove totally empty lines (no name + no contact)
        $contacts = array_values(array_filter($contacts, function ($c) {
            $name = trim(($c['firstname'] ?? '') . ($c['lastname'] ?? ''));
            $contact = trim(($c['phone'] ?? '') . ($c['email'] ?? ''));
            return $name !== '' || $contact !== '';
        }));

        // Sort by last name, first name, then type (Primary before Second)
        usort($contacts, function ($a, $b) {
            $la = strtolower($a['lastname'] ?? '');
            $lb = strtolower($b['lastname'] ?? '');
            if ($la !== $lb) return $la <=> $lb;
            $fa = strtolower($a['firstname'] ?? '');
            $fb = strtolower($b['firstname'] ?? '');
            if ($fa !== $fb) return $fa <=> $fb;
            // Primary before Second
            $ta = ($a['type'] ?? '') === 'Primary Parent' ? 0 : 1;
            $tb = ($b['type'] ?? '') === 'Primary Parent' ? 0 : 1;
            return $ta <=> $tb;
        });

        return view('whatsapp/parent_contacts', [
            'contacts'   => array_map(function ($c) {
                // The view expects 'phone' and 'email' directly; keep as-is
                return [
                    'firstname' => $c['firstname'],
                    'lastname'  => $c['lastname'],
                    'email'     => $c['email'],
                    'phone'     => $c['phone'],
                    'type'      => $c['type'],
                ];
            }, $contacts),
            'schoolYear' => $schoolYear,
            'semester'   => $semester,
        ]);
    }

    public function parentContactsByClass()
    {
        $schoolYear = $this->schoolYear ?? (string)($this->request->getGet('school_year') ?? '');
        // This page is year-only: ignore semester filters.
        $semester   = '';

        // 1) Get sections actually used this term from student_class, then name from classSection
        // 1) Get distinct class_section_id actually used this term from student_class
        $csb = $this->db->table('student_class sc')
            ->select('sc.class_section_id, MAX(sc.school_year) AS school_year');

        if ($schoolYear !== '') $csb->where('sc.school_year', $schoolYear);

        $sections = $csb->groupBy('sc.class_section_id')
            ->orderBy('sc.class_section_id', 'ASC')
            ->get()->getResultArray();

        // Build list of IDs
        $sectionIds = array_values(array_filter(array_unique(array_map(
            fn($r) => (int)($r['class_section_id'] ?? 0),
            $sections
        ))));

        // 2) Fetch names from classSection in a separate query and map by id
        $namesById = [];
        if (!empty($sectionIds)) {
            $nb = $this->db->table('classSection') // IMPORTANT: exact table name/case
                ->select('class_section_id, class_section_name')
                ->whereIn('class_section_id', $sectionIds)
                ->get()->getResultArray();

            foreach ($nb as $row) {
                $namesById[(int)$row['class_section_id']] = (string)($row['class_section_name'] ?? '');
            }
        }

        // 3) Build $classSections with reliable names
        $classSections = [];
        foreach ($sections as $s) {
            $sid = (int)($s['class_section_id'] ?? 0);
            if (!$sid) continue;

            $classSections[$sid] = [
                'class_section_id'   => $sid,
                'class_section_name' => $namesById[$sid] ?? ('Section ' . $sid),
                'semester'           => $semester,
                'school_year'        => (string)($s['school_year'] ?? ''),
                'description'        => '',
                'main_teachers'      => [],
                'teacher_assistants' => [],
                'parents'            => [],
            ];
        }


        // 2) Parents per class:
        //    sc → students s → users u (primary parent)
        //    parents sp on (sp.firstparent_id = u.id) for second parent (same term)
        $pb = $this->db->table('student_class sc');
        $pb->select("
        sc.class_section_id,

        u.id        AS primary_id,
        u.firstname AS primary_firstname,
        u.lastname  AS primary_lastname,
        u.email     AS primary_email,
        u.cellphone AS primary_phone,

        sp.id       AS second_id,
        sp.secondparent_firstname AS second_firstname,
        sp.secondparent_lastname  AS second_lastname,
        sp.secondparent_email     AS second_email,
        sp.secondparent_phone     AS second_phone
    ");

        // IMPORTANT: join through students table (NOT sc.parent_id)
        $pb->join('students s', 's.id = sc.student_id', 'inner');          // adjust if your table is named `student` (singular)
        $pb->join('users u',    'u.id = s.parent_id',   'inner');          // primary parent lives on students.parent_id

        // Second parent row for the same term (allow NULL/'' semester if you sometimes omit it)
        $pb->join(
            'parents sp',
            "sp.firstparent_id = u.id
         AND sp.school_year = sc.school_year",
            'left'
        );

        if ($schoolYear !== '') $pb->where('sc.school_year', $schoolYear);

        $pb->orderBy('sc.class_section_id', 'ASC')
            ->orderBy('u.lastname', 'ASC')
            ->orderBy('u.firstname', 'ASC');

        $rows = $pb->get()->getResultArray();

        // 3) De-duplicate a primary parent within the same class section
        $seen = [];
        foreach ($rows as $r) {
            $sid = (int)($r['class_section_id'] ?? 0);
            $pid = (int)($r['primary_id'] ?? 0);
            if (!$sid || !$pid || !isset($classSections[$sid])) continue;

            $key = $sid . ':' . $pid;
            if (isset($seen[$key])) continue;
            $seen[$key] = true;

            $classSections[$sid]['parents'][] = [
                'class_section_id' => $sid,
                'primary_id'   => $pid,
                'primary_name'  => trim(($r['primary_lastname'] ?? '') . ', ' . ($r['primary_firstname'] ?? '')),
                'primary_phone' => (string)($r['primary_phone'] ?? ''),
                'primary_email' => (string)($r['primary_email'] ?? ''),
                'second_id'     => (int)($r['second_id'] ?? 0),
                'second_name'   => trim(($r['second_lastname'] ?? '') . ', ' . ($r['second_firstname'] ?? '')),
                'second_phone'  => (string)($r['second_phone'] ?? ''),
                'second_email'  => (string)($r['second_email'] ?? ''),
                // Use null to indicate "unknown/not recorded" so UI can show — (like attendance)
                'primary_member' => null,
                'second_member'  => null,
            ];
        }

        // Annotate membership flags from whatsapp_group_memberships
        try {
            $sectionIds = array_keys($classSections);
            $m = $this->membershipModel->getBySectionsAndTerm($sectionIds, $schoolYear, $semester);
            foreach ($classSections as $sid => &$sec) {
                foreach ($sec['parents'] as &$p) {
                    $pk1 = sprintf('%d:primary:%d', (int)$p['class_section_id'], (int)$p['primary_id']);
                    if (isset($m[$pk1])) {
                        $p['primary_member'] = (int)($m[$pk1]['is_member'] ?? 0);
                    }
                    $secondId = (int)($p['second_id'] ?? 0);
                    if ($secondId > 0) {
                        $pk2 = sprintf('%d:second:%d', (int)$p['class_section_id'], $secondId);
                        if (isset($m[$pk2])) {
                            $p['second_member'] = (int)($m[$pk2]['is_member'] ?? 0);
                        }
                    }
                }
                unset($p);
            }
            unset($sec);
        } catch (\Throwable $e) {
            // ignore membership annotation errors
        }

        // 4) (Optional) Teachers, if you keep them in teacher_class
        try {
            $tb = $this->db->table('teacher_class tc')
                ->select('tc.class_section_id, tc.position, u.firstname, u.lastname')
                ->join('users u', 'u.id = tc.teacher_id', 'left');

            if ($schoolYear !== '') $tb->where('tc.school_year', $schoolYear);

            $teachers = $tb->get()->getResultArray();
            foreach ($teachers as $t) {
                $sid = (int)($t['class_section_id'] ?? 0);
                if (!isset($classSections[$sid])) continue;
                $name = trim(($t['lastname'] ?? '') . ', ' . ($t['firstname'] ?? ''));
                $pos  = strtolower((string)($t['position'] ?? 'main'));
                if ($pos === 'ta' || $pos === 'assistant') {
                    $classSections[$sid]['teacher_assistants'][] = $name;
                } else {
                    $classSections[$sid]['main_teachers'][] = $name;
                }
            }
        } catch (\Throwable $e) {
            // ignore if table not present
        }

        foreach ($classSections as &$sec) {
            $sec['main_teachers']      = array_values(array_filter($sec['main_teachers'] ?? []));
            $sec['teacher_assistants'] = array_values(array_filter($sec['teacher_assistants'] ?? []));
            $sec['parents']            = array_values($sec['parents'] ?? []);
        }
        unset($sec);

        return view('whatsapp/parent_contacts_by_class', [
            'classSections' => array_values($classSections),
        ]);
    }

    /**
     * POST: Update WhatsApp group membership flags for a class/parent(s).
     * Accepts fields:
     *  - class_section_id (int)
     *  - primary_id (int, optional)
     *  - second_id (int, optional)
     *  - primary_member ('on' or '1' if checked)
     *  - second_member  ('on' or '1' if checked)
     */
    public function updateMembership()
    {
        // Only accept POST for writes
        if (strtoupper($this->request->getMethod()) !== 'POST') {
            return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
        }

        // Read POSTed values explicitly (avoid GET bleed-through)
        $sid          = (int) ($this->request->getPost('class_section_id') ?? 0);
        $primaryId    = (int) ($this->request->getPost('primary_id') ?? 0);
        $secondId     = (int) ($this->request->getPost('second_id') ?? 0);

        // Handle checkbox values that may arrive as string or array (if duplicated inputs)
        $isChecked = static function ($v): bool {
            if (is_array($v)) {
                foreach ($v as $one) {
                    if ($one === '1' || strtolower((string)$one) === 'on') return true;
                }
                return false;
            }
            return ($v === '1' || strtolower((string)$v) === 'on');
        };
        $primaryValueRaw = $this->request->getPost('primary_member');
        $secondValueRaw  = $this->request->getPost('second_member');

        // Debug trace (temporary): log incoming payload to help diagnose saving issues
        try {
            log_message('error', 'DBG updateMembership: method={m} sid={sid} pid={pid} sec={sec} pRaw={p} sRaw={sr}', [
                'm'   => $this->request->getMethod(),
                'sid' => $sid,
                'pid' => $primaryId,
                'sec' => $secondId,
                'p'   => json_encode($primaryValueRaw),
                'sr'  => json_encode($secondValueRaw),
            ]);
        } catch (\Throwable $e) {
            // ignore
        }

        if ($sid <= 0) {
            return redirect()->back()->with('error', 'Invalid class section.');
        }

        $schoolYear = (string) ($this->request->getPost('school_year') ?? $this->schoolYear ?? '');
        $semester   = (string) ($this->request->getPost('semester') ?? $this->semester   ?? '');
        $schoolYear = trim($schoolYear);
        $semester   = trim($semester);

        // Identify the actor (if available)
        $verifiedBy = null;
        try {
            $verifiedBy = (int) (session('id') ?? 0) ?: null;
        } catch (\Throwable $e) {
            $verifiedBy = null;
        }

        // Ensure the target table exists (prefer tableExists if available)
        $tableExists = null;
        try {
            if (method_exists($this->db, 'tableExists')) {
                $tableExists = $this->db->tableExists('whatsapp_group_memberships');
            }
        } catch (\Throwable $e) {
            $tableExists = null; // fall back to getFieldNames below
        }
        if ($tableExists === null) {
            try {
                $this->db->getFieldNames('whatsapp_group_memberships');
                $tableExists = true;
            } catch (\Throwable $e) {
                $tableExists = false;
            }
        }
        if (!$tableExists) {
            $msg = 'Membership table missing. Please run migrations.';
            log_message('error', 'updateMembership aborted: {msg}', ['msg' => $msg]);
            if ($this->request->isAJAX()) {
                return $this->response->setStatusCode(500)->setJSON([
                    'status' => 'error',
                    'error'  => $msg,
                ]);
            }
            return redirect()->back()->with('error', $msg);
        }

        try {
            $saved = [];
            if ($primaryId > 0) {
                $isMember = $isChecked($primaryValueRaw);
                $id = $this->membershipModel->upsertMembership($sid, $schoolYear, $semester, 'primary', $primaryId, $isMember, $verifiedBy);
                if ($id <= 0) {
                    // Log DB error details if available
                    try {
                        $dberr = isset($this->membershipModel->db) ? $this->membershipModel->db->error() : [];
                        log_message('error', 'Failed to upsert primary membership: sid={sid} pid={pid} term={y}/{s} err={e}', [
                            'sid' => $sid, 'pid' => $primaryId, 'y' => $schoolYear, 's' => $semester, 'e' => json_encode($dberr)
                        ]);
                    } catch (\Throwable $e) {}
                }
                $saved[] = 'primary=' . $primaryId . ':' . ($isMember ? '1' : '0');
            }
            if ($secondId > 0) {
                $isMember = $isChecked($secondValueRaw);
                $id = $this->membershipModel->upsertMembership($sid, $schoolYear, $semester, 'second', $secondId, $isMember, $verifiedBy);
                if ($id <= 0) {
                    try {
                        $dberr = isset($this->membershipModel->db) ? $this->membershipModel->db->error() : [];
                        log_message('error', 'Failed to upsert second membership: sid={sid} sec={sec} term={y}/{s} err={e}', [
                            'sid' => $sid, 'sec' => $secondId, 'y' => $schoolYear, 's' => $semester, 'e' => json_encode($dberr)
                        ]);
                    } catch (\Throwable $e) {}
                }
                $saved[] = 'second=' . $secondId . ':' . ($isMember ? '1' : '0');
            }
        } catch (\Throwable $e) {
            log_message('error', 'Failed to update WhatsApp membership: {err}', ['err' => $e->getMessage()]);
            if ($this->request->isAJAX()) {
                return $this->response->setStatusCode(500)->setJSON([
                    'status' => 'error',
                    'error'  => 'Failed to save membership.',
                ]);
            }
            return redirect()->back()->with('error', 'Failed to save membership.');
        }

        if (empty($saved)) {
            if ($this->request->isAJAX()) {
                return $this->response->setJSON([
                    'status' => 'noop',
                    'message' => 'Nothing to save (missing parent IDs).',
                ]);
            }
            return redirect()->back()->with('error', 'Nothing to save (missing parent IDs).');
        }

        if ($this->request->isAJAX()) {
            return $this->response->setJSON([
                'status' => 'ok',
                'saved'  => $saved,
                'term'   => ['school_year' => $schoolYear, 'semester' => $semester],
            ]);
        }

        return redirect()->back()->with('message', 'Membership saved: ' . implode(', ', $saved));
    }


    /**
     * Build initial lists for the page:
     *  - ALL sections from student_class for current term
     *  - ALL parents: primaries (users who have students in term) + second parents (parents table)
     */
    private function prepareIndexData(string $schoolYear, string $semester): array
    {
        $schoolYear = (string) $schoolYear;
        $semester = trim((string) $semester);
        [$yearAliases, $normalizedYear, $startYear] = $this->syAliases($schoolYear);
        $semAliases = $semester !== '' ? $this->semAliases($semester) : [];
        $limitToSemester = $semester !== '' && $this->hasRosterForTerm($schoolYear, $semester);

        // -----------------------------
        // Sections list (primary source: student_class)
        // -----------------------------
        $secQ = $this->db->table('student_class sc')
            ->select('DISTINCT sc.class_section_id', false);

        // school_year filter with aliases
        $secQ->groupStart()
            ->whereIn('sc.school_year', $yearAliases)
            ->orWhere("REPLACE(REPLACE(sc.school_year,'/','-'),'–','-')", $normalizedYear)
            ->orWhere('sc.school_year', (string)$startYear)
            ->groupEnd();

        $secRows = $secQ->orderBy('sc.class_section_id', 'ASC')->get()->getResultArray();

        // Fallback: if student_class is empty for the term, try enrollments
        if (empty($secRows)) {
            $secQ = $this->db->table('enrollments e')
                ->select('DISTINCT e.class_section_id', false);

            $secQ->groupStart()
                ->whereIn('e.school_year', $yearAliases)
                ->orWhere("REPLACE(REPLACE(e.school_year,'/','-'),'–','-')", $normalizedYear)
                ->orWhere('e.school_year', (string)$startYear)
                ->groupEnd();

            if ($limitToSemester && !empty($semAliases)) {
                $secQ->groupStart();
                foreach ($semAliases as $i => $s) {
                    $i === 0
                        ? $secQ->where('LOWER(e.semester)', strtolower($s))
                        : $secQ->orWhere('LOWER(e.semester)', strtolower($s));
                }
                $secQ->orWhere('e.semester', $semester)->groupEnd();
            } elseif ($limitToSemester) {
                $secQ->where('e.semester', $semester);
            }

            $secRows = $secQ->orderBy('e.class_section_id', 'ASC')->get()->getResultArray();
        }

        $sections = [];
        if (!empty($secRows)) {
            foreach ($secRows as $r) {
                $cid = (int)($r['class_section_id'] ?? 0);
                if (!$cid) continue;

                // Try to get a friendly name from links; fallback to "Section {id}"
                $linkRow = $this->linkModel->getLinkForSection($cid, $schoolYear, $semester);
                $name    = $this->classSectionModel->getClassSectionNameBySectionId($cid);

                $sections[] = [
                    'class_section_id'   => $cid,
                    'class_section_name' => $name,
                ];
            }
        }

        // -----------------------------
        // Parents list (primaries + secondaries) for the term
        // primary source: student_class->students->users
        // -----------------------------

        $pidQ = $this->db->table('student_class sc')
            ->select('DISTINCT s.parent_id', false)
            ->join('students s', 's.id = sc.student_id', 'left');

        $pidQ->groupStart()
            ->whereIn('sc.school_year', $yearAliases)
            ->orWhere("REPLACE(REPLACE(sc.school_year,'/','-'),'–','-')", $normalizedYear)
            ->orWhere('sc.school_year', (string)$startYear)
            ->groupEnd();

        $pidQ->where('s.parent_id IS NOT NULL', null, false);

        $primaryIdRows = $pidQ->get()->getResultArray();

        // Fallback: if no rows via student_class, try enrollments
        if (empty($primaryIdRows)) {
            $pidQ = $this->db->table('enrollments e')
                ->select('DISTINCT s.parent_id', false)
                ->join('students s', 's.id = e.student_id', 'left');

            $pidQ->groupStart()
                ->whereIn('e.school_year', $yearAliases)
                ->orWhere("REPLACE(REPLACE(e.school_year,'/','-'),'–','-')", $normalizedYear)
                ->orWhere('e.school_year', (string)$startYear)
                ->groupEnd();

            if ($limitToSemester && !empty($semAliases)) {
                $pidQ->groupStart();
                foreach ($semAliases as $i => $s) {
                    $i === 0
                        ? $pidQ->where('LOWER(e.semester)', strtolower($s))
                        : $pidQ->orWhere('LOWER(e.semester)', strtolower($s));
                }
                $pidQ->orWhere('e.semester', $semester)->groupEnd();
            } elseif ($limitToSemester) {
                $pidQ->where('e.semester', $semester);
            }

            $pidQ->where('s.parent_id IS NOT NULL', null, false);

            $primaryIdRows = $pidQ->get()->getResultArray();
        }

        $primaryIds = [];
        foreach ($primaryIdRows as $r) {
            $pid = (int)($r['parent_id'] ?? 0);
            if ($pid) $primaryIds[$pid] = true;
        }
        $primaryIds = array_keys($primaryIds);

        $primaryProfiles = [];
        if (!empty($primaryIds)) {
            $primRows = $this->db->table('users')
                ->select('id, firstname, lastname, email')
                ->whereIn('id', $primaryIds)
                ->get()->getResultArray();

            foreach ($primRows as $u) {
                $uid = (int)($u['id'] ?? 0);
                if (!$uid) continue;
                $primaryProfiles[$uid] = [
                    'id'        => $uid,
                    'firstname' => (string)($u['firstname'] ?? ''),
                    'lastname'  => (string)($u['lastname']  ?? ''),
                    'email'     => (string)($u['email']     ?? ''),
                    'source'    => 'users',
                ];
            }
        }

        // Second parents (allow rows without semester; we’re already term-bound by primaryIds)
        $secondaryProfiles = [];
        if (!empty($primaryIds)) {
            $secRows = $this->db->table('parents')
                ->select('id, firstparent_id, secondparent_firstname, secondparent_lastname, secondparent_email, secondparent_phone')
                ->whereIn('firstparent_id', $primaryIds)
                ->orderBy('updated_at', 'DESC')   // in case of duplicates, latest wins
                ->get()->getResultArray();

            foreach ($secRows as $r) {
                $primaryId = (int) $r['firstparent_id'];
                $secondaryProfiles[$primaryId] = [
                    'id'        => (int) $r['id'],
                    'firstname' => (string) $r['secondparent_firstname'],
                    'lastname'  => (string) $r['secondparent_lastname'],
                    'email'     => (string) $r['secondparent_email'],
                    'phone'     => (string) $r['secondparent_phone'],
                    'source'    => 'parents',
                ];
            }
        }


        // Merge + sort
        $parents = array_values($primaryProfiles);
        foreach ($secondaryProfiles as $sp) $parents[] = $sp;

        usort($parents, function ($a, $b) {
            $ln = strcasecmp($a['lastname'] ?? '',  $b['lastname'] ?? '');
            if ($ln !== 0) return $ln;
            $fn = strcasecmp($a['firstname'] ?? '', $b['firstname'] ?? '');
            if ($fn !== 0) return $fn;
            $sa = ($a['source'] ?? '') === 'users' ? 0 : 1;
            $sb = ($b['source'] ?? '') === 'users' ? 0 : 1;
            return $sa <=> $sb;
        });

        return [$parents, $sections];
    }

    /** School-year aliases like 2025-2026, 2025/2026, 2025–2026, 2025-26, 2025 */
    private function syAliases(string $schoolYear): array
    {
        $trim = trim($schoolYear);
        if (preg_match('/\b(20\d{2})\b/', $trim, $m)) {
            $start = (int)$m[1];
        } else {
            $start = (int)date('Y');
        }
        $end = $start + 1;

        $normalized = "{$start}-{$end}";
        $aliases = [
            $normalized,
            "{$start}/{$end}",
            "{$start}–{$end}",                       // en dash
            sprintf('%d-%02d', $start, $end % 100),  // 2025-26
            (string)$start,                          // some schemas store only start year
        ];
        return [array_values(array_unique($aliases)), $normalized, $start];
    }

    /** Semester aliases */
    private function semAliases(string $semester): array
    {
        $s = strtolower(trim($semester));
        if ($s === 'fall')   return ['fall', 'sem1', 'semester 1', '1', 'f'];
        if ($s === 'spring') return ['spring', 'sem2', 'semester 2', '2', 's'];
        if ($s === 'summer') return ['summer', 'sem3', '3'];
        return [$s];
    }

    /**
     * Returns true if student_class has any rows for the given school year.
     * The semester argument is ignored because assignments are tracked per year.
     */
    private function hasRosterForTerm(string $schoolYear, string $semester): bool
    {
        $year = trim((string)$schoolYear);
        if ($year === '') {
            return false;
        }

        if (array_key_exists($year, $this->rosterPresenceCache)) {
            return $this->rosterPresenceCache[$year];
        }

        $cnt = $this->db->table('student_class')
            ->where('school_year', $year)
            ->countAllResults();

        $this->rosterPresenceCache[$year] = $cnt > 0;
        return $this->rosterPresenceCache[$year];
    }


    /**
     * Parents mode:
     * - pickedId can be a primary (users.id) or a secondary (parents.id)
     * - resolve to primary -> students -> class_section_ids (from student_class)
     * - collect non-empty links for those sections
     * - return one bundle (emails = primary + second-parent if present)
     */
    private function bundleForSingleParent(int $pickedId, array $linkBySection): ?array
    {
        // Determine if pickedId is primary
        $isPrimary = (bool) $this->db->table('users')->select('id')->where('id', $pickedId)->get()->getFirstRow();

        $primaryId = $pickedId;
        if (!$isPrimary) {
            // pickedId is likely a second-parent; map to firstparent_id
            $map = $this->db->table('parents')
                ->select('firstparent_id, id')
                ->where('id', $pickedId)
                ->get()->getFirstRow('array');
            if (!$map || empty($map['firstparent_id'])) {
                return null;
            }
            $primaryId = (int) $map['firstparent_id'];
        }

        // Primary profile
        $p = $this->db->table('users')->select('id, firstname, lastname, email')->where('id', $primaryId)->get()->getFirstRow('array');
        if (!$p) return null;

        // Optional second-parent profile
        $sec = $this->db->table('parents')
            ->select('id, secondparent_firstname, secondparent_lastname, secondparent_email')
            ->where('firstparent_id', $primaryId)
            ->where('id >', 0)
            ->get()->getFirstRow('array');

        // Sections for this primary via their students (term-scoped)
        $limitToSemester = $this->hasRosterForTerm($this->schoolYear, $this->semester);
        $secRows = $this->db->table('student_class sc')
            ->select('DISTINCT sc.class_section_id', false)
            ->join('students s', 's.id = sc.student_id', 'left')
            ->where('sc.school_year', $this->schoolYear);
        $secRows = $secRows->where('s.parent_id', $primaryId)
            ->get()->getResultArray();

        $sections = [];
        $linksSet = [];

        foreach ($secRows as $r) {
            $cid = (int) ($r['class_section_id'] ?? 0);
            if (!$cid) continue;

            $link = (string) ($linkBySection[$cid]['invite_link'] ?? '');
            if ($link === '') continue;

            $sections[] = [
                'class_section_id'   => $cid,
                'class_section_name' => (string) ($linkBySection[$cid]['class_section_name'] ?? ('Section ' . $cid)),
                'invite_link'        => $link,
            ];
            $linksSet[$link] = true;
        }

        if (empty($sections)) return null;

        $emailsSet = [];
        if (!empty($p['email'])) $emailsSet[$p['email']] = true;
        if (!empty($sec['secondparent_email'])) $emailsSet[$sec['secondparent_email']] = true;

        if (empty($emailsSet)) return null;

        return [
            'parent'            => [
                'id'        => (int) $p['id'],
                'firstname' => (string) ($p['firstname'] ?? ''),
                'lastname'  => (string) ($p['lastname']  ?? ''),
                'email'     => (string) ($p['email']     ?? ''),
                'source'    => 'users',
            ],
            'secondary_parents' => $sec && !empty($sec['id']) ? [[
                'id'        => (int) $sec['id'],
                'firstname' => (string) ($sec['secondparent_firstname'] ?? ''),
                'lastname'  => (string) ($sec['secondparent_lastname']  ?? ''),
                'email'     => (string) ($sec['secondparent_email']     ?? ''),
                'source'    => 'parents',
            ]] : [],
            'sections'          => $sections,
            'links'             => array_keys($linksSet),
            'emails'            => array_keys($emailsSet),
        ];
    }

    /**
     * Class mode:
     * - Given classSectionId, find students (student_class) for the term
     * - Map to primaries (users) and second-parents (parents)
     * - Return one bundle **per primary parent** (so each parent gets their own email)
     */
    private function bundleForSingleClass(int $classSectionId, array $linkBySection): array
    {
        $link = (string) ($linkBySection[$classSectionId]['invite_link'] ?? '');
        if ($link === '') return []; // no link => skip

        // Students in this class
        $stuRows = $this->db->table('student_class')
            ->select('student_id')
            ->where('school_year', $this->schoolYear)
            ->where('semester', $this->semester)
            ->where('class_section_id', $classSectionId)
            ->get()->getResultArray();
        if (empty($stuRows)) return [];

        $studentIds = array_values(array_unique(array_map(fn($r) => (int) $r['student_id'], $stuRows)));

        // Map students -> primary parent id
        $pRows = $this->db->table('students')
            ->select('id, parent_id')
            ->whereIn('id', $studentIds)
            ->get()->getResultArray();

        $parentIds = [];
        foreach ($pRows as $r) {
            $pid = (int) ($r['parent_id'] ?? 0);
            if ($pid) $parentIds[$pid] = true;
        }
        $parentIds = array_keys($parentIds);
        if (empty($parentIds)) return [];

        // Load primary parent profiles
        $primRows = $this->db->table('users')
            ->select('id, firstname, lastname, email')
            ->whereIn('id', $parentIds)
            ->get()->getResultArray();

        $profiles = [];
        foreach ($primRows as $u) {
            $uid = (int) $u['id'];
            $profiles[$uid] = [
                'id'        => $uid,
                'firstname' => (string) ($u['firstname'] ?? ''),
                'lastname'  => (string) ($u['lastname']  ?? ''),
                'email'     => (string) ($u['email']     ?? ''),
                'source'    => 'users',
            ];
        }

        // Second-parents for these primaries
        $secRows = $this->db->table('parents')
            ->select('firstparent_id, id, secondparent_firstname, secondparent_lastname, secondparent_email')
            ->where('id >', 0)
            ->whereIn('firstparent_id', $parentIds)
            ->get()->getResultArray();

        $secByPrimary = [];
        foreach ($secRows as $r) {
            $fp = (int) $r['firstparent_id'];
            $secByPrimary[$fp] = [
                'id'        => (int) ($r['id'] ?? 0),
                'firstname' => (string) ($r['secondparent_firstname'] ?? ''),
                'lastname'  => (string) ($r['secondparent_lastname']  ?? ''),
                'email'     => (string) ($r['secondparent_email']     ?? ''),
                'source'    => 'parents',
            ];
        }

        // One bundle per primary parent
        $bundles = [];
        foreach ($parentIds as $pid) {
            $profile = $profiles[$pid] ?? null;
            if (!$profile) continue;

            $emails = [];
            if (!empty($profile['email'])) $emails[$profile['email']] = true;
            if (!empty($secByPrimary[$pid]['email'])) $emails[$secByPrimary[$pid]['email']] = true;
            if (empty($emails)) continue;

            $bundles[] = [
                'parent'            => $profile,
                'secondary_parents' => !empty($secByPrimary[$pid]['id']) ? [$secByPrimary[$pid]] : [],
                'sections'          => [[
                    'class_section_id'   => $classSectionId,
                    'class_section_name' => (string) ($linkBySection[$classSectionId]['class_section_name'] ?? ('Section ' . $classSectionId)),
                    'invite_link'        => $link,
                ]],
                'links'             => [$link],
                'emails'            => array_keys($emails),
            ];
        }

        return $bundles;
    }

    /**
     * All mode:
     * - Iterate all distinct class_section_id in student_class (for the term)
     * - Reuse class bundles per section and flatten
     */
    private function bundlesForAllParentsAllClasses(array $linkBySection): array
    {
        $secRows = $this->db->table('student_class')
            ->select('DISTINCT class_section_id', false)
            ->where('school_year', $this->schoolYear)
            ->where('semester', $this->semester)
            ->orderBy('class_section_id', 'ASC')
            ->get()->getResultArray();

        $bundles = [];
        foreach ($secRows as $r) {
            $cid = (int) ($r['class_section_id'] ?? 0);
            if (!$cid) continue;

            $classBundles = $this->bundleForSingleClass($cid, $linkBySection);
            foreach ($classBundles as $b) {
                $bundles[] = $b;
            }
        }
        return $bundles;
    }
}
