<?php
declare(strict_types=1);

/**
 * Modul Jadwal Jaga & Poin Keperawatan
 * - Berdiri sendiri di luar modul Logbook
 * - Upload Excel jadwal bulanan
 * - Tampilkan/edit jadwal di UI
 * - Hitung poin per staf
 * - Generate bulan berikutnya dengan pola P-P-S-S-M-M-L-L
 * - Mode kebutuhan jumlah staf per shift atau pembagian grup awal
 *
 * Penempatan yang disarankan:
 *   /modules/jadwal_jaga_poin/index.php
 *
 * Ketergantungan:
 *   - PHP 8+
 *   - mysqli
 *   - ZipArchive (untuk membaca .xlsx)
 *   - config/database.php dengan getAppConnection() atau getConnection()
 */

error_reporting(E_ALL);
ini_set('display_errors', '1');

require_once __DIR__ . '/../../config/database.php';
$conn = function_exists('getAppConnection') ? getAppConnection() : getConnection();
$conn->set_charset('utf8mb4');

if (session_status() !== PHP_SESSION_ACTIVE) session_start();

function jj_e(mixed $v): string {
    return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
}
function jj_exec(mysqli $db, string $sql): bool {
    try { return (bool)$db->query($sql); }
    catch (Throwable $e) { return false; }
}
function jj_one(mysqli $db, string $sql, array $params = [], string $types = ''): ?array {
    $st = $db->prepare($sql);
    if (!$st) return null;
    if ($params) $st->bind_param($types, ...$params);
    $st->execute();
    $rs = $st->get_result();
    $row = $rs ? $rs->fetch_assoc() : null;
    $st->close();
    return $row ?: null;
}
function jj_all(mysqli $db, string $sql, array $params = [], string $types = ''): array {
    $st = $db->prepare($sql);
    if (!$st) return [];
    if ($params) $st->bind_param($types, ...$params);
    $st->execute();
    $rs = $st->get_result();
    $rows = [];
    if ($rs) while ($r = $rs->fetch_assoc()) $rows[] = $r;
    $st->close();
    return $rows;
}
function jj_user(): array {
    $u = $_SESSION['user'] ?? [];
    return [
        'id' => (int)($_SESSION['user_id'] ?? $u['id'] ?? 0),
        'name' => (string)($_SESSION['full_name'] ?? $u['full_name'] ?? $u['name'] ?? 'User'),
        'role' => strtolower((string)($_SESSION['user_role'] ?? $u['role'] ?? 'user')),
        'unit' => (string)($_SESSION['unit'] ?? $u['unit'] ?? ''),
    ];
}
function jj_norm_name(string $name): string {
    $name = strtoupper(trim($name));
    $name = preg_replace('/\b(NS|N\.S|S\.KEP|S KEP|A\.MD\.KEP|A MD KEP|AMD KEP|S\.TR\.KEP|STR KEP|S\.KEP\.NERS|NERS)\b\.?/u', ' ', $name) ?? $name;
    $name = preg_replace('/[^A-Z0-9]+/u', ' ', $name) ?? $name;
    return trim(preg_replace('/\s+/', ' ', $name) ?? $name);
}
function jj_employee_key(?int $employeeId, string $name): string {
    return $employeeId ? 'EMP-'.$employeeId : 'NAME-'.substr(sha1(jj_norm_name($name)), 0, 24);
}
function jj_days(int $month, int $year): int {
    return cal_days_in_month(CAL_GREGORIAN, $month, $year);
}
function jj_month_name(int $m): string {
    return [1=>'Januari',2=>'Februari',3=>'Maret',4=>'April',5=>'Mei',6=>'Juni',
            7=>'Juli',8=>'Agustus',9=>'September',10=>'Oktober',11=>'November',12=>'Desember'][$m] ?? '';
}
function jj_canonical_shift(string $code): string {
    $c = strtoupper(trim($code));
    if ($c === '') return '';
    if (preg_match('/^(L|OFF|-|CUTI|IZIN|SAKIT|LIBUR)$/', $c)) return 'L';
    if (preg_match('/^(PS|PS1|PSE1|EPES1|EPE|PSE)$/', $c)) return 'PS';
    if (preg_match('/^(BO|BO1|BO2|P|P\d+[A-Z]*|PAGI)$/', $c)) return 'P';
    if (preg_match('/^(S|S\d+[A-Z]*|SORE)$/', $c)) return 'S';
    if (preg_match('/^(M|M\d*[A-Z]*|MALAM)$/', $c)) return 'M';
    return 'OTHER';
}
function jj_cycle_index(string $canonical): ?int {
    return match ($canonical) {
        'P' => 0, 'S' => 2, 'M' => 4, 'L' => 6, default => null
    };
}
function jj_shift_class(string $code): string {
    return match (jj_canonical_shift($code)) {
        'P' => 'jj-pagi', 'S' => 'jj-sore', 'M' => 'jj-malam',
        'L' => 'jj-libur', 'PS' => 'jj-double', default => 'jj-other'
    };
}

function jj_cycle_patterns(): array {
    return [
        '3SHIFT_PPSSMMLL' => [
            'label' => '3 Shift: P-P-S-S-M-M-L-L',
            'description' => 'Pola 8 hari: dua pagi, dua sore, dua malam, dua libur.',
            'cycle' => ['P','P','S','S','M','M','L','L'],
            'uses_night' => true,
        ],
        '2SHIFT_PPSLL' => [
            'label' => '2 Shift: P-P-S-S-L-L',
            'description' => 'Pola 6 hari: dua pagi, dua sore, dua libur. Tidak memakai shift malam.',
            'cycle' => ['P','P','S','S','L','L'],
            'uses_night' => false,
        ],
        '2SHIFT_PSLL' => [
            'label' => '2 Shift Ringkas: P-S-L-L',
            'description' => 'Pola 4 hari: pagi, sore, dua libur.',
            'cycle' => ['P','S','L','L'],
            'uses_night' => false,
        ],
    ];
}
function jj_pattern_config(string $patternKey): array {
    $patterns = jj_cycle_patterns();
    return $patterns[$patternKey] ?? $patterns['3SHIFT_PPSSMMLL'];
}
function jj_pattern_key_from_cycle(?string $cyclePattern): string {
    $clean = strtoupper(trim((string)$cyclePattern));
    $clean = str_replace([' ', '–', '—'], ['', '-', '-'], $clean);
    if ($clean === 'P-P-S-S-L-L') return '2SHIFT_PPSLL';
    if ($clean === 'P-S-L-L') return '2SHIFT_PSLL';
    return '3SHIFT_PPSSMMLL';
}
function jj_pattern_cycle_text(array $pattern): string {
    return implode('-', $pattern['cycle']);
}
function jj_pattern_offset_for_group(string $group, array $pattern): int {
    $group = strtoupper(trim($group));
    foreach ($pattern['cycle'] as $idx => $code) {
        if ($code === $group) return $idx;
    }
    return 0;
}
function jj_infer_offset_with_cycle(array $codes, array $cycle): int {
    $observed = [];
    foreach ($codes as $day => $code) {
        $c = jj_canonical_shift($code);
        if (in_array($c, ['P','S','M','L'], true)) $observed[(int)$day] = $c;
    }
    if (!$observed || !$cycle) return 0;
    $bestOffset = 0; $bestScore = -1; $len = count($cycle);
    for ($offset = 0; $offset < $len; $offset++) {
        $score = 0;
        foreach ($observed as $day => $c) {
            if ($cycle[($offset + $day - 1) % $len] === $c) $score++;
        }
        if ($score > $bestScore) { $bestScore = $score; $bestOffset = $offset; }
    }
    return $bestOffset;
}

function jj_parse_holiday_dates(string $raw, int $targetMonth, int $targetYear): array {
    $raw = trim($raw);
    if ($raw === '') return [];
    $parts = preg_split('/[\s,;]+/', $raw) ?: [];
    $out = [];
    foreach ($parts as $p) {
        $p = trim($p);
        if ($p === '') continue;

        if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $p, $m)) {
            $y = (int)$m[1]; $mo = (int)$m[2]; $d = (int)$m[3];
        } elseif (preg_match('/^(\d{1,2})[\/\-](\d{1,2})(?:[\/\-](\d{2,4}))?$/', $p, $m)) {
            $d = (int)$m[1]; $mo = (int)$m[2]; $y = isset($m[3]) && $m[3] !== '' ? (int)$m[3] : $targetYear;
            if ($y < 100) $y += 2000;
        } else {
            continue;
        }

        if ($y === $targetYear && $mo === $targetMonth && checkdate($mo, $d, $y)) {
            $out[sprintf('%04d-%02d-%02d', $y, $mo, $d)] = true;
        }
    }
    return $out;
}
function jj_is_weekend_or_holiday(string $date, array $holidays): bool {
    $dow = (int)date('N', strtotime($date)); // 6 Sabtu, 7 Minggu
    return $dow >= 6 || isset($holidays[$date]);
}
function jj_is_bo_code(string $code): bool {
    return (bool)preg_match('/^BO\d*$/', strtoupper(trim($code)));
}
function jj_bo_staff_code_from_source(array $sourceEntries): ?string {
    $boCounts = [];
    $working = 0;
    foreach ($sourceEntries as $r) {
        $code = strtoupper(trim((string)($r['shift_code'] ?? '')));
        $canonical = jj_canonical_shift($code);
        if (in_array($canonical, ['P','S','M','PS'], true)) $working++;
        if (jj_is_bo_code($code)) {
            $boCounts[$code] = ($boCounts[$code] ?? 0) + 1;
        }
    }
    if (!$boCounts) return null;
    arsort($boCounts);
    $topCode = (string)array_key_first($boCounts);
    $topCount = (int)reset($boCounts);

    // Dianggap staf BO bila kode BO cukup dominan pada bulan sumber.
    // Ini mencegah staf rotasi yang hanya sesekali BO ikut terkunci.
    $threshold = max(3, (int)ceil(max(1, $working) * 0.50));
    return $topCount >= $threshold ? $topCode : null;
}

function jj_token(): string {
    if (empty($_SESSION['jj_csrf'])) $_SESSION['jj_csrf'] = bin2hex(random_bytes(24));
    return $_SESSION['jj_csrf'];
}
function jj_check_token(): void {
    if (!hash_equals((string)($_SESSION['jj_csrf'] ?? ''), (string)($_POST['csrf'] ?? ''))) {
        throw new RuntimeException('Token keamanan tidak valid. Muat ulang halaman.');
    }
}

function jj_schema(mysqli $db): void {
    jj_exec($db, "CREATE TABLE IF NOT EXISTS nursing_shift_master (
        id INT AUTO_INCREMENT PRIMARY KEY,
        shift_code VARCHAR(20) NOT NULL UNIQUE,
        shift_name VARCHAR(100) NOT NULL,
        canonical_code ENUM('P','S','M','L','PS','OTHER') NOT NULL DEFAULT 'OTHER',
        start_time TIME NULL,
        end_time TIME NULL,
        incentive_point DECIMAL(8,2) NOT NULL DEFAULT 0,
        salary_point DECIMAL(8,2) NOT NULL DEFAULT 0,
        is_working TINYINT(1) NOT NULL DEFAULT 1,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    jj_exec($db, "CREATE TABLE IF NOT EXISTS nursing_schedule_months (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        unit_id BIGINT UNSIGNED NOT NULL,
        unit_name VARCHAR(160) NOT NULL,
        month TINYINT NOT NULL,
        year SMALLINT NOT NULL,
        status ENUM('draft','published','locked') NOT NULL DEFAULT 'draft',
        source_type ENUM('upload','generated','manual') NOT NULL DEFAULT 'manual',
        source_month_id BIGINT UNSIGNED NULL,
        cycle_pattern VARCHAR(100) NOT NULL DEFAULT 'P-P-S-S-M-M-L-L',
        need_morning INT NOT NULL DEFAULT 0,
        need_evening INT NOT NULL DEFAULT 0,
        need_night INT NOT NULL DEFAULT 0,
        note VARCHAR(255) NULL,
        created_by BIGINT UNSIGNED NULL,
        created_by_name VARCHAR(160) NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_unit_period(unit_id, year, month),
        INDEX idx_period(year, month)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    jj_exec($db, "CREATE TABLE IF NOT EXISTS nursing_schedule_entries (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        schedule_month_id BIGINT UNSIGNED NOT NULL,
        unit_id BIGINT UNSIGNED NOT NULL,
        employee_id BIGINT UNSIGNED NULL,
        employee_key VARCHAR(40) NOT NULL,
        employee_name VARCHAR(180) NOT NULL,
        schedule_date DATE NOT NULL,
        shift_code VARCHAR(20) NOT NULL,
        incentive_point DECIMAL(8,2) NOT NULL DEFAULT 0,
        salary_point DECIMAL(8,2) NOT NULL DEFAULT 0,
        is_manual_override TINYINT(1) NOT NULL DEFAULT 0,
        source_row INT NULL,
        note VARCHAR(255) NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_month_employee_date(schedule_month_id, employee_key, schedule_date),
        INDEX idx_unit_date(unit_id, schedule_date),
        INDEX idx_employee(employee_id),
        CONSTRAINT fk_nse_month FOREIGN KEY(schedule_month_id)
            REFERENCES nursing_schedule_months(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    jj_exec($db, "CREATE TABLE IF NOT EXISTS nursing_schedule_groups (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        unit_id BIGINT UNSIGNED NOT NULL,
        employee_id BIGINT UNSIGNED NULL,
        employee_key VARCHAR(40) NOT NULL,
        employee_name VARCHAR(180) NOT NULL,
        group_code ENUM('P','S','M','L') NOT NULL,
        cycle_offset TINYINT NOT NULL DEFAULT 0,
        effective_from DATE NOT NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_unit_employee(unit_id, employee_key)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    jj_exec($db, "CREATE TABLE IF NOT EXISTS nursing_schedule_uploads (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        schedule_month_id BIGINT UNSIGNED NULL,
        unit_id BIGINT UNSIGNED NOT NULL,
        month TINYINT NOT NULL,
        year SMALLINT NOT NULL,
        original_filename VARCHAR(255) NOT NULL,
        imported_staff INT NOT NULL DEFAULT 0,
        imported_cells INT NOT NULL DEFAULT 0,
        unmatched_staff INT NOT NULL DEFAULT 0,
        uploaded_by BIGINT UNSIGNED NULL,
        uploaded_by_name VARCHAR(160) NULL,
        uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    jj_exec($db, "CREATE TABLE IF NOT EXISTS nursing_schedule_audit (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        schedule_month_id BIGINT UNSIGNED NULL,
        action_name VARCHAR(80) NOT NULL,
        detail TEXT NULL,
        user_id BIGINT UNSIGNED NULL,
        user_name VARCHAR(160) NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    $seed = [
        ['P','Pagi','P','07:00:00','14:00:00',1,0,1],
        ['P1','Pagi 1','P','07:00:00','14:00:00',1,0,1],
        ['P1E','Pagi Ekstra','P','07:00:00','16:00:00',1,0,1],
        ['S','Sore','S','14:00:00','21:00:00',1,0,1],
        ['S1','Sore 1','S','14:00:00','21:00:00',1,0,1],
        ['S1E','Sore Ekstra','S','13:00:00','21:00:00',1,0,1],
        ['M','Malam','M','21:00:00','07:00:00',2,0,1],
        ['M2X','Malam Khusus','M','21:00:00','07:00:00',2,0,1],
        ['PS1','Pagi-Sore','PS','07:00:00','21:00:00',2,0,1],
        ['PSE1','Pagi-Sore Extended','PS','07:00:00','21:00:00',2,0,1],
        ['BO1','Back Office 1','P','08:00:00','16:00:00',1,0,1],
        ['BO2','Back Office 2','P','08:00:00','16:00:00',1,0,1],
        ['L','Libur','L',null,null,0,0,0],
        ['OFF','Libur','L',null,null,0,0,0],
        ['CUTI','Cuti','L',null,null,0,0,0],
        ['IZIN','Izin','L',null,null,0,0,0],
        ['SAKIT','Sakit','L',null,null,0,0,0],
        ['-','Tidak Dijadwalkan','L',null,null,0,0,0],
        ['P3','Pagi 3 / HD','P','07:00:00','14:00:00',1,0,1],
        ['S1HD','Sore HD','S','14:00:00','21:00:00',1,0,1],
        ['P1E','Pagi Ekstra','P','07:00:00','16:00:00',1,0,1],
        ['S1E','Sore Ekstra','S','13:00:00','21:00:00',1,0,1],
        ['PS1','Pagi-Sore','PS','07:00:00','21:00:00',2,0,1],
        ['PSE1','Pagi-Sore Extended','PS','07:00:00','21:00:00',2,0,1],
        ['EPES1','Pagi-Sore Extended','PS','07:00:00','21:00:00',2,0,1],
    ];
    $st = $db->prepare("INSERT INTO nursing_shift_master
        (shift_code,shift_name,canonical_code,start_time,end_time,incentive_point,salary_point,is_working)
        VALUES(?,?,?,?,?,?,?,?)
        ON DUPLICATE KEY UPDATE shift_name=VALUES(shift_name),canonical_code=VALUES(canonical_code)");
    if ($st) {
        foreach ($seed as $s) {
            $st->bind_param('sssssdii', $s[0],$s[1],$s[2],$s[3],$s[4],$s[5],$s[6],$s[7]);
            $st->execute();
        }
        $st->close();
    }
}
jj_schema($conn);

function jj_nursing_units(mysqli $db): array {
    $sql = "SELECT id,name FROM units
            WHERE LOWER(name) REGEXP 'igd|gawat darurat|bersalin|(^| )vk($| )|hemodial|(^| )hd($| )|poliklinik|rawat jalan|kamar operasi|(^| )ok($| )|canigara|cempaka|cendana|(^| )icu($| )|nicu|ruang bayi|perinat'
            ORDER BY name";
    return jj_all($db, $sql);
}
function jj_unit_staff(mysqli $db, int $unitId): array {
    if ($unitId <= 0) return [];
    $sql = "SELECT e.id, e.full_name, e.nik,
                   COALESCE(e.position_name,'') position_name,
                   COALESCE(e.employment_status,'aktif') employment_status
            FROM employees e
            WHERE e.unit_id=?
              AND (e.is_active=1 OR e.is_active IS NULL)
              AND LOWER(COALESCE(e.employment_status,'aktif')) NOT IN ('resign','keluar','mutasi keluar','nonaktif')
            ORDER BY e.full_name";
    return jj_all($db, $sql, [$unitId], 'i');
}
function jj_shift_map(mysqli $db): array {
    $rows = jj_all($db, "SELECT * FROM nursing_shift_master WHERE is_active=1");
    $map = [];
    foreach ($rows as $r) $map[strtoupper((string)$r['shift_code'])] = $r;
    return $map;
}
function jj_points(array $shiftMap, string $code): array {
    $u = strtoupper(trim($code));
    if (isset($shiftMap[$u])) return [(float)$shiftMap[$u]['incentive_point'], (float)$shiftMap[$u]['salary_point']];
    return [0.0, 0.0];
}

function jj_default_shift_payload(string $code): array {
    $code = strtoupper(trim($code));
    $canonical = jj_canonical_shift($code);
    $name = match ($canonical) {
        'P' => 'Pagi / Kode '.$code,
        'S' => 'Sore / Kode '.$code,
        'M' => 'Malam / Kode '.$code,
        'L' => 'Libur/Cuti / Kode '.$code,
        'PS' => 'Pagi-Sore / Kode '.$code,
        default => 'Kode Jadwal '.$code,
    };
    $start = match ($canonical) {
        'P' => '07:00:00',
        'S' => '14:00:00',
        'M' => '21:00:00',
        'PS' => '07:00:00',
        default => null,
    };
    $end = match ($canonical) {
        'P' => '14:00:00',
        'S' => '21:00:00',
        'M' => '07:00:00',
        'PS' => '21:00:00',
        default => null,
    };
    $point = match ($canonical) {
        'M','PS' => 2.0,
        'P','S' => 1.0,
        default => 0.0,
    };
    $working = in_array($canonical, ['P','S','M','PS'], true) ? 1 : 0;
    $dbCanonical = in_array($canonical, ['P','S','M','L','PS'], true) ? $canonical : 'OTHER';
    return [$name, $dbCanonical, $start, $end, $point, 0.0, $working];
}
function jj_ensure_shift_code(mysqli $db, string $code): void {
    $code = strtoupper(trim($code));
    if ($code === '') return;
    [$name,$canonical,$start,$end,$ip,$sp,$working] = jj_default_shift_payload($code);
    $st = $db->prepare("INSERT INTO nursing_shift_master
        (shift_code,shift_name,canonical_code,start_time,end_time,incentive_point,salary_point,is_working,is_active)
        VALUES(?,?,?,?,?,?,?,?,1)
        ON DUPLICATE KEY UPDATE is_active=1");
    if ($st) {
        $st->bind_param('sssssdii', $code,$name,$canonical,$start,$end,$ip,$sp,$working);
        $st->execute();
        $st->close();
    }
}
function jj_ensure_shift_codes(mysqli $db, array $codes): void {
    foreach ($codes as $code) jj_ensure_shift_code($db, (string)$code);
}


/** XLSX parser tanpa library eksternal selain ZipArchive. */
function jj_parse_xlsx(string $file): array {
    if (!class_exists('ZipArchive')) throw new RuntimeException('Ekstensi PHP ZipArchive belum aktif.');
    $zip = new ZipArchive();
    if ($zip->open($file) !== true) throw new RuntimeException('File Excel tidak dapat dibuka.');

    $shared = [];
    $sharedXml = $zip->getFromName('xl/sharedStrings.xml');
    if ($sharedXml !== false) {
        $xml = @simplexml_load_string($sharedXml);
        if ($xml) {
            $nodes = $xml->xpath('//*[local-name()="si"]') ?: [];
            foreach ($nodes as $si) {
                $texts = $si->xpath('.//*[local-name()="t"]') ?: [];
                $v = '';
                foreach ($texts as $t) $v .= (string)$t;
                $shared[] = $v;
            }
        }
    }

    $sheetPath = 'xl/worksheets/sheet1.xml';
    if ($zip->locateName($sheetPath) === false) {
        for ($i=0; $i<$zip->numFiles; $i++) {
            $n = $zip->getNameIndex($i);
            if (preg_match('#^xl/worksheets/sheet\d+\.xml$#', (string)$n)) { $sheetPath = (string)$n; break; }
        }
    }
    $sheetXml = $zip->getFromName($sheetPath);
    if ($sheetXml === false) { $zip->close(); throw new RuntimeException('Worksheet Excel tidak ditemukan.'); }
    $xml = @simplexml_load_string($sheetXml);
    if (!$xml) { $zip->close(); throw new RuntimeException('Worksheet Excel tidak valid.'); }

    $rows = [];
    $rowNodes = $xml->xpath('//*[local-name()="sheetData"]/*[local-name()="row"]') ?: [];
    foreach ($rowNodes as $rowNode) {
        $ridx = (int)($rowNode['r'] ?? 0);
        $rows[$ridx] = [];
        $cells = $rowNode->xpath('./*[local-name()="c"]') ?: [];
        foreach ($cells as $c) {
            $ref = (string)($c['r'] ?? '');
            preg_match('/([A-Z]+)(\d+)/', $ref, $m);
            $letters = $m[1] ?? 'A';
            $col = 0;
            for ($k=0; $k<strlen($letters); $k++) $col = $col*26 + (ord($letters[$k])-64);
            $type = (string)($c['t'] ?? '');
            $value = '';
            if ($type === 'inlineStr') {
                $ts = $c->xpath('.//*[local-name()="t"]') ?: [];
                foreach ($ts as $t) $value .= (string)$t;
            } else {
                $vs = $c->xpath('./*[local-name()="v"]') ?: [];
                $raw = isset($vs[0]) ? (string)$vs[0] : '';
                if ($type === 's') $value = $shared[(int)$raw] ?? '';
                else $value = $raw;
            }
            $rows[$ridx][$col] = trim($value);
        }
    }
    $zip->close();
    ksort($rows);
    return $rows;
}

/**
 * Mendeteksi otomatis posisi header tanggal dan kolom nama.
 * Mendukung format:
 * - A1 Nama Pegawai, B1 Tanggal (merge), B2.. tanggal 1..31
 * - Header tanggal berada pada baris lain
 * - Ada kolom tambahan sebelum/sesudah tanggal
 */
function jj_detect_excel_layout(array $rows, int $month, int $year): array {
    $daysInMonth = jj_days($month, $year);
    $bestHeaderRow = 0;
    $bestDateMap = [];
    $bestScore = -1;

    foreach ($rows as $rowNo => $cells) {
        if ($rowNo > 20) break;
        $dateMap = [];
        foreach ($cells as $col => $value) {
            $v = trim((string)$value);
            if ($v === '') continue;

            // Terima angka murni 1..31 atau teks tanggal sederhana.
            if (preg_match('/^\d{1,2}$/', $v)) {
                $day = (int)$v;
                if ($day >= 1 && $day <= $daysInMonth) {
                    $dateMap[(int)$col] = $day;
                }
            } elseif (preg_match('/^(\d{1,2})[\/\-](\d{1,2})(?:[\/\-](\d{2,4}))?$/', $v, $m)) {
                $day = (int)$m[1];
                $mon = (int)$m[2];
                if ($day >= 1 && $day <= $daysInMonth && $mon === $month) {
                    $dateMap[(int)$col] = $day;
                }
            }
        }

        $score = count($dateMap);
        if ($score > $bestScore) {
            $bestScore = $score;
            $bestHeaderRow = (int)$rowNo;
            $bestDateMap = $dateMap;
        }
    }

    if (count($bestDateMap) < min(7, $daysInMonth)) {
        throw new RuntimeException('Header tanggal Excel tidak terdeteksi. Pastikan terdapat tanggal 1 sampai akhir bulan pada satu baris.');
    }

    // Cari kolom nama dari baris header atau baris di atasnya.
    $nameCol = 1;
    $candidateRows = [$bestHeaderRow, $bestHeaderRow - 1, 1];
    foreach ($candidateRows as $rNo) {
        if ($rNo <= 0 || empty($rows[$rNo])) continue;
        foreach ($rows[$rNo] as $col => $value) {
            $label = strtolower(trim((string)$value));
            if ($label !== '' && (
                str_contains($label, 'nama pegawai') ||
                str_contains($label, 'nama staf') ||
                str_contains($label, 'nama perawat') ||
                $label === 'nama'
            )) {
                $nameCol = (int)$col;
                break 2;
            }
        }
    }

    // Jika tidak ada label nama, gunakan kolom tepat sebelum tanggal pertama.
    if ($nameCol === 1 && $bestDateMap) {
        $firstDateCol = min(array_keys($bestDateMap));
        if ($firstDateCol > 1) $nameCol = $firstDateCol - 1;
    }

    ksort($bestDateMap);
    return [
        'header_row' => $bestHeaderRow,
        'data_start_row' => $bestHeaderRow + 1,
        'name_col' => $nameCol,
        'date_map' => $bestDateMap,
    ];
}

function jj_match_employee(array $employees, string $excelName): ?array {
    $needle = jj_norm_name($excelName);
    if ($needle === '') return null;
    foreach ($employees as $e) {
        if (jj_norm_name((string)$e['full_name']) === $needle) return $e;
    }
    $best = null; $bestScore = 0.0;
    foreach ($employees as $e) {
        similar_text($needle, jj_norm_name((string)$e['full_name']), $pct);
        if ($pct > $bestScore) { $bestScore = $pct; $best = $e; }
    }
    return $bestScore >= 86 ? $best : null;
}
function jj_upsert_month(mysqli $db, int $unitId, string $unitName, int $month, int $year, string $sourceType, ?int $sourceMonthId, array $user, int $needP=0, int $needS=0, int $needM=0, string $patternKey='3SHIFT_PPSSMMLL'): int {
    $pattern = jj_pattern_config($patternKey);
    $cyclePattern = jj_pattern_cycle_text($pattern);
    $st = $db->prepare("INSERT INTO nursing_schedule_months
        (unit_id,unit_name,month,year,source_type,source_month_id,cycle_pattern,need_morning,need_evening,need_night,created_by,created_by_name)
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?)
        ON DUPLICATE KEY UPDATE unit_name=VALUES(unit_name),source_type=VALUES(source_type),
        source_month_id=VALUES(source_month_id),cycle_pattern=VALUES(cycle_pattern),
        need_morning=VALUES(need_morning), need_evening=VALUES(need_evening),
        need_night=VALUES(need_night),updated_at=NOW()");
    $sid = $sourceMonthId ?: null;
    $st->bind_param('isiisisiiiis', $unitId,$unitName,$month,$year,$sourceType,$sid,$cyclePattern,$needP,$needS,$needM,$user['id'],$user['name']);
    $st->execute(); $st->close();
    $row = jj_one($db, "SELECT id FROM nursing_schedule_months WHERE unit_id=? AND month=? AND year=?", [$unitId,$month,$year], 'iii');
    return (int)($row['id'] ?? 0);
}
function jj_insert_entry(mysqli $db, int $monthId, int $unitId, ?int $employeeId, string $employeeName, string $date, string $code, array $shiftMap, bool $override=false, ?int $sourceRow=null): void {
    [$ip,$sp] = jj_points($shiftMap, $code);
    $key = jj_employee_key($employeeId, $employeeName);
    $st = $db->prepare("INSERT INTO nursing_schedule_entries
        (schedule_month_id,unit_id,employee_id,employee_key,employee_name,schedule_date,shift_code,incentive_point,salary_point,is_manual_override,source_row)
        VALUES(?,?,?,?,?,?,?,?,?,?,?)
        ON DUPLICATE KEY UPDATE employee_id=VALUES(employee_id),employee_name=VALUES(employee_name),
        shift_code=VALUES(shift_code),incentive_point=VALUES(incentive_point),salary_point=VALUES(salary_point),
        is_manual_override=VALUES(is_manual_override),source_row=VALUES(source_row),updated_at=NOW()");
    $eid = $employeeId ?: null;
    $ov = $override ? 1 : 0;
    $st->bind_param('iiissssddii', $monthId,$unitId,$eid,$key,$employeeName,$date,$code,$ip,$sp,$ov,$sourceRow);
    $st->execute(); $st->close();
}
function jj_infer_offset(array $codes): int {
    $cycle = ['P','P','S','S','M','M','L','L'];
    $observed = [];
    foreach ($codes as $day=>$code) {
        $c = jj_canonical_shift($code);
        if (in_array($c,['P','S','M','L'],true)) $observed[(int)$day] = $c;
    }
    if (!$observed) return 0;
    $bestOffset=0; $best=-1;
    for ($offset=0;$offset<8;$offset++) {
        $score=0;
        foreach ($observed as $day=>$c) {
            $expected = $cycle[($offset + $day - 1) % 8];
            if ($expected === $c) $score++;
        }
        if ($score > $best) { $best=$score; $bestOffset=$offset; }
    }
    return $bestOffset;
}

$user = jj_user();
$notice = '';
$error = '';
$tab = (string)($_GET['tab'] ?? 'dashboard');
$month = max(1,min(12,(int)($_GET['month'] ?? date('n'))));
$year = max(2020,min(2100,(int)($_GET['year'] ?? date('Y'))));
$unitId = (int)($_GET['unit_id'] ?? 0);
$units = jj_nursing_units($conn);
if ($unitId <= 0 && $units) $unitId = (int)$units[0]['id'];
$unitRow = null;
foreach ($units as $u) if ((int)$u['id']===$unitId) $unitRow=$u;
$unitName = (string)($unitRow['name'] ?? '');
$shiftMap = jj_shift_map($conn);

try {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        jj_check_token();
        $action = (string)($_POST['action'] ?? '');

        if ($action === 'upload_schedule') {
            $pUnit = (int)($_POST['unit_id'] ?? 0);
            $pMonth = (int)($_POST['month'] ?? 0);
            $pYear = (int)($_POST['year'] ?? 0);
            $urow = jj_one($conn, "SELECT id,name FROM units WHERE id=?", [$pUnit], 'i');
            if (!$urow) throw new RuntimeException('Unit tidak ditemukan.');
            if (empty($_FILES['schedule_file']['tmp_name'])) throw new RuntimeException('Pilih file Excel terlebih dahulu.');
            $ext = strtolower(pathinfo((string)$_FILES['schedule_file']['name'], PATHINFO_EXTENSION));
            if ($ext !== 'xlsx') throw new RuntimeException('Format file harus .xlsx.');

            $rows = jj_parse_xlsx((string)$_FILES['schedule_file']['tmp_name']);
            $layout = jj_detect_excel_layout($rows, $pMonth, $pYear);
            $employees = jj_unit_staff($conn, $pUnit);
            $monthId = jj_upsert_month($conn,$pUnit,(string)$urow['name'],$pMonth,$pYear,'upload',null,$user);
            $conn->begin_transaction();
            try {
                $conn->query("DELETE FROM nursing_schedule_entries WHERE schedule_month_id=".$monthId);
                $staffCount=0; $cellCount=0; $unmatched=0;
                foreach ($rows as $rowNo=>$cells) {
                    if ($rowNo < (int)$layout['data_start_row']) continue;

                    $name = trim((string)($cells[(int)$layout['name_col']] ?? ''));
                    if ($name === '') continue;

                    // Abaikan baris total/keterangan.
                    $nameLower = strtolower($name);
                    if (str_contains($nameLower, 'total') || str_contains($nameLower, 'keterangan')) continue;

                    $emp = jj_match_employee($employees,$name);
                    $eid = $emp ? (int)$emp['id'] : null;
                    $display = $emp ? (string)$emp['full_name'] : $name;
                    if (!$emp) $unmatched++;

                    $has=false;
                    foreach ($layout['date_map'] as $excelCol => $day) {
                        $code = strtoupper(trim((string)($cells[(int)$excelCol] ?? '')));
                        if ($code === '') continue;

                        // Simpan persis kode jadwal yang tertulis di Excel.
                        // Jika kode belum ada di master shift, sistem membuatnya otomatis
                        // agar UI dropdown tidak mengganti tampilan ke kode pertama.
                        jj_ensure_shift_code($conn, $code);
                        $shiftMap = jj_shift_map($conn);
                        jj_insert_entry(
                            $conn,
                            $monthId,
                            $pUnit,
                            $eid,
                            $display,
                            sprintf('%04d-%02d-%02d',$pYear,$pMonth,(int)$day),
                            $code,
                            $shiftMap,
                            false,
                            (int)$rowNo
                        );
                        $cellCount++;
                        $has=true;
                    }
                    if ($has) $staffCount++;
                }
                $st=$conn->prepare("INSERT INTO nursing_schedule_uploads
                    (schedule_month_id,unit_id,month,year,original_filename,imported_staff,imported_cells,unmatched_staff,uploaded_by,uploaded_by_name)
                    VALUES(?,?,?,?,?,?,?,?,?,?)");
                $fn=(string)$_FILES['schedule_file']['name'];
                $st->bind_param('iiiisiiiis',$monthId,$pUnit,$pMonth,$pYear,$fn,$staffCount,$cellCount,$unmatched,$user['id'],$user['name']);
                $st->execute(); $st->close();
                $conn->commit();
                $notice="Upload berhasil: {$staffCount} staf, {$cellCount} jadwal, {$unmatched} nama belum cocok otomatis.";
                $unitId=$pUnit; $month=$pMonth; $year=$pYear; $tab='schedule';
            } catch (Throwable $e) {
                $conn->rollback(); throw $e;
            }
        }

        if ($action === 'save_schedule_grid') {
            $monthId=(int)($_POST['schedule_month_id'] ?? 0);
            $codes=$_POST['shift'] ?? [];
            if (!is_array($codes)) $codes=[];
            $conn->begin_transaction();
            try {
                $st=$conn->prepare("UPDATE nursing_schedule_entries SET shift_code=?,incentive_point=?,salary_point=?,is_manual_override=1,updated_at=NOW() WHERE id=?");
                foreach ($codes as $entryId=>$code) {
                    $entryId=(int)$entryId; $code=strtoupper(trim((string)$code));
                    jj_ensure_shift_code($conn, $code);
                    $shiftMap = jj_shift_map($conn);
                    [$ip,$sp]=jj_points($shiftMap,$code);
                    $st->bind_param('sddi',$code,$ip,$sp,$entryId); $st->execute();
                }
                $st->close();
                $conn->commit();
                $notice='Perubahan jadwal berhasil disimpan.';
                $tab='schedule';
            } catch(Throwable $e){$conn->rollback();throw $e;}
        }

        if ($action === 'save_shift_master') {
            $code=strtoupper(trim((string)($_POST['shift_code']??'')));
            $name=trim((string)($_POST['shift_name']??''));
            $canonical=(string)($_POST['canonical_code']??'OTHER');
            $ip=(float)($_POST['incentive_point']??0);
            $sp=(float)($_POST['salary_point']??0);
            $working=isset($_POST['is_working'])?1:0;
            if($code===''||$name==='') throw new RuntimeException('Kode dan nama shift wajib diisi.');
            $st=$conn->prepare("INSERT INTO nursing_shift_master(shift_code,shift_name,canonical_code,incentive_point,salary_point,is_working)
                VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE shift_name=VALUES(shift_name),canonical_code=VALUES(canonical_code),
                incentive_point=VALUES(incentive_point),salary_point=VALUES(salary_point),is_working=VALUES(is_working)");
            $st->bind_param('sssddi',$code,$name,$canonical,$ip,$sp,$working);$st->execute();$st->close();
            $notice='Master shift berhasil disimpan.'; $tab='master';
            $shiftMap=jj_shift_map($conn);
        }

        if ($action === 'generate_next') {
            $srcId=(int)($_POST['source_month_id']??0);
            $targetMonth=(int)($_POST['target_month']??0);
            $targetYear=(int)($_POST['target_year']??0);
            $mode=(string)($_POST['generate_mode']??'continue');
            $patternKey=(string)($_POST['pattern_key']??'3SHIFT_PPSSMMLL');
            $pattern=jj_pattern_config($patternKey);
            $cycle=$pattern['cycle'];
            $needP=max(0,(int)($_POST['need_p']??0));
            $needS=max(0,(int)($_POST['need_s']??0));
            $needM=$pattern['uses_night'] ? max(0,(int)($_POST['need_m']??0)) : 0;
            $holidayRaw=(string)($_POST['holiday_dates']??'');
            $holidayDates=jj_parse_holiday_dates($holidayRaw,$targetMonth,$targetYear);
            $src=jj_one($conn,"SELECT * FROM nursing_schedule_months WHERE id=?",[$srcId],'i');
            if(!$src) throw new RuntimeException('Jadwal sumber tidak ditemukan.');
            $pUnit=(int)$src['unit_id'];
            $monthId=jj_upsert_month($conn,$pUnit,(string)$src['unit_name'],$targetMonth,$targetYear,'generated',$srcId,$user,$needP,$needS,$needM,$patternKey);
            $staffRows=jj_all($conn,"SELECT employee_id,employee_key,MAX(employee_name) employee_name
                FROM nursing_schedule_entries WHERE schedule_month_id=? GROUP BY employee_id,employee_key ORDER BY employee_name",[$srcId],'i');
            if(!$staffRows) throw new RuntimeException('Jadwal sumber belum memiliki staf.');

            $groupAssign=$_POST['group']??[];
            if(!is_array($groupAssign))$groupAssign=[];
            if($mode==='needs'){
                $idx=0;
                foreach($staffRows as $s){
                    if ($pattern['uses_night']) {
                        $g = $idx<$needP?'P':($idx<$needP+$needS?'S':($idx<$needP+$needS+$needM?'M':'L'));
                    } else {
                        $g = $idx<$needP?'P':($idx<$needP+$needS?'S':'L');
                    }
                    $groupAssign[(string)$s['employee_key']]=$g; $idx++;
                }
            }

            $daysTarget=jj_days($targetMonth,$targetYear);
            $daysSource=jj_days((int)$src['month'],(int)$src['year']);
            $conn->begin_transaction();
            try{
                $conn->query("DELETE FROM nursing_schedule_entries WHERE schedule_month_id=".$monthId);
                foreach($staffRows as $s){
                    $eid=(int)($s['employee_id']??0) ?: null;
                    $key=(string)$s['employee_key']; $name=(string)$s['employee_name'];
                    $srcEntries=jj_all($conn,"SELECT DAY(schedule_date) d,schedule_date,shift_code FROM nursing_schedule_entries
                        WHERE schedule_month_id=? AND employee_key=? ORDER BY schedule_date",[$srcId,$key],'is');
                    $boCode = jj_bo_staff_code_from_source($srcEntries);

                    if($boCode !== null){
                        // Staf Back Office tidak ikut rotasi. Hari Senin-Jumat tetap BO,
                        // Sabtu/Minggu dan tanggal libur khusus menjadi L.
                        for($d=1;$d<=$daysTarget;$d++){
                            $date = sprintf('%04d-%02d-%02d',$targetYear,$targetMonth,$d);
                            $code = jj_is_weekend_or_holiday($date, $holidayDates) ? 'L' : $boCode;
                            jj_insert_entry($conn,$monthId,$pUnit,$eid,$name,$date,$code,$shiftMap,false,null);
                        }
                        $gCode='P';
                        $startOffset=0;
                    } else {
                        if($mode==='continue'){
                            $codes=[]; foreach($srcEntries as $r)$codes[(int)$r['d']]=(string)$r['shift_code'];
                            $srcPatternKey = jj_pattern_key_from_cycle((string)($src['cycle_pattern'] ?? 'P-P-S-S-M-M-L-L'));
                            $srcPattern = jj_pattern_config($srcPatternKey);
                            $sourceOffset = jj_infer_offset_with_cycle($codes, $srcPattern['cycle']);
                            $startOffset = ($patternKey === $srcPatternKey)
                                ? (($sourceOffset + $daysSource) % count($cycle))
                                : jj_pattern_offset_for_group($srcPattern['cycle'][$sourceOffset % count($srcPattern['cycle'])] ?? 'P', $pattern);
                        } else {
                            $g=(string)($groupAssign[$key]??'L');
                            $startOffset=jj_pattern_offset_for_group($g, $pattern);
                        }
                        for($d=1;$d<=$daysTarget;$d++){
                            $code=$cycle[($startOffset+$d-1)%count($cycle)];
                            jj_insert_entry($conn,$monthId,$pUnit,$eid,$name,sprintf('%04d-%02d-%02d',$targetYear,$targetMonth,$d),$code,$shiftMap,false,null);
                        }
                        $gCode=$cycle[$startOffset % count($cycle)] ?? 'L';
                    }
                    $stg=$conn->prepare("INSERT INTO nursing_schedule_groups(unit_id,employee_id,employee_key,employee_name,group_code,cycle_offset,effective_from)
                        VALUES(?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE employee_id=VALUES(employee_id),employee_name=VALUES(employee_name),
                        group_code=VALUES(group_code),cycle_offset=VALUES(cycle_offset),effective_from=VALUES(effective_from),is_active=1");
                    $eff=sprintf('%04d-%02d-01',$targetYear,$targetMonth);
                    $stg->bind_param('iisssis',$pUnit,$eid,$key,$name,$gCode,$startOffset,$eff);$stg->execute();$stg->close();
                }
                $conn->commit();
                $notice='Jadwal bulan berikutnya berhasil digenerate.';
                $unitId=$pUnit;$month=$targetMonth;$year=$targetYear;$tab='schedule';
            }catch(Throwable $e){$conn->rollback();throw $e;}
        }

        if ($action === 'publish_month') {
            $mid=(int)($_POST['schedule_month_id']??0);
            $st=$conn->prepare("UPDATE nursing_schedule_months SET status='published' WHERE id=?");
            $st->bind_param('i',$mid);$st->execute();$st->close();
            $notice='Jadwal telah dipublikasikan.';$tab='schedule';
        }
    }
} catch (Throwable $e) {
    $error=$e->getMessage();
}

if (isset($_GET['export']) && $_GET['export']==='points') {
    $mid=(int)($_GET['schedule_month_id']??0);
    $mrow=jj_one($conn,"SELECT * FROM nursing_schedule_months WHERE id=?",[$mid],'i');
    $recs=jj_all($conn,"SELECT employee_name,
        SUM(CASE WHEN shift_code IN ('P','P1','P1E','BO1','BO2') THEN 1 ELSE 0 END) pagi,
        SUM(CASE WHEN shift_code IN ('S','S1','S1E') THEN 1 ELSE 0 END) sore,
        SUM(CASE WHEN shift_code IN ('M','M2X','M1','M2') THEN 1 ELSE 0 END) malam,
        SUM(CASE WHEN shift_code IN ('PS','PS1','PSE1') THEN 1 ELSE 0 END) double_shift,
        SUM(CASE WHEN shift_code IN ('L','OFF','CUTI','IZIN','SAKIT','-') THEN 1 ELSE 0 END) libur,
        SUM(incentive_point) poin_insentif,SUM(salary_point) poin_gaji
        FROM nursing_schedule_entries WHERE schedule_month_id=? GROUP BY employee_key,employee_name ORDER BY employee_name",[$mid],'i');
    $fn='rekap_poin_'.preg_replace('/[^a-z0-9]+/i','_',strtolower((string)($mrow['unit_name']??'unit'))).'_'.($mrow['year']??'').'_'.$mrow['month'].'.xls';
    header('Content-Type: application/vnd.ms-excel; charset=utf-8');
    header('Content-Disposition: attachment; filename="'.$fn.'"');
    echo "\xEF\xBB\xBF";
    echo "<table border='1'><tr><th colspan='9'>Rekap Poin Jaga</th></tr>";
    echo "<tr><td colspan='9'>".jj_e(($mrow['unit_name']??'').' - '.jj_month_name((int)($mrow['month']??0)).' '.($mrow['year']??''))."</td></tr>";
    echo "<tr><th>No</th><th>Nama</th><th>Pagi</th><th>Sore</th><th>Malam</th><th>Double</th><th>Libur/Cuti</th><th>Poin Insentif</th><th>Poin Gaji</th></tr>";
    foreach($recs as $i=>$r) echo "<tr><td>".($i+1)."</td><td>".jj_e($r['employee_name'])."</td><td>{$r['pagi']}</td><td>{$r['sore']}</td><td>{$r['malam']}</td><td>{$r['double_shift']}</td><td>{$r['libur']}</td><td>{$r['poin_insentif']}</td><td>{$r['poin_gaji']}</td></tr>";
    echo "</table>"; exit;
}

$scheduleMonth=jj_one($conn,"SELECT * FROM nursing_schedule_months WHERE unit_id=? AND month=? AND year=?",[$unitId,$month,$year],'iii');
$scheduleMonthId=(int)($scheduleMonth['id']??0);
$staff=jj_unit_staff($conn,$unitId);
$entries=[];
$staffSchedule=[];
if($scheduleMonthId){
    $entries=jj_all($conn,"SELECT * FROM nursing_schedule_entries WHERE schedule_month_id=? ORDER BY employee_name,schedule_date",[$scheduleMonthId],'i');
    $existingCodes = [];
    foreach ($entries as $er) { $existingCodes[] = (string)($er['shift_code'] ?? ''); }
    jj_ensure_shift_codes($conn, array_unique($existingCodes));
    $shiftMap = jj_shift_map($conn);
    foreach($entries as $r){
        $k=(string)$r['employee_key'];
        if(!isset($staffSchedule[$k]))$staffSchedule[$k]=['name'=>$r['employee_name'],'employee_id'=>$r['employee_id'],'days'=>[],'ip'=>0,'sp'=>0];
        $d=(int)date('j',strtotime((string)$r['schedule_date']));
        $staffSchedule[$k]['days'][$d]=$r;
        $staffSchedule[$k]['ip']+=(float)$r['incentive_point'];
        $staffSchedule[$k]['sp']+=(float)$r['salary_point'];
    }
}
$pointRows=$scheduleMonthId?jj_all($conn,"SELECT employee_key,MAX(employee_name) employee_name,
    SUM(CASE WHEN canonical.canonical_code='P' THEN 1 ELSE 0 END) pagi,
    SUM(CASE WHEN canonical.canonical_code='S' THEN 1 ELSE 0 END) sore,
    SUM(CASE WHEN canonical.canonical_code='M' THEN 1 ELSE 0 END) malam,
    SUM(CASE WHEN canonical.canonical_code='PS' THEN 1 ELSE 0 END) double_shift,
    SUM(CASE WHEN canonical.canonical_code='L' THEN 1 ELSE 0 END) libur,
    SUM(e.incentive_point) poin_insentif,SUM(e.salary_point) poin_gaji
    FROM nursing_schedule_entries e
    LEFT JOIN nursing_shift_master canonical ON canonical.shift_code=e.shift_code
    WHERE e.schedule_month_id=? GROUP BY e.employee_key ORDER BY employee_name",[$scheduleMonthId],'i'):[];
$coverage=$scheduleMonthId?jj_all($conn,"SELECT schedule_date,
    SUM(CASE WHEN COALESCE(sm.canonical_code,'OTHER')='P' THEN 1 ELSE 0 END) pagi,
    SUM(CASE WHEN COALESCE(sm.canonical_code,'OTHER')='S' THEN 1 ELSE 0 END) sore,
    SUM(CASE WHEN COALESCE(sm.canonical_code,'OTHER')='M' THEN 1 ELSE 0 END) malam,
    SUM(CASE WHEN COALESCE(sm.canonical_code,'OTHER')='L' THEN 1 ELSE 0 END) libur
    FROM nursing_schedule_entries e LEFT JOIN nursing_shift_master sm ON sm.shift_code=e.shift_code
    WHERE schedule_month_id=? GROUP BY schedule_date ORDER BY schedule_date",[$scheduleMonthId],'i'):[];
$history=jj_all($conn,"SELECT m.*,COUNT(DISTINCT e.employee_key) staff_count,COUNT(e.id) cell_count
    FROM nursing_schedule_months m LEFT JOIN nursing_schedule_entries e ON e.schedule_month_id=m.id
    GROUP BY m.id ORDER BY m.year DESC,m.month DESC,m.unit_name LIMIT 30");
$shiftRows=jj_all($conn,"SELECT * FROM nursing_shift_master ORDER BY canonical_code,shift_code");
$csrf=jj_token();
$base='?module=jadwal_jaga_poin';
?>
<style>
.jj-wrap{font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif;color:#0f172a}
.jj-hero{background:linear-gradient(120deg,#2563eb,#7c3aed);color:#fff;padding:26px 30px;border-radius:28px;margin-bottom:18px;box-shadow:0 18px 45px rgba(37,99,235,.18)}
.jj-hero h1{font-size:30px;margin:0 0 6px;font-weight:800}.jj-hero p{margin:0;opacity:.92}
.jj-nav{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}
.jj-nav a{padding:10px 16px;border:1px solid #dbe4f0;border-radius:999px;text-decoration:none;color:#172033;background:#fff;font-weight:700}
.jj-nav a.active{background:#172033;color:#fff;border-color:#172033}
.jj-card{background:#fff;border:1px solid #e5eaf2;border-radius:22px;padding:20px;margin-bottom:18px;box-shadow:0 10px 30px rgba(15,23,42,.05)}
.jj-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}.jj-grid.two{grid-template-columns:repeat(2,minmax(0,1fr))}
.jj-field label{display:block;font-size:12px;font-weight:800;color:#64748b;margin-bottom:6px;text-transform:uppercase}
.jj-field input,.jj-field select{width:100%;padding:11px 12px;border:1px solid #d8e0ec;border-radius:12px;background:#fff;box-sizing:border-box}
.jj-btn{display:inline-flex;align-items:center;gap:7px;padding:11px 16px;border:0;border-radius:12px;background:#2563eb;color:#fff;font-weight:800;cursor:pointer;text-decoration:none}
.jj-btn.dark{background:#172033}.jj-btn.green{background:#059669}.jj-btn.gray{background:#e8eef6;color:#172033}
.jj-alert{padding:13px 16px;border-radius:13px;margin:12px 0;font-weight:700}.jj-alert.ok{background:#ecfdf5;color:#047857}.jj-alert.err{background:#fff1f2;color:#be123c}
.jj-table-wrap{overflow:auto;border:1px solid #e5eaf2;border-radius:16px}
.jj-table{width:100%;border-collapse:collapse;min-width:980px}.jj-table th,.jj-table td{border-bottom:1px solid #e6ebf2;padding:9px 10px;text-align:center;white-space:nowrap}
.jj-table th{background:#f8fafc;font-size:11px;text-transform:uppercase;color:#475569;position:sticky;top:0;z-index:2}
.jj-table td.name,.jj-table th.name{text-align:left;position:sticky;left:0;background:#fff;z-index:1;min-width:250px}
.jj-table th.name{background:#f8fafc;z-index:3}
.jj-shift{min-width:52px;padding:6px 4px;border:1px solid transparent;border-radius:8px;font-weight:800;text-align:center}
.jj-pagi{background:#fef3c7;color:#92400e}.jj-sore{background:#dbeafe;color:#1d4ed8}.jj-malam{background:#ede9fe;color:#6d28d9}.jj-libur{background:#f1f5f9;color:#64748b}.jj-double{background:#dcfce7;color:#15803d}.jj-other{background:#ffe4e6;color:#be123c}
.jj-kpi{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.jj-kpi>div{background:#f8fafc;border-radius:16px;padding:14px}.jj-kpi b{font-size:24px;display:block}
.jj-badge{display:inline-block;padding:5px 9px;border-radius:999px;font-size:11px;font-weight:800;background:#edf2f7}
.jj-group-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.jj-group{border:1px solid #e2e8f0;border-radius:16px;padding:12px;background:#f8fafc}.jj-group h4{margin:0 0 10px}
@media(max-width:900px){.jj-grid,.jj-grid.two,.jj-kpi,.jj-group-grid{grid-template-columns:1fr}}
@media print{.jj-nav,.jj-filter,.jj-btn,.jj-no-print{display:none!important}.jj-card{box-shadow:none}.jj-table-wrap{overflow:visible}.jj-table{font-size:9px}}
</style>

<div class="jj-wrap">
  <div class="jj-hero">
    <h1>Jadwal Jaga & Poin Keperawatan</h1>
    <p>Upload jadwal Excel, tampilkan dan edit di UI, hitung poin, lalu lanjutkan pola P–P–S–S–M–M–L–L atau pola 2 shift P/S ke bulan berikutnya.</p>
  </div>

  <?php if($notice):?><div class="jj-alert ok"><?=jj_e($notice)?></div><?php endif;?>
  <?php if($error):?><div class="jj-alert err"><?=jj_e($error)?></div><?php endif;?>

  <div class="jj-nav">
    <?php foreach(['dashboard'=>'Dashboard','upload'=>'Upload Excel','schedule'=>'Jadwal Bulanan','generate'=>'Generate Bulan Berikutnya','points'=>'Rekap Poin','master'=>'Master Shift','history'=>'Riwayat'] as $k=>$label):?>
      <a class="<?=$tab===$k?'active':''?>" href="<?=$base?>&tab=<?=$k?>&unit_id=<?=$unitId?>&month=<?=$month?>&year=<?=$year?>"><?=jj_e($label)?></a>
    <?php endforeach;?>
  </div>

  <form class="jj-card jj-filter" method="get">
    <input type="hidden" name="module" value="jadwal_jaga_poin">
    <input type="hidden" name="tab" value="<?=jj_e($tab)?>">
    <div class="jj-grid">
      <div class="jj-field"><label>Unit</label><select name="unit_id"><?php foreach($units as $u):?><option value="<?=$u['id']?>" <?=$unitId==(int)$u['id']?'selected':''?>><?=jj_e($u['name'])?></option><?php endforeach;?></select></div>
      <div class="jj-field"><label>Bulan</label><select name="month"><?php for($m=1;$m<=12;$m++):?><option value="<?=$m?>" <?=$month===$m?'selected':''?>><?=jj_month_name($m)?></option><?php endfor;?></select></div>
      <div class="jj-field"><label>Tahun</label><input type="number" name="year" value="<?=$year?>"></div>
      <div class="jj-field"><label>&nbsp;</label><button class="jj-btn dark">Terapkan Filter</button></div>
    </div>
  </form>

<?php if($tab==='dashboard'):?>
  <div class="jj-card">
    <h2>Ringkasan Periode</h2>
    <div class="jj-kpi">
      <div><span>Unit</span><b><?=jj_e($unitName?:'-')?></b></div>
      <div><span>Periode</span><b><?=jj_month_name($month).' '.$year?></b></div>
      <div><span>Staf Terjadwal</span><b><?=count($staffSchedule)?></b></div>
      <div><span>Status</span><b><?=jj_e($scheduleMonth['status']??'Belum Ada')?></b></div>
    </div>
  </div>
  <div class="jj-card">
    <h2>Kecukupan Staf per Hari</h2>
    <?php if(!$coverage):?><p>Belum ada jadwal pada periode ini.</p><?php else:?>
    <div class="jj-table-wrap"><table class="jj-table">
      <thead><tr><th>Tanggal</th><th>Pagi</th><th>Sore</th><th>Malam</th><th>Libur</th><th>Validasi Kebutuhan</th></tr></thead>
      <tbody><?php foreach($coverage as $r):
        $okP=(int)$r['pagi']>=(int)($scheduleMonth['need_morning']??0);
        $okS=(int)$r['sore']>=(int)($scheduleMonth['need_evening']??0);
        $okM=(int)$r['malam']>=(int)($scheduleMonth['need_night']??0);
      ?><tr>
        <td><?=date('d/m/Y',strtotime($r['schedule_date']))?></td><td><?=$r['pagi']?></td><td><?=$r['sore']?></td><td><?=$r['malam']?></td><td><?=$r['libur']?></td>
        <td><?=($okP&&$okS&&$okM)?'<span class="jj-badge">Cukup</span>':'<span class="jj-badge" style="background:#fff1f2;color:#be123c">Perlu Penyesuaian</span>'?></td>
      </tr><?php endforeach;?></tbody>
    </table></div><?php endif;?>
  </div>

<?php elseif($tab==='upload'):?>
  <div class="jj-card">
    <h2>Upload Jadwal Excel</h2>
    <p>Sistem mendeteksi otomatis baris tanggal, kolom nama staf, dan seluruh kode jadwal pada Excel. Jadwal dibaca persis seperti file tanpa perlu diketik ulang.</p>
    <form method="post" enctype="multipart/form-data">
      <input type="hidden" name="csrf" value="<?=$csrf?>"><input type="hidden" name="action" value="upload_schedule">
      <div class="jj-grid">
        <div class="jj-field"><label>Unit</label><select name="unit_id" required><?php foreach($units as $u):?><option value="<?=$u['id']?>" <?=$unitId==(int)$u['id']?'selected':''?>><?=jj_e($u['name'])?></option><?php endforeach;?></select></div>
        <div class="jj-field"><label>Bulan</label><select name="month"><?php for($m=1;$m<=12;$m++):?><option value="<?=$m?>" <?=$month===$m?'selected':''?>><?=jj_month_name($m)?></option><?php endfor;?></select></div>
        <div class="jj-field"><label>Tahun</label><input type="number" name="year" value="<?=$year?>" required></div>
        <div class="jj-field"><label>File .xlsx</label><input type="file" name="schedule_file" accept=".xlsx" required></div>
      </div><br><button class="jj-btn green">Upload dan Baca Jadwal</button>
    </form>
  </div>

<?php elseif($tab==='schedule'):?>
  <div class="jj-card">
    <div style="display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap">
      <div><h2 style="margin:0">Jadwal Bulanan <?=jj_e($unitName)?> — <?=jj_month_name($month).' '.$year?></h2>
      <p><?=count($staffSchedule)?> staf · Sumber: <?=jj_e($scheduleMonth['source_type']??'-')?> · Status: <?=jj_e($scheduleMonth['status']??'-')?></p></div>
      <?php if($scheduleMonthId):?><div class="jj-no-print">
        <a class="jj-btn gray" href="<?=$base?>&tab=points&unit_id=<?=$unitId?>&month=<?=$month?>&year=<?=$year?>">Lihat Poin</a>
        <button class="jj-btn dark" onclick="window.print()">Print</button>
      </div><?php endif;?>
    </div>
    <?php if(!$scheduleMonthId):?><p>Belum ada jadwal. Upload Excel atau generate dari bulan sebelumnya.</p><?php else:?>
    <form method="post">
      <input type="hidden" name="csrf" value="<?=$csrf?>"><input type="hidden" name="action" value="save_schedule_grid"><input type="hidden" name="schedule_month_id" value="<?=$scheduleMonthId?>">
      <div class="jj-table-wrap"><table class="jj-table">
        <thead><tr><th class="name">Nama Staf</th><?php for($d=1;$d<=jj_days($month,$year);$d++):?><th><?=$d?></th><?php endfor;?><th>Poin</th></tr></thead>
        <tbody>
        <?php foreach($staffSchedule as $s):?><tr>
          <td class="name"><b><?=jj_e($s['name'])?></b></td>
          <?php for($d=1;$d<=jj_days($month,$year);$d++):$r=$s['days'][$d]??null;$code=(string)($r['shift_code']??'');?>
            <td><select class="jj-shift <?=jj_shift_class($code)?>" name="shift[<?=isset($r['id'])?(int)$r['id']:0?>]" onchange="jjColor(this)">
              <?php
                $hasCurrentCode = false;
                foreach($shiftRows as $sr):
                  if ($code === (string)$sr['shift_code']) $hasCurrentCode = true;
              ?>
                <option value="<?=jj_e($sr['shift_code'])?>" <?=$code===$sr['shift_code']?'selected':''?>><?=jj_e($sr['shift_code'])?></option>
              <?php endforeach; ?>
              <?php if($code!=='' && !$hasCurrentCode): ?>
                <option value="<?=jj_e($code)?>" selected><?=jj_e($code)?></option>
              <?php endif; ?>
            </select></td>
          <?php endfor;?>
          <td><b><?=number_format((float)$s['ip'],1)?></b></td>
        </tr><?php endforeach;?>
        </tbody>
      </table></div><br>
      <button class="jj-btn">Simpan Perubahan Jadwal</button>
    </form>
    <?php if(($scheduleMonth['status']??'')!=='published'):?><form method="post" style="display:inline-block;margin-top:10px">
      <input type="hidden" name="csrf" value="<?=$csrf?>"><input type="hidden" name="action" value="publish_month"><input type="hidden" name="schedule_month_id" value="<?=$scheduleMonthId?>">
      <button class="jj-btn green">Publikasikan Jadwal</button>
    </form><?php endif;?>
    <?php endif;?>
  </div>

<?php elseif($tab==='generate'):?>
  <div class="jj-card">
    <h2>Generate Jadwal Bulan Berikutnya</h2>
    <?php if(!$scheduleMonthId):?><p>Pilih periode sumber yang sudah memiliki jadwal.</p><?php else:
      $nextMonth=$month===12?1:$month+1;$nextYear=$month===12?$year+1:$year;?>
    <form method="post" id="generateForm">
      <input type="hidden" name="csrf" value="<?=$csrf?>"><input type="hidden" name="action" value="generate_next"><input type="hidden" name="source_month_id" value="<?=$scheduleMonthId?>">
      <div class="jj-grid">
        <div class="jj-field"><label>Bulan Tujuan</label><select name="target_month"><?php for($m=1;$m<=12;$m++):?><option value="<?=$m?>" <?=$m===$nextMonth?'selected':''?>><?=jj_month_name($m)?></option><?php endfor;?></select></div>
        <div class="jj-field"><label>Tahun Tujuan</label><input type="number" name="target_year" value="<?=$nextYear?>"></div>
        <div class="jj-field"><label>Metode</label><select name="generate_mode" id="genMode" onchange="jjMode()">
          <option value="continue">Lanjutkan otomatis dari pola bulan sumber</option>
          <option value="needs">Bagi otomatis berdasarkan kebutuhan per shift</option>
          <option value="groups">Tentukan grup awal tiap staf</option>
        </select></div>
        <div class="jj-field"><label>Pola Jadwal</label><select name="pattern_key" id="patternKey" onchange="jjMode()">
          <?php foreach(jj_cycle_patterns() as $pk=>$pp): ?>
            <option value="<?=jj_e($pk)?>" <?=$pk==='3SHIFT_PPSSMMLL'?'selected':''?>><?=jj_e($pp['label'])?></option>
          <?php endforeach; ?>
        </select></div>
      </div>
      <div class="jj-card" style="background:#f8fafc;margin-top:14px;margin-bottom:0;padding:14px">
        <div style="font-weight:800">Keterangan Pola</div>
        <div id="patternDescription" style="font-size:13px;color:#64748b;margin-top:4px">Pola 8 hari: dua pagi, dua sore, dua malam, dua libur.</div>
      </div>
      <div class="jj-grid" style="margin-top:14px">
        <div class="jj-field"><label>Kebutuhan Pagi</label><input type="number" min="0" name="need_p" value="<?=$scheduleMonth['need_morning']??0?>"></div>
        <div class="jj-field"><label>Kebutuhan Sore</label><input type="number" min="0" name="need_s" value="<?=$scheduleMonth['need_evening']??0?>"></div>
        <div class="jj-field" id="needNightBox"><label>Kebutuhan Malam</label><input type="number" min="0" name="need_m" value="<?=$scheduleMonth['need_night']??0?>"></div>
        <div class="jj-field"><label>Total Staf Sumber</label><input value="<?=count($staffSchedule)?>" readonly></div>
      </div>
      <div class="jj-card" style="background:#fff7ed;border-color:#fed7aa;margin-top:14px;margin-bottom:0;padding:14px">
        <div style="font-weight:900;color:#9a3412">Aturan Staf Back Office / BO1</div>
        <div style="font-size:13px;color:#9a3412;margin-top:4px">
          Staf yang dominan memiliki jadwal BO/BO1/BO2 pada bulan sumber akan otomatis tertahan sebagai BO pada Senin–Jumat.
          Sabtu, Minggu, dan tanggal libur khusus akan dibuat <b>L</b>.
        </div>
        <div class="jj-field" style="margin-top:10px">
          <label>Tanggal Libur Khusus/Nasional Bulan Tujuan</label>
          <input name="holiday_dates" placeholder="Contoh: 2026-07-17, 25/07/2026, 26/07">
          <div style="font-size:12px;color:#9a3412;margin-top:5px">Pisahkan dengan koma/spasi. Sabtu dan Minggu otomatis dianggap libur.</div>
        </div>
      </div>
      <div id="groupBox" style="display:none;margin-top:18px">
        <h3>Tentukan Grup Awal Staf</h3>
        <div class="jj-table-wrap"><table class="jj-table"><thead><tr><th class="name">Staf</th><th>Grup Awal</th></tr></thead><tbody>
        <?php $i=0;foreach($staffSchedule as $key=>$s):$default=['P','S','M','L'][$i%4];$i++;?>
          <tr><td class="name"><?=jj_e($s['name'])?></td><td><select class="groupSelect" name="group[<?=jj_e($key)?>]">
            <option value="P" <?=$default==='P'?'selected':''?>>Grup Pagi</option><option value="S" <?=$default==='S'?'selected':''?>>Grup Sore</option>
            <option value="M" <?=$default==='M'?'selected':''?>>Grup Malam</option><option value="L" <?=$default==='L'?'selected':''?>>Grup Libur</option>
          </select></td></tr>
        <?php endforeach;?></tbody></table></div>
      </div><br>
      <button class="jj-btn green" onclick="return confirm('Generate jadwal bulan tujuan? Data draft bulan tujuan akan diganti.')">Generate Jadwal</button>
    </form><?php endif;?>
  </div>

<?php elseif($tab==='points'):?>
  <div class="jj-card">
    <div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap">
      <div><h2>Rekap Poin Jaga</h2><p><?=jj_e($unitName)?> — <?=jj_month_name($month).' '.$year?></p></div>
      <?php if($scheduleMonthId):?><a class="jj-btn green" href="<?=$base?>&tab=points&unit_id=<?=$unitId?>&month=<?=$month?>&year=<?=$year?>&export=points&schedule_month_id=<?=$scheduleMonthId?>">Export Excel</a><?php endif;?>
    </div>
    <?php if(!$pointRows):?><p>Belum ada data poin.</p><?php else:?>
    <div class="jj-table-wrap"><table class="jj-table"><thead><tr><th>No</th><th class="name">Nama</th><th>Pagi</th><th>Sore</th><th>Malam</th><th>Double</th><th>Libur/Cuti</th><th>Poin Insentif</th><th>Poin Gaji</th></tr></thead><tbody>
    <?php foreach($pointRows as $i=>$r):?><tr><td><?=$i+1?></td><td class="name"><b><?=jj_e($r['employee_name'])?></b></td><td><?=$r['pagi']?></td><td><?=$r['sore']?></td><td><?=$r['malam']?></td><td><?=$r['double_shift']?></td><td><?=$r['libur']?></td><td><b><?=number_format((float)$r['poin_insentif'],1)?></b></td><td><?=number_format((float)$r['poin_gaji'],1)?></td></tr><?php endforeach;?>
    </tbody></table></div><?php endif;?>
  </div>

<?php elseif($tab==='master'):?>
  <div class="jj-grid two">
    <div class="jj-card"><h2>Tambah/Ubah Master Shift</h2><form method="post">
      <input type="hidden" name="csrf" value="<?=$csrf?>"><input type="hidden" name="action" value="save_shift_master">
      <div class="jj-field"><label>Kode</label><input name="shift_code" required></div><br>
      <div class="jj-field"><label>Nama Shift</label><input name="shift_name" required></div><br>
      <div class="jj-field"><label>Kelompok</label><select name="canonical_code"><option>P</option><option>S</option><option>M</option><option>L</option><option>PS</option><option>OTHER</option></select></div><br>
      <div class="jj-grid two"><div class="jj-field"><label>Poin Insentif</label><input type="number" step=".01" name="incentive_point" value="0"></div><div class="jj-field"><label>Poin Gaji</label><input type="number" step=".01" name="salary_point" value="0"></div></div><br>
      <label><input type="checkbox" name="is_working" checked> Termasuk hari kerja</label><br><br><button class="jj-btn">Simpan Master</button>
    </form></div>
    <div class="jj-card"><h2>Daftar Master Shift</h2><div class="jj-table-wrap"><table class="jj-table"><thead><tr><th>Kode</th><th>Nama</th><th>Kelompok</th><th>Poin Insentif</th><th>Poin Gaji</th></tr></thead><tbody>
      <?php foreach($shiftRows as $r):?><tr><td><b><?=jj_e($r['shift_code'])?></b></td><td><?=jj_e($r['shift_name'])?></td><td><?=jj_e($r['canonical_code'])?></td><td><?=$r['incentive_point']?></td><td><?=$r['salary_point']?></td></tr><?php endforeach;?>
    </tbody></table></div></div>
  </div>

<?php elseif($tab==='history'):?>
  <div class="jj-card"><h2>Riwayat Jadwal</h2><div class="jj-table-wrap"><table class="jj-table"><thead><tr><th>Unit</th><th>Periode</th><th>Sumber</th><th>Status</th><th>Staf</th><th>Sel Jadwal</th><th>Aksi</th></tr></thead><tbody>
  <?php foreach($history as $r):?><tr><td><?=jj_e($r['unit_name'])?></td><td><?=jj_month_name((int)$r['month']).' '.$r['year']?></td><td><?=jj_e($r['source_type'])?></td><td><?=jj_e($r['status'])?></td><td><?=$r['staff_count']?></td><td><?=$r['cell_count']?></td><td><a class="jj-btn gray" href="<?=$base?>&tab=schedule&unit_id=<?=$r['unit_id']?>&month=<?=$r['month']?>&year=<?=$r['year']?>">Buka</a></td></tr><?php endforeach;?>
  </tbody></table></div></div>
<?php endif;?>
</div>

<script>
function jjColor(el){
  el.className='jj-shift';
  const c=el.value.toUpperCase();
  if(['L','OFF','CUTI','IZIN','SAKIT','-'].includes(c))el.classList.add('jj-libur');
  else if(['PS','PS1','PSE1','EPES1'].includes(c))el.classList.add('jj-double');
  else if(/^BO\d*$/.test(c) || /^P\d*[A-Z]*$/.test(c) || c==='P')el.classList.add('jj-pagi');
  else if(/^S\d*[A-Z]*$/.test(c) || c==='S')el.classList.add('jj-sore');
  else if(/^M\d*[A-Z]*$/.test(c) || c==='M')el.classList.add('jj-malam');
  else el.classList.add('jj-other');
}
function jjMode(){
  const m=document.getElementById('genMode');
  const box=document.getElementById('groupBox');
  const pattern=document.getElementById('patternKey');
  const needNight=document.getElementById('needNightBox');
  const desc=document.getElementById('patternDescription');
  const patterns={
    '3SHIFT_PPSSMMLL':{usesNight:true,desc:'Pola 8 hari: dua pagi, dua sore, dua malam, dua libur.',groups:['P','S','M','L']},
    '2SHIFT_PPSLL':{usesNight:false,desc:'Pola 6 hari: dua pagi, dua sore, dua libur. Tidak memakai shift malam.',groups:['P','S','L']},
    '2SHIFT_PSLL':{usesNight:false,desc:'Pola 4 hari: pagi, sore, dua libur. Cocok untuk unit kecil/non-malam.',groups:['P','S','L']}
  };
  const cfg=patterns[pattern?.value || '3SHIFT_PPSSMMLL'] || patterns['3SHIFT_PPSSMMLL'];
  if(m&&box)box.style.display=m.value==='groups'?'block':'none';
  if(needNight) needNight.style.display=cfg.usesNight?'block':'none';
  if(desc) desc.textContent=cfg.desc;
  document.querySelectorAll('.groupSelect').forEach(sel=>{
    [...sel.options].forEach(opt=>{
      opt.disabled=!cfg.groups.includes(opt.value);
      opt.hidden=!cfg.groups.includes(opt.value);
    });
    if(!cfg.groups.includes(sel.value)) sel.value='L';
  });
}
document.addEventListener('DOMContentLoaded',jjMode);
</script>
