<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

require '../config.php';
session_start();
session_write_close();

// Define safety constants for file inclusion validation
define('CONN_PROTECT_OK', true);
require 'leave_integration.php';

// ---------------------------
// USER AUTHENTICATION & ACCESS
// ---------------------------
$userId = $_SESSION['user_id'] ?? null;
$userRole = strtolower(trim((string)($_SESSION['role'] ?? '')));
$userDeptId = intval($_SESSION['department_id'] ?? 0);

if (!$userId) {
    header("Location: ../login.php");
    exit;
}

$username = $_SESSION['username'] ?? 'User';

$loggedInEmployeeId = 0;
if ($userId) {
    // IMPORTANT: $_SESSION['user_id'] is the login users.id, not employees.id
    // -- those are separate tables with separate auto-increment ids (an
    // employee's own record is linked back via employees.user_id). Matching
    // against employees.id directly here would silently resolve to the
    // wrong employee (or none) whenever the two ids don't coincidentally
    // match, which would break any "show me my own row" scoping.
    $stmt = $conn->prepare("SELECT id FROM employees WHERE user_id = ? LIMIT 1");
    $stmt->bind_param("i", $userId);
    $stmt->execute();
    $res = $stmt->get_result();
    $loggedInEmployeeId = $res->fetch_assoc()['id'] ?? 0;
    $stmt->close();
}

// ---------------------------
// PAYROLL RUN DATE CONFIGURATION
// ---------------------------
$payrollRunConfig = $conn->query("SELECT * FROM payroll_run_date ORDER BY id DESC LIMIT 1")->fetch_assoc();
$defaultStartDay  = (int)($payrollRunConfig['start_day'] ?? 1);
$defaultEndDay    = (int)($payrollRunConfig['end_day'] ?? 0); // 0 indicates last day of month

function getPayrollCycleDates($targetDate, $startDay, $endDay) {
    $timestamp = strtotime($targetDate);
    $month = (int)date('n', $timestamp);
    $year  = (int)date('Y', $timestamp);

    if ($endDay <= 0) {
        // Full Month (e.g., 1st to End of Month)
        $start = sprintf('%04d-%02d-%02d', $year, $month, 1);
        $lastDay = date('t', strtotime($start));
        $end   = sprintf('%04d-%02d-%02d', $year, $month, $lastDay);
    } elseif ($startDay > $endDay) {
        // Crosses month boundary (e.g., 26th of prev month to 25th of current month)
        $prevTimestamp = strtotime("-1 month", mktime(0, 0, 0, $month, 1, $year));
        $prevMonth = date('n', $prevTimestamp);
        $prevYear  = date('Y', $prevTimestamp);

        $maxPrevDays = date('t', $prevTimestamp);
        $actualStartDay = min($startDay, $maxPrevDays);
        $start = sprintf('%04d-%02d-%02d', $prevYear, $prevMonth, $actualStartDay);

        $maxCurrDays = date('t', mktime(0, 0, 0, $month, 1, $year));
        $actualEndDay = min($endDay, $maxCurrDays);
        $end = sprintf('%04d-%02d-%02d', $year, $month, $actualEndDay);
    } else {
        // Standard range within same month
        $maxDays = date('t', mktime(0, 0, 0, $month, 1, $year));
        $actualStartDay = min($startDay, $maxDays);
        $actualEndDay   = min($endDay, $maxDays);
        $start = sprintf('%04d-%02d-%02d', $year, $month, $actualStartDay);
        $end   = sprintf('%04d-%02d-%02d', $year, $month, $actualEndDay);
    }

    return [$start, $end];
}

// ---------------------------
// PERIOD TIMELINE MANAGEMENT
// ---------------------------
$periodType   = $_GET['type'] ?? 'payroll';
$selectedDate = $_GET['start'] ?? date('Y-m-d');
$filterDeptId = isset($_GET['dept_id']) ? intval($_GET['dept_id']) : 0;

if ($periodType === 'weekly') {
    $startDate = date('Y-m-d', strtotime('monday this week', strtotime($selectedDate)));
    $endDate   = date('Y-m-d', strtotime('+6 days', strtotime($startDate)));
} elseif ($periodType === 'monthly') {
    $startDate = date('Y-01', strtotime($selectedDate));
    $endDate   = date('Y-m-t', strtotime($selectedDate));
} else { // 'payroll' or fallback default
    list($startDate, $endDate) = getPayrollCycleDates($selectedDate, $defaultStartDay, $defaultEndDay);
}

// ---------------------------
// BUILD DAYS MATRIX SELECTION
// ---------------------------
$days = [];
$currentLoopDate = $startDate;
while (strtotime($currentLoopDate) <= strtotime($endDate)) {
    $days[] = $currentLoopDate;
    $currentLoopDate = date('Y-m-d', strtotime("$currentLoopDate +1 day"));
}

$windowStart = $days[0];
$windowEnd   = end($days);

// ---------------------------
// EXTERNAL MODULE: EXTRACT LEAVES
// ---------------------------
$approvedLeaves = fetchApprovedLeavesScope($conn, $days, $windowStart, $windowEnd);

// ---------------------------
// LOAD DEPARTMENTS FILTER SCOPE
// ---------------------------
$departments = [];
if ($userRole === 'manager' || $userRole === 'staff' || $userRole === 'supervisor') {
    $stmt = $conn->prepare("SELECT id, name FROM departments WHERE id = ?");
    $stmt->bind_param("i", $userDeptId);
    $stmt->execute();
    $res = $stmt->get_result();
} else {
    $res = $conn->query("SELECT id, name FROM departments ORDER BY name");
}

while ($row = $res->fetch_assoc()) {
    $departments[$row['id']] = $row;
}
if (isset($stmt) && $userRole !== 'admin' && $userRole !== 'superadmin' && $userRole !== 'hr') {
    $stmt->close();
}

// Default filter to first department if none specified or restricted
if ($filterDeptId <= 0 || !isset($departments[$filterDeptId])) {
    $filterDeptId = array_key_first($departments) ?? 0;
}

$departmentOrder = $filterDeptId ? [$filterDeptId] : array_keys($departments);

// ---------------------------
// SHIFTS DICTIONARIES & COLOR MAP
// ---------------------------
function getReadableShiftTextColor($hexColor) {
    $hex = ltrim((string)$hexColor, '#');
    if (strlen($hex) === 3) {
        $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
    }
    if (strlen($hex) !== 6 || !ctype_xdigit($hex)) return '#ffffff';
    $r = hexdec(substr($hex, 0, 2));
    $g = hexdec(substr($hex, 2, 2));
    $b = hexdec(substr($hex, 4, 2));
    // Standard perceived-brightness formula -- picks dark text on light/pastel
    // shift colors and white text on dark/saturated ones, so every shift stays
    // readable regardless of what color it was given.
    $luminance = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
    return $luminance > 0.6 ? '#1e293b' : '#ffffff';
}

function formatShiftTimeRange($shiftRow) {
    $rawStart = !empty($shiftRow['checkin']) ? $shiftRow['checkin'] : (!empty($shiftRow['start_time']) ? $shiftRow['start_time'] : ($shiftRow['time_in'] ?? null));
    $rawEnd   = !empty($shiftRow['checkout']) ? $shiftRow['checkout'] : (!empty($shiftRow['end_time']) ? $shiftRow['end_time'] : ($shiftRow['time_out'] ?? null));
    if (!$rawStart || !$rawEnd) return '';
    return date('g:iA', strtotime($rawStart)) . '–' . date('g:iA', strtotime($rawEnd));
}

$shifts = [];
$shiftColors = [];
$shiftTextColors = [];
$shiftTimes = [];
$res = $conn->query("SELECT * FROM shifts ORDER BY name");
while ($row = $res->fetch_assoc()) {
    $shifts[$row['id']] = $row;
    // Map shift ID directly to the database 'color' column value with fallback
    $color = !empty($row['color']) ? $row['color'] : '#475569';
    $shiftColors[(string)$row['id']] = $color;
    $shiftTextColors[(string)$row['id']] = getReadableShiftTextColor($color);
    $shiftTimes[(string)$row['id']] = formatShiftTimeRange($row);
}

// Which department(s) each shift is restricted to, if any. A shift with NO
// rows here is unrestricted (shown/assignable in every department) -- see
// the migration notes for why that's the deliberate default.
$shiftDepartmentMap = [];
$deptLinkRes = $conn->query("SELECT shift_id, department_id FROM shift_departments");
if ($deptLinkRes) {
    while ($row = $deptLinkRes->fetch_assoc()) {
        $shiftDepartmentMap[(string)$row['shift_id']][] = (int)$row['department_id'];
    }
}

// Returns just the shifts assignable within a given department: shifts with
// no department restriction at all, plus any explicitly assigned to this
// department. $keepShiftId (if given) is always included even if it
// wouldn't otherwise qualify -- so a cell that was already assigned a shift
// before it got scoped to a different department still shows what it's
// currently set to, instead of that option silently vanishing.
function getShiftsForDepartment($shifts, $shiftDepartmentMap, $deptId, $keepShiftId = null) {
    $filtered = [];
    foreach ($shifts as $shift) {
        $assignedDepts = $shiftDepartmentMap[(string)$shift['id']] ?? [];
        $isUnrestricted = empty($assignedDepts);
        $isAllowedHere = $isUnrestricted || in_array((int)$deptId, $assignedDepts, true);
        if ($isAllowedHere) {
            $filtered[$shift['id']] = $shift;
        }
    }
    if ($keepShiftId && !isset($filtered[$keepShiftId]) && isset($shifts[$keepShiftId])) {
        $filtered[$keepShiftId] = $shifts[$keepShiftId];
    }
    return $filtered;
}

// ---------------------------
// INCLUDE ISOLATED FILTER LOGIC MODULE
// ---------------------------
require 'filter_managers.php';

// Restrict leave data to only the employees actually visible to this viewer.
// $approvedLeaves was fetched company-wide above; without this, every other
// employee's leave records would still ride along in the page's JS
// (APPROVED_LEAVES_DATA) even when the grid itself only renders rows for
// people this user is allowed to see.
$visibleEmployeeIds = [];
foreach ($employees as $deptEmployees) {
    foreach ($deptEmployees as $emp) {
        $visibleEmployeeIds[(int)$emp['id']] = true;
    }
}
$approvedLeaves = array_intersect_key($approvedLeaves, $visibleEmployeeIds);

// ---------------------------
// RENDER ROSTER MATRIX DATASET
// ---------------------------
$schedule = [];
if ($userRole === 'staff') {
    // Plain staff only ever get their own shift data -- this feeds
    // MATRIX_ROSTER_DATA, which is sent to the browser as JSON, so scoping
    // it here (not just filtering which rows get rendered) matters: without
    // this, a curious staff member could read every other employee's shift
    // assignments straight out of the page source / dev tools even though
    // the visible grid only shows their own row.
    $stmt = $conn->prepare("
        SELECT ds.*, s.name AS shift_name
        FROM department_schedules ds
        LEFT JOIN shifts s ON s.id = ds.shift_id
        WHERE ds.employee_id = ?
    ");
    $stmt->bind_param("i", $loggedInEmployeeId);
    $stmt->execute();
    $res = $stmt->get_result();
} elseif ($userRole === 'manager' || $userRole === 'supervisor') {
    $stmt = $conn->prepare("
        SELECT ds.*, s.name AS shift_name 
        FROM department_schedules ds
        LEFT JOIN shifts s ON s.id = ds.shift_id
        INNER JOIN employees e ON e.id = ds.employee_id
        WHERE FIND_IN_SET(?, e.department_id) > 0
    ");
    $stmt->bind_param("i", $userDeptId);
    $stmt->execute();
    $res = $stmt->get_result();
} else {
    $res = $conn->query("
        SELECT ds.*, s.name AS shift_name 
        FROM department_schedules ds
        LEFT JOIN shifts s ON s.id = ds.shift_id
    ");
}

while ($row = $res->fetch_assoc()) {
    $empId = intval($row['employee_id']);
    $date = $row['schedule_date'];
    $schedule[$empId][$date] = [
        'shift_id'   => $row['shift_id'],
        'shift_name' => $row['shift_name']
    ];
}
if (isset($stmt) && in_array($userRole, ['staff', 'manager', 'supervisor'], true)) {
    $stmt->close();
}

// ---------------------------
// HELPER: RENDER SCHEDULE GRID HTML
// ---------------------------
function renderScheduleGrid($departmentOrder, $departments, $employees, $days, $approvedLeaves, $schedule, $shifts, $shiftColors, $userRole, $shiftTextColors, $shiftTimes, $shiftDepartmentMap) {
    ob_start();
    ?>
    <!-- Mobile Screen Layout View -->
    <div class="block md:hidden no-print mobile-only space-y-4">
        <p class="text-[10px] font-black text-slate-500 uppercase tracking-wider">🗓️ Select Day Parameters:</p>
        <div class="flex gap-2 overflow-x-auto pb-2 pt-0.5 no-scrollbar snap-x">
            <?php foreach ($days as $index => $day): ?>
                <button type="button" onclick="activateMobileDay('<?= $day ?>')" id="mb-btn-<?= $day ?>" class="mobile-date-tab shrink-0 snap-center flex flex-col items-center justify-center w-12 h-14 rounded-xl border transition-all duration-200 <?= $index === 0 ? 'bg-slate-900 border-slate-950 text-white shadow-xs' : 'bg-white border-slate-300 text-slate-700' ?>">
                    <span class="text-[9px] font-black uppercase tracking-tight opacity-80"><?= date('D', strtotime($day)) ?></span>
                    <span class="text-sm font-black font-mono mt-0.5"><?= date('d', strtotime($day)) ?></span>
                </button>
            <?php endforeach; ?>
        </div>

        <div class="space-y-4">
            <?php foreach ($departmentOrder as $deptId): ?>
                <?php if (!isset($employees[$deptId])) continue; ?>
                <div class="mobile-dept-block bg-white rounded-xl border border-slate-300/80 shadow-sm overflow-hidden">
                    <div class="bg-stone-50 border-b border-slate-300/80 px-4 py-2 text-slate-950 text-[10px] font-black uppercase tracking-wider">
                        📂 <?= htmlspecialchars((string)($departments[$deptId]['name'] ?? 'Unknown branch')) ?>
                    </div>
                    <div class="divide-y divide-slate-200/80 font-bold text-xs">
                        <?php foreach ($employees[$deptId] as $emp): ?>
                            <div class="p-3 flex items-center justify-between gap-4 hover:bg-stone-50/50 transition">
                                <div class="min-w-0 flex-1 space-y-0.5">
                                    <h4 class="font-extrabold text-slate-900 text-xs truncate"><?= htmlspecialchars($emp['name']) ?></h4>
                                    <p class="text-[9px] text-slate-500 font-black uppercase tracking-wider" id="mb-subtext-<?= $emp['id'] ?>">Adjust shift mapping</p>
                                </div>
                                <div class="shrink-0 w-32 h-9 rounded-lg relative overflow-hidden transition-all duration-300 border border-slate-300" id="mb-wrapper-<?= $emp['id'] ?>">
                                    <div class="absolute inset-0 flex items-center justify-center px-2 text-[10px] font-black uppercase tracking-wide text-center pointer-events-none" id="mb-label-<?= $emp['id'] ?>"></div>
                                    <select class="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
                                            id="mb-select-<?= $emp['id'] ?>"
                                            data-dept="<?= $deptId ?>"
                                            <?= !in_array($userRole, ['manager','admin','superadmin','supervisor','hr']) ? 'disabled' : '' ?>
                                            onchange="saveMobileShift(<?= $emp['id'] ?>, this.value, this, <?= $deptId ?>)">
                                    </select>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>
    </div>

    <!-- Desktop Grid Presentation Layer -->
    <div class="hidden md:block desktop-only bg-white border border-slate-300/80 rounded-xl shadow-sm overflow-hidden">
        <div class="overflow-x-auto w-full">
            <table class="w-full border-collapse">
                <thead>
                    <tr class="bg-stone-50 text-slate-500 uppercase text-[9px] font-black tracking-wider border-b border-slate-300/80">
                        <th class="p-3 text-left bg-stone-50 text-slate-950 font-black sticky left-0 z-30 min-w-[180px] border-r border-slate-300/80 shadow-xs">
                            Staff Deployment
                        </th>
                        <?php foreach ($days as $day): ?>
                            <th class="p-2 text-center border-r border-slate-200 last:border-0 min-w-[95px]">
                                <div class="text-[10px] font-black text-indigo-700"><?= date('D', strtotime($day)) ?></div>
                                <div class="text-[9px] text-slate-500 font-mono font-bold mt-0.5"><?= date('d M', strtotime($day)) ?></div>
                            </th>
                        <?php endforeach; ?>
                    </tr>
                </thead>
                <tbody class="divide-y divide-slate-200/80 text-xs font-bold">
                    <?php foreach ($departmentOrder as $deptId): ?>
                        <?php if (!isset($employees[$deptId])): ?>
                            <tr>
                                <td colspan="<?= count($days) + 1 ?>" class="p-4 text-center text-slate-500 text-xs font-semibold">
                                    No staff assigned to this department.
                                </td>
                            </tr>
                            <?php continue; ?>
                        <?php endif; ?>
                        <tr class="bg-slate-900 text-white font-black uppercase text-[10px] tracking-wider border-b border-slate-950">
                            <td colspan="<?= count($days) + 1 ?>" class="p-2.5 bg-slate-900 sticky left-0 z-10">
                                📂 <?= htmlspecialchars((string)($departments[$deptId]['name'] ?? 'Unknown Branch')) ?>
                            </td>
                        </tr>
                        <?php $deptShifts = getShiftsForDepartment($shifts, $shiftDepartmentMap, $deptId); ?>
                        <?php foreach ($employees[$deptId] as $emp): ?>
                            <tr class="hover:bg-stone-50/50 transition group border-b border-slate-200/80 last:border-0">
                                <td class="p-3 text-xs font-extrabold text-slate-900 bg-white sticky left-0 z-10 staff-name-col border-r border-slate-300/80 group-hover:bg-stone-50 transition whitespace-nowrap shadow-xs">
                                    <?= htmlspecialchars($emp['name']) ?>
                                </td>
                                <?php foreach ($days as $day): ?>
                                    <?php
                                    $hasLeave = isset($approvedLeaves[$emp['id']][$day]);
                                    $currentShiftId = $schedule[$emp['id']][$day]['shift_id'] ?? '';
                                    $isAuthorized = in_array($userRole, ['manager','admin','superadmin','supervisor','hr']);

                                    $chipColor = $currentShiftId ? ($shiftColors[$currentShiftId] ?? '#475569') : '';
                                    $chipTextColor = $currentShiftId ? ($shiftTextColors[$currentShiftId] ?? '#ffffff') : '#94a3b8';
                                    ?>
                                    <td class="p-1.5 border-r border-slate-200 last:border-0 schedule-cell-td" data-desktop-cell="<?= $emp['id'] ?>-<?= $day ?>">
                                        <?php if ($hasLeave): ?>
                                            <div class="shift-chip shift-chip-leave" title="On Approved Leave: <?= htmlspecialchars($approvedLeaves[$emp['id']][$day]['name']) ?>">
                                                <span class="shift-chip-leave-icon">🌴</span>
                                                <span><?= htmlspecialchars($approvedLeaves[$emp['id']][$day]['abbr']) ?></span>
                                            </div>
                                        <?php else: ?>
                                            <div class="shift-chip <?= $currentShiftId ? '' : 'shift-chip-empty' ?> <?= $isAuthorized ? '' : 'shift-chip-readonly' ?>"
                                                 data-shift-chip="<?= $emp['id'] ?>-<?= $day ?>"
                                                 style="<?= $currentShiftId ? 'background:' . htmlspecialchars($chipColor) . ';' : '' ?>">
                                                <div class="shift-chip-label" style="color:<?= htmlspecialchars($chipTextColor) ?>;">
                                                    <?= $currentShiftId && isset($shifts[$currentShiftId]) ? htmlspecialchars($shifts[$currentShiftId]['name']) : '+ Assign' ?>
                                                </div>
                                                <select class="shift-select"
                                                        data-emp="<?= $emp['id'] ?>"
                                                        data-date="<?= $day ?>"
                                                        <?= !$isAuthorized ? 'disabled' : '' ?>
                                                        onchange="saveShift(<?= $emp['id'] ?>, '<?= $day ?>', this.value, this, <?= $deptId ?>)">
                                                    <option value="" <?= $currentShiftId == '' ? 'selected' : '' ?>>Unassigned</option>
                                                    <?php $cellShiftOptions = $currentShiftId && !isset($deptShifts[$currentShiftId]) ? getShiftsForDepartment($shifts, $shiftDepartmentMap, $deptId, $currentShiftId) : $deptShifts; ?>
                                                    <?php foreach ($cellShiftOptions as $shift): ?>
                                                        <?php
                                                        $optColor = $shiftColors[(string)$shift['id']];
                                                        $optText = $shiftTextColors[(string)$shift['id']];
                                                        $optLabel = !empty($shift['description']) ? $shift['description'] : $shift['name'];
                                                        ?>
                                                        <option value="<?= $shift['id'] ?>"
                                                                <?= $currentShiftId == $shift['id'] ? 'selected' : '' ?>
                                                                style="background:<?= htmlspecialchars($optColor) ?>; color:<?= htmlspecialchars($optText) ?>;">
                                                            <?= htmlspecialchars($optLabel) ?>
                                                        </option>
                                                    <?php endforeach; ?>
                                                </select>
                                            </div>
                                        <?php endif; ?>
                                    </td>
                                <?php endforeach; ?>
                            </tr>
                        <?php endforeach; ?>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
    </div>
    <?php
    return ob_get_clean();
}

// ---------------------------
// BACKEND CONTROL: AJAX ENDPOINTS
// ---------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax'])) {
    
    // AJAX: FETCH FILTERED DEPARTMENT SCHEDULE GRID
    if ($_POST['ajax'] === 'fetch_dept_schedule') {
        $filterDeptId = intval($_POST['dept_id'] ?? 0);
        // Re-validate against the same allow-list the initial page load uses
        // -- $departments is already scoped per role (e.g. a plain staff
        // viewer only ever has their own department in it), so this stops a
        // crafted request from asking for a department outside that list.
        if ($filterDeptId <= 0 || !isset($departments[$filterDeptId])) {
            $filterDeptId = array_key_first($departments) ?? 0;
        }
        $departmentOrder = $filterDeptId ? [$filterDeptId] : array_keys($departments);

        $html = renderScheduleGrid($departmentOrder, $departments, $employees, $days, $approvedLeaves, $schedule, $shifts, $shiftColors, $userRole, $shiftTextColors, $shiftTimes, $shiftDepartmentMap);

        header('Content-Type: application/json');
        echo json_encode([
            'status' => 'success',
            'html' => $html
        ]);
        exit;
    }

    // AJAX: SHIFT UPDATE ENDPOINT
    $empId   = intval($_POST['emp_id']);
    $shiftId = intval($_POST['shift_id']);
    $date    = $_POST['date'];
    $currentActiveGridDeptId = isset($_POST['grid_dept_id']) ? intval($_POST['grid_dept_id']) : 0;
    $scheduleYear = (int)date('Y', strtotime($date));

    // Server-side authorization: the "disabled" attribute on the shift
    // picker is only a client-side hint -- it does nothing to stop a direct
    // POST request here, so this endpoint has to independently enforce who
    // is actually allowed to write. Without this, any logged-in user
    // (including plain staff, who the UI treats as view-only) could rewrite
    // any employee's schedule by posting directly to this URL.
    $canEditShifts = in_array($userRole, ['manager', 'admin', 'superadmin', 'supervisor', 'hr'], true);
    if (!$canEditShifts) {
        http_response_code(403);
        exit;
    }

    // 1. Block edit if employee is on locked leave
    if (isEmployeeLeaveLocked($conn, $empId, $date)) {
        echo "leave_locked";
        exit;
    }

    // 2. Safely fetch PH Shift ID using the exact 'name' column
    $phShiftRes = $conn->query("SELECT id FROM shifts WHERE LOWER(TRIM(name)) = 'ph' OR LOWER(name) LIKE '%public holiday%' LIMIT 1");
    $phShiftId = ($phShiftRes && $phShiftRes->num_rows > 0) ? (int)$phShiftRes->fetch_assoc()['id'] : 0;

    // Fetch PH Leave Type ID from leave_types (Defaults to ID 7)
    $phLeaveTypeRes = $conn->query("SELECT id FROM leave_types WHERE LOWER(TRIM(name)) = 'ph' OR LOWER(name) LIKE '%public holiday%' LIMIT 1");
    $phLeaveTypeId = ($phLeaveTypeRes && $phLeaveTypeRes->num_rows > 0) ? (int)$phLeaveTypeRes->fetch_assoc()['id'] : 7;

    // 3. Get existing schedule record to detect shift changes
    $check = $conn->prepare("SELECT id, shift_id FROM department_schedules WHERE employee_id = ? AND schedule_date = ?");
    $check->bind_param("is", $empId, $date);
    $check->execute();
    $existing = $check->get_result()->fetch_assoc();
    $check->close();

    $oldShiftId = $existing['shift_id'] ?? null;

    // Helper: Safely adjust PH leave balances (Allocated Days = Fixed Baseline)
    $updatePhBalance = function($conn, $empId, $scheduleYear, $phLeaveTypeId, $changeAmount) {
        $stmt = $conn->prepare("SELECT id, allocated_days, used_days FROM leave_balances WHERE employee_id = ? AND leave_type_id = ? AND year = ?");
        $stmt->bind_param("iii", $empId, $phLeaveTypeId, $scheduleYear);
        $stmt->execute();
        $lb = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if ($lb) {
            $baselineAllocated = (int)$lb['allocated_days'];
            $newUsedDays      = max(0, (int)$lb['used_days'] + $changeAmount);
            $newRemainingDays = max(0, $baselineAllocated - $newUsedDays);

            $upd = $conn->prepare("
                UPDATE leave_balances 
                SET used_days = ?,
                    remaining_days = ? 
                WHERE id = ?
            ");
            $upd->bind_param("iii", $newUsedDays, $newRemainingDays, $lb['id']);
            $upd->execute();
            $upd->close();
        } else if ($changeAmount > 0) {
            $ltStmt = $conn->prepare("SELECT days_allowed FROM leave_types WHERE id = ?");
            $ltStmt->bind_param("i", $phLeaveTypeId);
            $ltStmt->execute();
            $ltRow = $ltStmt->get_result()->fetch_assoc();
            $ltStmt->close();

            $baselineAllocated = (int)($ltRow['days_allowed'] ?? 7);
            $usedDays          = max(0, $changeAmount);
            $remainingDays     = max(0, $baselineAllocated - $usedDays);

            $ins = $conn->prepare("
                INSERT INTO leave_balances (employee_id, leave_type_id, year, allocated_days, used_days, carry_forward, remaining_days) 
                VALUES (?, ?, ?, ?, ?, 0.00, ?)
            ");
            $ins->bind_param("iiiiii", $empId, $phLeaveTypeId, $scheduleYear, $baselineAllocated, $usedDays, $remainingDays);
            $ins->execute();
            $ins->close();
        }
    };

    // --- CASE A: DELETE SCHEDULE ---
    if (empty($_POST['shift_id'])) {
        if ($existing) {
            if ((int)$oldShiftId === (int)$phShiftId && $phShiftId > 0) {
                $updatePhBalance($conn, $empId, $scheduleYear, $phLeaveTypeId, -1);
            }

            $delete = $conn->prepare("DELETE FROM department_schedules WHERE id = ?");
            $delete->bind_param("i", $existing['id']);
            $delete->execute();
            $delete->close();
        }
        echo "saved";
        exit;
    }

    // --- CASE B: UPDATE OR INSERT SCHEDULE ---
    $deptQuery = $conn->query("SELECT department_id FROM employees WHERE id = $empId");
    $deptData = $deptQuery->fetch_assoc();
    if (!$deptData) exit;
    
    $allowedDeptsArray = explode(',', $deptData['department_id']);
    if (($userRole === 'manager' || $userRole === 'supervisor') && !in_array((string)$userDeptId, $allowedDeptsArray)) {
        exit;
    }

    $targetScheduleDeptId = (in_array((string)$currentActiveGridDeptId, $allowedDeptsArray)) 
        ? $currentActiveGridDeptId 
        : intval($allowedDeptsArray[0] ?? 0);

    // Reject assigning a shift that's restricted to OTHER departments than
    // the one this schedule entry is being recorded against. Clearing an
    // assignment (shiftId 0) is always allowed regardless.
    if ($shiftId > 0) {
        $shiftAssignedDepts = $shiftDepartmentMap[(string)$shiftId] ?? [];
        $isUnrestricted = empty($shiftAssignedDepts);
        if (!$isUnrestricted && !in_array((int)$targetScheduleDeptId, $shiftAssignedDepts, true)) {
            echo "shift_not_allowed_for_department";
            exit;
        }
    }

    if ($existing) {
        $update = $conn->prepare("UPDATE department_schedules SET shift_id = ?, department_id = ? WHERE id = ?");
        $update->bind_param("iii", $shiftId, $targetScheduleDeptId, $existing['id']);
        $update->execute();
        $update->close();

        if ((int)$oldShiftId === (int)$phShiftId && (int)$shiftId !== (int)$phShiftId) {
            $updatePhBalance($conn, $empId, $scheduleYear, $phLeaveTypeId, -1);
        }
        elseif ((int)$oldShiftId !== (int)$phShiftId && (int)$shiftId === (int)$phShiftId) {
            $updatePhBalance($conn, $empId, $scheduleYear, $phLeaveTypeId, 1);
        }
    } else {
        $insert = $conn->prepare("INSERT INTO department_schedules (department_id, employee_id, shift_id, schedule_date) VALUES (?, ?, ?, ?)");
        $insert->bind_param("iiis", $targetScheduleDeptId, $empId, $shiftId, $date);
        $insert->execute();
        $insert->close();

        if ((int)$shiftId === (int)$phShiftId && $phShiftId > 0) {
            $updatePhBalance($conn, $empId, $scheduleYear, $phLeaveTypeId, 1);
        }
    }

    echo "saved";
    exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Roster Management - HR System</title>
    
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Mulish:ital,wght=0,200..1000;1,200..1000&display=swap" rel="stylesheet">
    
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    fontFamily: {
                        sans: ['Mulish', 'sans-serif'],
                    }
                }
            }
        }
    </script>
    <link rel="stylesheet" href="../components/sidebar.css">
    <style>
        .schedule-cell-td {
            min-width: 108px;
            height: 60px;
            vertical-align: middle;
        }
        .shift-chip {
            position: relative;
            height: 100%;
            min-height: 46px;
            border-radius: 10px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            background: #94a3b8;
            box-shadow: 0 1px 2px rgba(15,23,42,.10), inset 0 0 0 1px rgba(255,255,255,.15);
            transition: background-color .2s ease-in-out, box-shadow .15s ease, transform .1s ease;
            overflow: hidden;
        }
        .shift-chip:hover {
            box-shadow: 0 4px 10px rgba(15,23,42,.18), inset 0 0 0 1px rgba(255,255,255,.25);
            transform: translateY(-1px);
        }
        .shift-chip-readonly:hover {
            box-shadow: 0 1px 2px rgba(15,23,42,.10), inset 0 0 0 1px rgba(255,255,255,.15);
            transform: none;
        }
        /* The <select> itself is made invisible (see below) so it can carry
           different open-list text than the closed cell; without this a
           keyboard user tabbing onto a cell would have no visible focus cue
           at all. */
        .shift-chip:focus-within {
            outline: 2px solid #4f46e5;
            outline-offset: 1px;
        }
        .shift-chip-empty {
            background: #f8fafc !important;
            border: 1.5px dashed #cbd5e1;
            box-shadow: none;
        }
        .shift-chip-empty:hover { border-color: #94a3b8; background: #f1f5f9 !important; }
        .shift-chip-empty.shift-chip-readonly:hover { border-color: #cbd5e1; background: #f8fafc !important; }

        .shift-chip-label {
            font-weight: 800;
            font-size: 11px;
            letter-spacing: .01em;
            line-height: 1.25;
            text-align: center;
            padding: 2px 6px;
            pointer-events: none;
        }
        .shift-chip-empty .shift-chip-label { color: #94a3b8 !important; font-weight: 700; }

        /* The real <select> stays fully functional -- clickable, keyboard
           navigable, screen-reader friendly -- but is made invisible.
           .shift-chip-label above is what's actually seen in the closed
           cell. A native <select> always shows its selected option's own
           text when closed, so there's no way for that to differ from what
           the open dropdown list shows on a single visible control; making
           the control itself invisible and overlaying a custom label is the
           standard way around that (the same trick used for custom file
           upload buttons). */
        .shift-select {
            position: absolute;
            inset: 0;
            width: 100%;
            height: 100%;
            opacity: 0;
            border: none;
            outline: none;
            cursor: pointer;
            font-size: 11px;
        }
        .shift-select:disabled { cursor: default; }
        .shift-select option { font-weight: bold; }
        .shift-select::-ms-expand { display: none; }

        .shift-chip-leave {
            align-items: center;
            gap: 1px;
            background-image: repeating-linear-gradient(45deg, rgba(15,23,42,.05) 0 6px, transparent 6px 12px);
            background-color: #f1f5f9;
            color: #475569;
            font-weight: 800;
            font-size: 10px;
            letter-spacing: .05em;
            border: 1px solid #e2e8f0;
        }
        .shift-chip-leave-icon { font-size: 13px; line-height: 1; }

        .staff-name-col { position: sticky; left: 0; z-index: 20; }
        .no-scrollbar::-webkit-scrollbar { display: none; }
        .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }

        .leave-blocked-bg {
            background-image: linear-gradient(45deg, rgba(15, 23, 42, 0.05) 25%, transparent 25%, transparent 50%, rgba(15, 23, 42, 0.05) 50%, rgba(15, 23, 42, 0.05) 75%, transparent 75%, transparent) !important;
            background-size: 12px 12px !important;
            background-color: #f1f5f9 !important;
            color: #475569 !important;
        }

        @media print {
            .no-print, #sidebar-overlay, .mobile-only, header { display: none !important; }
            .desktop-only { display: block !important; }
            body { background: white !important; }
            #main-content { padding: 0 !important; md-left: 0 !important; pl: 0 !important; }
            .schedule-cell-td { min-width: 70px; height: 40px; }
            .shift-chip-label { font-size: 9px; }
            table { font-size: 10px; width: 100%; table-layout: fixed; }
            th, td { padding: 4px !important; }
        }
    </style>
</head>
<body class="bg-stone-100/60 min-h-screen text-slate-800 antialiased selection:bg-indigo-600 selection:text-white font-sans">

    <div class="flex">
        
        <?php include '../components/sidebar.php'; ?>

        <div id="sidebar-overlay" class="fixed inset-0 bg-slate-900/40 hidden z-40 transition-opacity duration-300 no-print" onclick="toggleSidebar()"></div>

        <div id="main-content" class="flex-1 min-w-0 md:pl-60 transition-all duration-300 min-h-screen flex flex-col">
            
            <header class="sticky top-0 bg-white/90 backdrop-blur-md border-b border-slate-300/80 h-14 flex items-center justify-between px-4 sm:px-6 z-30 shadow-xs no-print">
                <div class="flex items-center gap-3">
                    <button onclick="toggleSidebar()" class="p-1.5 rounded-lg text-slate-600 hover:bg-slate-200/80 transition active:scale-95" aria-label="Toggle Side Area Menu">
                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16"></path>
                        </svg>
                    </button>
                    <div>
                        <h1 class="text-sm font-black tracking-tight text-slate-950">Roster Management</h1>
                        <p class="text-[10px] text-indigo-700 font-bold uppercase tracking-wider">Deployment Engine</p>
                    </div>
                </div>
                
                <div class="flex items-center gap-4">
                    <div class="hidden sm:flex flex-col text-right">
                        <span class="text-[11px] font-extrabold text-slate-950 leading-tight"><?= htmlspecialchars($username); ?></span>
                        <span class="text-[9px] text-indigo-700 uppercase font-black tracking-wider leading-none mt-0.5"><?= htmlspecialchars($userRole ?: 'Staff'); ?></span>
                    </div>
                    <div class="w-7 h-7 bg-slate-200 border border-slate-300 rounded-lg flex items-center justify-center font-black text-xs text-slate-700 select-none uppercase">
                        <?= strtoupper(substr($username, 0, 1)); ?>
                    </div>
                </div>
            </header>

            <main class="flex-1 p-4 sm:p-6 max-w-7xl w-full mx-auto space-y-6">

                <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 no-print">
                    <div>
                        <h2 class="text-xl font-black text-slate-950 tracking-tight">Staff Shift Scheduler</h2>
                        <p class="text-[11px] text-slate-600 font-semibold">
                            Period Scope: <span class="font-bold text-indigo-700"><?= date('d M Y', strtotime($startDate)) ?></span> &ndash; <span class="font-bold text-indigo-700"><?= date('d M Y', strtotime($endDate)) ?></span>
                        </p>
                    </div>
                    <button onclick="window.print()" class="w-full sm:w-auto bg-slate-900 hover:bg-slate-800 active:scale-[0.99] transition text-white font-bold px-4 py-2 rounded-lg text-xs border border-slate-950 shadow-sm flex items-center justify-center gap-2">
                        <span>🖨️</span> Print Roster Sheet
                    </button>
                </div>

                <div class="bg-white border border-slate-300/80 rounded-xl p-4 shadow-sm space-y-3 no-print">
                    <div class="flex items-center justify-between border-b border-slate-200 pb-2">
                        <h3 class="text-[10px] font-black text-slate-500 uppercase tracking-wider">Roster Parameters</h3>
                        <span class="text-[9px] font-bold text-indigo-700 uppercase">Configured Payroll Defaults (Day <?= $defaultStartDay ?> to <?= $defaultEndDay ?: 'End' ?>)</span>
                    </div>
                    <form method="GET" class="grid grid-cols-1 sm:grid-cols-4 gap-3">
                        <div>
                            <label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">Department</label>
                            <select name="dept_id" id="dept-filter-select" onchange="fetchDepartmentSchedule(this.value)" class="w-full border border-slate-300 bg-stone-50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-indigo-100 focus:border-indigo-500 outline-none text-xs font-bold text-slate-800">
                                <?php foreach ($departments as $dept): ?>
                                    <option value="<?= $dept['id'] ?>" <?= $filterDeptId == $dept['id'] ? 'selected' : '' ?>>
                                        <?= htmlspecialchars($dept['name']) ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                        <div>
                            <label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">View Type</label>
                            <select name="type" class="w-full border border-slate-300 bg-stone-50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-indigo-100 focus:border-indigo-500 outline-none text-xs font-bold text-slate-800">
                                <option value="payroll" <?= $periodType == 'payroll' ? 'selected' : '' ?>>Payroll Cycle View</option>
                                <option value="weekly" <?= $periodType == 'weekly' ? 'selected' : '' ?>>Weekly View</option>
                                <option value="monthly" <?= $periodType == 'monthly' ? 'selected' : '' ?>>Calendar Month View</option>
                            </select>
                        </div>
                        <div>
                            <label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">Start Date</label>
                            <input type="date" name="start" value="<?= $startDate ?>" class="w-full border border-slate-300 bg-stone-50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-indigo-100 focus:border-indigo-500 outline-none text-xs text-slate-800 font-mono font-bold">
                        </div>
                        <div class="flex items-end">
                            <button type="submit" class="w-full bg-slate-900 hover:bg-slate-800 active:scale-[0.99] transition text-white rounded-lg font-bold py-2 text-xs shadow-sm border border-slate-950">
                                Load Dynamic Grid
                            </button>
                        </div>
                    </form>
                </div>

                <div class="bg-white border border-slate-300/80 rounded-xl p-4 shadow-sm space-y-3 no-print">
                    <div class="flex items-center justify-between border-b border-slate-200 pb-1.5">
                        <h3 class="text-[10px] font-black text-slate-500 uppercase tracking-wider">Roster Legend</h3>
                    </div>
                    <div class="flex flex-wrap gap-2 items-center">
                        <?php foreach ($shifts as $shift): ?>
                            <?php
                            $legendColor = $shiftColors[(string)$shift['id']] ?? '#475569';
                            $legendTextColor = $shiftTextColors[(string)$shift['id']] ?? '#ffffff';
                            $legendTime = $shiftTimes[(string)$shift['id']] ?? '';
                            ?>
                            <div class="px-3 py-1.5 rounded-lg text-[10px] font-extrabold tracking-wide uppercase shadow-xs border border-black/10 flex flex-col items-center justify-center min-w-[80px]" style="background: <?= htmlspecialchars($legendColor) ?>; color: <?= htmlspecialchars($legendTextColor) ?>;">
                                <span><?= htmlspecialchars($shift['name']) ?></span>
                                <?php if ($legendTime): ?>
                                    <span class="text-[8.5px] opacity-90 font-mono tracking-normal normal-case font-semibold mt-0.5 leading-none"><?= htmlspecialchars($legendTime) ?></span>
                                <?php endif; ?>
                            </div>
                        <?php endforeach; ?>
                        <div class="leave-blocked-bg border border-slate-300 px-3 py-1.5 rounded-lg text-[10px] font-extrabold tracking-wide uppercase shadow-xs flex items-center justify-center">
                            🌴 Approved Leave (Locked)
                        </div>
                    </div>
                </div>

                <!-- Container Container for Schedule Matrix Grid and Spinner -->
                <div class="relative min-h-[300px]">
                    <!-- Loading Spinner Overlay -->
                    <div id="roster-loading-spinner" class="hidden absolute inset-0 bg-white/80 backdrop-blur-xs rounded-xl z-50 flex flex-col items-center justify-center gap-3">
                        <svg class="animate-spin h-8 w-8 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                        </svg>
                        <span class="text-xs font-black text-slate-700 tracking-wide uppercase">Updating Schedule Data...</span>
                    </div>

                    <!-- Dynamic Roster Container -->
                    <div id="roster-grid-container">
                        <?= renderScheduleGrid($departmentOrder, $departments, $employees, $days, $approvedLeaves, $schedule, $shifts, $shiftColors, $userRole, $shiftTextColors, $shiftTimes, $shiftDepartmentMap); ?>
                    </div>
                </div>

            </main>

            <footer class="mt-auto border-t border-slate-300 bg-white px-6 py-4 text-center">
                <p class="text-[9px] text-slate-500 font-mono tracking-wider">&copy; 2026 Core HR Engine System Platform • Dynamic Secure Architecture Protocol.</p>
            </footer>
        </div>
    </div>

<script src="../components/sidebar.js"></script>
<script>
const SHIFT_COLORS = <?= json_encode($shiftColors) ?>;
const SHIFT_TEXT_COLORS = <?= json_encode($shiftTextColors) ?>;
const SHIFT_TIMES = <?= json_encode($shiftTimes) ?>;
const SHIFTS_ARRAY = <?= json_encode(array_values($shifts)) ?>;
<?php
// Per-department shift lists for the mobile picker, matching the same
// unrestricted-by-default + always-keep-currently-assigned logic the
// desktop table already applies. Mobile's dropdown options are built in JS
// (not server-rendered <option> tags like desktop), so this needs to be a
// JS-readable lookup rather than something baked directly into markup.
$shiftsByDeptForJs = [];
foreach ($departmentOrder as $deptId) {
    $shiftsByDeptForJs[$deptId] = array_values(getShiftsForDepartment($shifts, $shiftDepartmentMap, $deptId));
}
?>
const SHIFTS_BY_DEPT = <?= json_encode($shiftsByDeptForJs) ?>;
const MATRIX_ROSTER_DATA = <?= json_encode($schedule) ?>;
const APPROVED_LEAVES_DATA = <?= json_encode($approvedLeaves) ?>;

let activeMobileDateStr = "<?= $days[0] ?? date('Y-m-d') ?>";

// Run immediately rather than waiting for DOMContentLoaded -- this script tag
// only ever executes once the surrounding markup already exists, whether
// that's on a normal page load or after an AJAX navigation swap
// (DOMContentLoaded only ever fires once per real browser page load).
// activateMobileDay() is declared with `function` below, so it's hoisted and
// safe to call here even though it's defined later in this file.
activateMobileDay(activeMobileDateStr);

function fetchDepartmentSchedule(deptId) {
    const spinner = document.getElementById('roster-loading-spinner');
    const container = document.getElementById('roster-grid-container');

    spinner.classList.remove('hidden');

    fetch("", {
        method: "POST",
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
            ajax: 'fetch_dept_schedule',
            dept_id: deptId
        })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'success') {
            container.innerHTML = data.html;
            activateMobileDay(activeMobileDateStr);
        } else {
            alert("Error loading department schedule.");
        }
    })
    .catch(err => {
        console.error(err);
        alert("Failed to filter department schedule. Check network connection.");
    })
    .finally(() => {
        spinner.classList.add('hidden');
    });
}

function activateMobileDay(targetDateStr) {
    activeMobileDateStr = targetDateStr;
    
    document.querySelectorAll('.mobile-date-tab').forEach(btn => {
        btn.classList.remove('bg-slate-900', 'border-slate-950', 'text-white', 'shadow-xs');
        btn.classList.add('bg-white', 'border-slate-300', 'text-slate-700');
    });
    
    const activeBtn = document.getElementById('mb-btn-' + targetDateStr);
    if(activeBtn) {
        activeBtn.classList.remove('bg-white', 'border-slate-300', 'text-slate-700');
        activeBtn.classList.add('bg-slate-900', 'border-slate-950', 'text-white', 'shadow-xs');
    }
    
    document.querySelectorAll('[id^="mb-select-"]').forEach(selectEl => {
        const targetEmpId = parseInt(selectEl.id.replace('mb-select-', ''));
        const parentWrapper = document.getElementById('mb-wrapper-' + targetEmpId);
        const labelEl = document.getElementById('mb-label-' + targetEmpId);
        const subtextEl = document.getElementById('mb-subtext-' + targetEmpId);
        
        selectEl.innerHTML = '';
        
        if (APPROVED_LEAVES_DATA[targetEmpId] && APPROVED_LEAVES_DATA[targetEmpId][targetDateStr]) {
            const leaveInfo = APPROVED_LEAVES_DATA[targetEmpId][targetDateStr];
            const leaveOpt = document.createElement('option');
            leaveOpt.value = ""; leaveOpt.textContent = "🌴 " + leaveInfo.abbr; leaveOpt.selected = true;
            selectEl.appendChild(leaveOpt);
            selectEl.disabled = true;
            if(parentWrapper) {
                parentWrapper.className = "shrink-0 w-32 h-9 rounded-lg relative overflow-hidden border border-slate-300 leave-blocked-bg";
                parentWrapper.style.backgroundColor = "";
            }
            if(labelEl) {
                labelEl.textContent = "🌴 " + leaveInfo.abbr;
                labelEl.style.color = "#475569";
            }
            if(subtextEl) {
                subtextEl.textContent = "Locked: " + leaveInfo.name;
                subtextEl.className = "text-[9px] text-amber-600 font-bold mt-0.5";
            }
        } else {
            const isAuthorized = <?= in_array($userRole, ['manager','admin','superadmin','supervisor','hr']) ? 'true' : 'false' ?>;
            selectEl.disabled = !isAuthorized;
            if(subtextEl) {
                subtextEl.textContent = "Adjust shift assignment";
                subtextEl.className = "text-[9px] text-slate-500 font-black uppercase tracking-wider mt-0.5";
            }

            const blankOption = document.createElement('option');
            blankOption.value = ""; blankOption.textContent = "Unassigned";
            selectEl.appendChild(blankOption);
            
            let currentAssignedId = "";
            if (MATRIX_ROSTER_DATA[targetEmpId] && MATRIX_ROSTER_DATA[targetEmpId][targetDateStr]) {
                currentAssignedId = String(MATRIX_ROSTER_DATA[targetEmpId][targetDateStr]['shift_id']);
            }
            if(currentAssignedId === "") blankOption.selected = true;
            
            // The closed pill shows just the shift's short name (via
            // labelEl below); the open option list shows the fuller
            // description instead, falling back to the name if none is set.
            //
            // Shift options are scoped to this employee's department (via
            // the select's own data-dept, set per-department at render
            // time) rather than the full company-wide shift list -- with
            // one exception: if they're already assigned a shift that isn't
            // in that department's allowed list (e.g. it was scoped to a
            // different department after the fact), that shift is still
            // included so the picker doesn't silently drop what's currently
            // set.
            const selectDeptId = selectEl.getAttribute('data-dept');
            let deptShiftList = SHIFTS_BY_DEPT[selectDeptId] || SHIFTS_ARRAY;
            if (currentAssignedId && !deptShiftList.some(s => String(s.id) === currentAssignedId)) {
                const fallbackShift = SHIFTS_ARRAY.find(s => String(s.id) === currentAssignedId);
                if (fallbackShift) deptShiftList = deptShiftList.concat([fallbackShift]);
            }

            let assignedShiftName = "";
            deptShiftList.forEach(shiftSpec => {
                const opt = document.createElement('option');
                opt.value = shiftSpec.id;

                opt.textContent = shiftSpec.description || shiftSpec.name;
                opt.style.backgroundColor = SHIFT_COLORS[shiftSpec.id] || '#475569';
                opt.style.color = SHIFT_TEXT_COLORS[shiftSpec.id] || '#ffffff';
                if(String(shiftSpec.id) === currentAssignedId) {
                    opt.selected = true;
                    assignedShiftName = shiftSpec.name;
                }
                selectEl.appendChild(opt);
            });
            
            if(parentWrapper) {
                if (currentAssignedId) {
                    parentWrapper.className = "shrink-0 w-32 h-9 rounded-lg relative overflow-hidden border border-slate-300";
                    parentWrapper.style.backgroundColor = SHIFT_COLORS[currentAssignedId] ?? "#475569";
                } else {
                    parentWrapper.className = "shrink-0 w-32 h-9 rounded-lg relative overflow-hidden border border-slate-300 shift-chip-empty";
                    parentWrapper.style.backgroundColor = "";
                }
            }
            if(labelEl) {
                labelEl.textContent = currentAssignedId ? assignedShiftName : "+ Assign";
                labelEl.style.color = currentAssignedId ? (SHIFT_TEXT_COLORS[currentAssignedId] ?? "#ffffff") : "#94a3b8";
            }
        }
    });
}

function saveMobileShift(employeeId, shiftIdValue, selectNodeElement, deptId) {
    saveShift(employeeId, activeMobileDateStr, shiftIdValue, selectNodeElement, deptId);
}

function saveShift(empId, date, targetShiftId, selectElement, deptId) {
    if (APPROVED_LEAVES_DATA[empId] && APPROVED_LEAVES_DATA[empId][date]) {
        alert("Action Denied: Employee is on approved leave during this range date parameter.");
        return;
    }

    // Dim the visible label while the save is in flight -- not the <select>
    // itself, which is invisible (opacity:0 via CSS); setting an inline
    // opacity on it would override that and make it flash visible.
    let desktopLabelForDim = document.querySelector(`[data-shift-chip="${empId}-${date}"] .shift-chip-label`);
    let mobileLabelForDim = (date === activeMobileDateStr) ? document.getElementById(`mb-label-${empId}`) : null;
    if (desktopLabelForDim) desktopLabelForDim.style.opacity = "0.4";
    if (mobileLabelForDim) mobileLabelForDim.style.opacity = "0.4";

    fetch("", {
        method: "POST",
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
            ajax: 1, emp_id: empId, shift_id: targetShiftId, date: date, grid_dept_id: deptId 
        })
    })
    .then(response => response.text())
    .then(data => {
        if (data.trim() === "leave_locked") {
            alert("Action Denied: This date range was recently locked by an approved leave request.");
            location.reload(); return;
        }
        if (data.trim() === "shift_not_allowed_for_department") {
            alert("This shift isn't available for this department. Refreshing to show the current list.");
            location.reload(); return;
        }
        if (data.trim() !== "saved") throw new Error("Validation Error Exception");
        if (desktopLabelForDim) desktopLabelForDim.style.opacity = "1";
        if (mobileLabelForDim) mobileLabelForDim.style.opacity = "1";

        if (!MATRIX_ROSTER_DATA[empId]) MATRIX_ROSTER_DATA[empId] = {};
        if (!MATRIX_ROSTER_DATA[empId][date]) MATRIX_ROSTER_DATA[empId][date] = {};
        MATRIX_ROSTER_DATA[empId][date]['shift_id'] = targetShiftId;

        let parsedShiftKey = String(targetShiftId);
        let isAssigned = parsedShiftKey !== "";
        let updatedBgColor = isAssigned ? (SHIFT_COLORS[parsedShiftKey] ?? "#475569") : "";
        let updatedTextColor = isAssigned ? (SHIFT_TEXT_COLORS[parsedShiftKey] ?? "#ffffff") : "#94a3b8";
        let updatedLabelText = "+ Assign";
        if (isAssigned) {
            let matchedShift = SHIFTS_ARRAY.find(s => String(s.id) === parsedShiftKey);
            updatedLabelText = matchedShift ? matchedShift.name : "";
        }

        // Desktop chip: background lives on the .shift-chip wrapper, and the
        // empty/assigned dashed-vs-solid look is driven by the
        // .shift-chip-empty class. The <select> itself is invisible (see
        // CSS) -- .shift-chip-label is the actual visible text, so it needs
        // updating here too, not just the select's value.
        let chipNode = document.querySelector(`[data-shift-chip="${empId}-${date}"]`);
        if (chipNode && !chipNode.classList.contains('shift-chip-leave')) {
            chipNode.style.backgroundColor = updatedBgColor;
            chipNode.classList.toggle('shift-chip-empty', !isAssigned);

            let labelNode = chipNode.querySelector('.shift-chip-label');
            if (labelNode) {
                labelNode.textContent = updatedLabelText;
                labelNode.style.color = updatedTextColor;
            }

            let desktopSelect = chipNode.querySelector('select');
            if (desktopSelect) {
                desktopSelect.value = targetShiftId;
            }
        }

        let mobileSelect = document.getElementById(`mb-select-${empId}`);
        if(mobileSelect && date === activeMobileDateStr && !mobileSelect.disabled) {
            mobileSelect.value = targetShiftId;

            let mobileWrapper = document.getElementById(`mb-wrapper-${empId}`);
            let mobileLabel = document.getElementById(`mb-label-${empId}`);
            if (mobileWrapper) {
                if (isAssigned) {
                    mobileWrapper.className = "shrink-0 w-32 h-9 rounded-lg relative overflow-hidden border border-slate-300";
                    mobileWrapper.style.backgroundColor = updatedBgColor;
                } else {
                    mobileWrapper.className = "shrink-0 w-32 h-9 rounded-lg relative overflow-hidden border border-slate-300 shift-chip-empty";
                    mobileWrapper.style.backgroundColor = "";
                }
            }
            if (mobileLabel) {
                mobileLabel.textContent = updatedLabelText;
                mobileLabel.style.color = updatedTextColor;
            }
        }
    })
    .catch(err => {
        console.error(err);
        if (desktopLabelForDim) desktopLabelForDim.style.opacity = "1";
        if (mobileLabelForDim) mobileLabelForDim.style.opacity = "1";
        alert("CRITICAL SECURITY / CONFIG ERROR: Could not save assignment data parameters.");
    });
}
</script>
</body>
</html>